Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6aab18704a
commit
b0107e8756
|
|
@ -328,6 +328,9 @@ Cop/SidekiqOptionsQueue:
|
||||||
|
|
||||||
Graphql/AuthorizeTypes:
|
Graphql/AuthorizeTypes:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
Include:
|
||||||
|
- 'app/graphql/types/**/*'
|
||||||
|
- 'ee/app/graphql/types/**/*'
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/**/*.rb'
|
- 'spec/**/*.rb'
|
||||||
- 'ee/spec/**/*.rb'
|
- 'ee/spec/**/*.rb'
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import LogLine from './line.vue';
|
||||||
import LogLineHeader from './line_header.vue';
|
import LogLineHeader from './line_header.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CollpasibleLogSection',
|
name: 'CollapsibleLogSection',
|
||||||
components: {
|
components: {
|
||||||
LogLine,
|
LogLine,
|
||||||
LogLineHeader,
|
LogLineHeader,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapState, mapActions } from 'vuex';
|
||||||
import CollpasibleLogSection from './collapsible_section.vue';
|
import CollapsibleLogSection from './collapsible_section.vue';
|
||||||
import LogLine from './line.vue';
|
import LogLine from './line.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CollpasibleLogSection,
|
CollapsibleLogSection,
|
||||||
LogLine,
|
LogLine,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -51,7 +51,7 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<code class="job-log d-block" data-qa-selector="job_log_content">
|
<code class="job-log d-block" data-qa-selector="job_log_content">
|
||||||
<template v-for="(section, index) in trace">
|
<template v-for="(section, index) in trace">
|
||||||
<collpasible-log-section
|
<collapsible-log-section
|
||||||
v-if="section.isHeader"
|
v-if="section.isHeader"
|
||||||
:key="`collapsible-${index}`"
|
:key="`collapsible-${index}`"
|
||||||
:section="section"
|
:section="section"
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ export const receiveTraceError = ({ dispatch }) => {
|
||||||
flash(__('An error occurred while fetching the job log.'));
|
flash(__('An error occurred while fetching the job log.'));
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* When the user clicks a collpasible line in the job
|
* When the user clicks a collapsible line in the job
|
||||||
* log, we commit a mutation to update the state
|
* log, we commit a mutation to update the state
|
||||||
*
|
*
|
||||||
* @param {Object} section
|
* @param {Object} section
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const parseLine = (line = {}, lineNumber) => ({
|
||||||
/**
|
/**
|
||||||
* When a line has `section_header` set to true, we create a new
|
* When a line has `section_header` set to true, we create a new
|
||||||
* structure to allow to nest the lines that belong to the
|
* structure to allow to nest the lines that belong to the
|
||||||
* collpasible section
|
* collapsible section
|
||||||
*
|
*
|
||||||
* @param Object line
|
* @param Object line
|
||||||
* @param Number lineNumber
|
* @param Number lineNumber
|
||||||
|
|
@ -91,7 +91,7 @@ export const getIncrementalLineNumber = acc => {
|
||||||
* Parses the job log content into a structure usable by the template
|
* Parses the job log content into a structure usable by the template
|
||||||
*
|
*
|
||||||
* For collaspible lines (section_header = true):
|
* For collaspible lines (section_header = true):
|
||||||
* - creates a new array to hold the lines that are collpasible,
|
* - creates a new array to hold the lines that are collapsible,
|
||||||
* - adds a isClosed property to handle toggle
|
* - adds a isClosed property to handle toggle
|
||||||
* - adds a isHeader property to handle template logic
|
* - adds a isHeader property to handle template logic
|
||||||
* - adds the section_duration
|
* - adds the section_duration
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script>
|
||||||
|
import { GlTabs, GlTab, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import ForkGroupsListItem from './fork_groups_list_item.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GlTabs,
|
||||||
|
GlTab,
|
||||||
|
GlLoadingIcon,
|
||||||
|
GlSearchBoxByType,
|
||||||
|
ForkGroupsListItem,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
hasReachedProjectLimit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
endpoint: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
namespaces: null,
|
||||||
|
filter: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredNamespaces() {
|
||||||
|
return this.namespaces.filter(n => n.name.toLowerCase().includes(this.filter.toLowerCase()));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadGroups();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
loadGroups() {
|
||||||
|
axios
|
||||||
|
.get(this.endpoint)
|
||||||
|
.then(response => {
|
||||||
|
this.namespaces = response.data.namespaces;
|
||||||
|
})
|
||||||
|
.catch(() => createFlash(__('There was a problem fetching groups.')));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
i18n: {
|
||||||
|
searchPlaceholder: __('Search by name'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<gl-tabs class="fork-groups">
|
||||||
|
<gl-tab :title="__('Groups and subgroups')">
|
||||||
|
<gl-loading-icon v-if="!namespaces" size="md" class="gl-mt-3" />
|
||||||
|
<template v-else-if="namespaces.length === 0">
|
||||||
|
<div class="gl-text-center">
|
||||||
|
<div class="h5">{{ __('No available groups to fork the project.') }}</div>
|
||||||
|
<p class="gl-mt-5">
|
||||||
|
{{ __('You must have permission to create a project in a group before forking.') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else-if="filteredNamespaces.length === 0" class="gl-text-center gl-mt-3">
|
||||||
|
{{ s__('GroupsTree|No groups matched your search') }}
|
||||||
|
</div>
|
||||||
|
<ul v-else class="groups-list group-list-tree">
|
||||||
|
<fork-groups-list-item
|
||||||
|
v-for="(namespace, index) in filteredNamespaces"
|
||||||
|
:key="index"
|
||||||
|
:group="namespace"
|
||||||
|
:has-reached-project-limit="hasReachedProjectLimit"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</gl-tab>
|
||||||
|
<template #tabs-end>
|
||||||
|
<gl-search-box-by-type
|
||||||
|
v-if="namespaces && namespaces.length"
|
||||||
|
v-model="filter"
|
||||||
|
:placeholder="$options.i18n.searchPlaceholder"
|
||||||
|
class="gl-align-self-center gl-ml-auto fork-filtered-search"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</gl-tabs>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
GlLink,
|
||||||
|
GlButton,
|
||||||
|
GlIcon,
|
||||||
|
GlAvatar,
|
||||||
|
GlTooltipDirective,
|
||||||
|
GlTooltip,
|
||||||
|
GlBadge,
|
||||||
|
} from '@gitlab/ui';
|
||||||
|
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import csrf from '~/lib/utils/csrf';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
GlIcon,
|
||||||
|
GlAvatar,
|
||||||
|
GlBadge,
|
||||||
|
GlButton,
|
||||||
|
GlTooltip,
|
||||||
|
GlLink,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
GlTooltip: GlTooltipDirective,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
group: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hasReachedProjectLimit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return { namespaces: null };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
rowClass() {
|
||||||
|
return {
|
||||||
|
'has-description': this.group.description,
|
||||||
|
'being-removed': this.isGroupPendingRemoval,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isGroupPendingRemoval() {
|
||||||
|
return this.group.marked_for_deletion;
|
||||||
|
},
|
||||||
|
hasForkedProject() {
|
||||||
|
return Boolean(this.group.forked_project_path);
|
||||||
|
},
|
||||||
|
visibilityIcon() {
|
||||||
|
return VISIBILITY_TYPE_ICON[this.group.visibility];
|
||||||
|
},
|
||||||
|
visibilityTooltip() {
|
||||||
|
return GROUP_VISIBILITY_TYPE[this.group.visibility];
|
||||||
|
},
|
||||||
|
isSelectButtonDisabled() {
|
||||||
|
return this.hasReachedProjectLimit || !this.group.can_create_project;
|
||||||
|
},
|
||||||
|
selectButtonDisabledTooltip() {
|
||||||
|
return this.hasReachedProjectLimit
|
||||||
|
? this.$options.i18n.hasReachedProjectLimitMessage
|
||||||
|
: this.$options.i18n.insufficientPermissionsMessage;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
i18n: {
|
||||||
|
hasReachedProjectLimitMessage: __('You have reached your project limit'),
|
||||||
|
insufficientPermissionsMessage: __(
|
||||||
|
'You must have permission to create a project in a namespace before forking.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
csrf,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<li :class="rowClass" class="group-row">
|
||||||
|
<div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
|
||||||
|
<div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center">
|
||||||
|
<gl-icon name="folder-o" />
|
||||||
|
</div>
|
||||||
|
<gl-link
|
||||||
|
:href="group.relative_path"
|
||||||
|
class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3"
|
||||||
|
>
|
||||||
|
<gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatarUrl" />
|
||||||
|
</gl-link>
|
||||||
|
<div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
|
||||||
|
<div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
|
||||||
|
<div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
|
||||||
|
<gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{
|
||||||
|
group.full_name
|
||||||
|
}}</gl-link>
|
||||||
|
<gl-icon
|
||||||
|
v-gl-tooltip.hover.bottom
|
||||||
|
class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary"
|
||||||
|
:name="visibilityIcon"
|
||||||
|
:title="visibilityTooltip"
|
||||||
|
/>
|
||||||
|
<gl-badge
|
||||||
|
v-if="isGroupPendingRemoval"
|
||||||
|
variant="warning"
|
||||||
|
class="gl-display-none gl-display-sm-flex gl-mt-3 gl-mr-1"
|
||||||
|
>{{ __('pending removal') }}</gl-badge
|
||||||
|
>
|
||||||
|
<span v-if="group.permission" class="user-access-role gl-mt-3">
|
||||||
|
{{ group.permission }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="group.description" class="description">
|
||||||
|
<span v-html="group.markdown_description"> </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gl-display-flex gl-flex-shrink-0">
|
||||||
|
<gl-button
|
||||||
|
v-if="hasForkedProject"
|
||||||
|
class="gl-h-7 gl-text-decoration-none!"
|
||||||
|
:href="group.forked_project_path"
|
||||||
|
>{{ __('Go to fork') }}</gl-button
|
||||||
|
>
|
||||||
|
<template v-else>
|
||||||
|
<div ref="selectButtonWrapper">
|
||||||
|
<form method="POST" :action="group.fork_path">
|
||||||
|
<input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
|
||||||
|
<gl-button
|
||||||
|
type="submit"
|
||||||
|
class="gl-h-7 gl-text-decoration-none!"
|
||||||
|
:data-qa-name="group.full_name"
|
||||||
|
variant="success"
|
||||||
|
:disabled="isSelectButtonDisabled"
|
||||||
|
>{{ __('Select') }}</gl-button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper">
|
||||||
|
{{ selectButtonDisabledTooltip }}
|
||||||
|
</gl-tooltip>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
@ -17,11 +17,8 @@ module Routable
|
||||||
|
|
||||||
after_validation :set_path_errors
|
after_validation :set_path_errors
|
||||||
|
|
||||||
before_validation do
|
before_validation :prepare_route
|
||||||
if full_path_changed? || full_name_changed?
|
before_save :prepare_route # in case validation is skipped
|
||||||
prepare_route
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
@ -118,6 +115,8 @@ module Routable
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_route
|
def prepare_route
|
||||||
|
return unless full_path_changed? || full_name_changed?
|
||||||
|
|
||||||
route || build_route(source: self)
|
route || build_route(source: self)
|
||||||
route.path = build_full_path
|
route.path = build_full_path
|
||||||
route.name = build_full_name
|
route.name = build_full_name
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class ProjectStatistics < ApplicationRecord
|
||||||
COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size].freeze
|
COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size].freeze
|
||||||
INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze
|
INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze
|
||||||
NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze
|
NAMESPACE_RELATABLE_COLUMNS = [:repository_size, :wiki_size, :lfs_objects_size].freeze
|
||||||
|
FLAGGED_NAMESPACE_RELATABLE_COLUMNS = [*NAMESPACE_RELATABLE_COLUMNS, :snippets_size].freeze
|
||||||
|
|
||||||
scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
|
scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ class ProjectStatistics < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if only.empty? || only.any? { |column| NAMESPACE_RELATABLE_COLUMNS.include?(column) }
|
if only.empty? || only.any? { |column| namespace_relatable_columns.include?(column) }
|
||||||
schedule_namespace_aggregation_worker
|
schedule_namespace_aggregation_worker
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -110,6 +111,10 @@ class ProjectStatistics < ApplicationRecord
|
||||||
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
|
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def namespace_relatable_columns
|
||||||
|
Feature.enabled?(:namespace_snippets_size_stat) ? FLAGGED_NAMESPACE_RELATABLE_COLUMNS : NAMESPACE_RELATABLE_COLUMNS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ProjectStatistics.prepend_if_ee('EE::ProjectStatistics')
|
ProjectStatistics.prepend_if_ee('EE::ProjectStatistics')
|
||||||
|
|
|
||||||
|
|
@ -1527,7 +1527,7 @@
|
||||||
- :name: project_update_repository_storage
|
- :name: project_update_repository_storage
|
||||||
:feature_category: :gitaly
|
:feature_category: :gitaly
|
||||||
:has_external_dependencies:
|
:has_external_dependencies:
|
||||||
:urgency: :low
|
:urgency: :throttled
|
||||||
:resource_boundary: :unknown
|
:resource_boundary: :unknown
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent: true
|
:idempotent: true
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ class ProjectUpdateRepositoryStorageWorker
|
||||||
|
|
||||||
idempotent!
|
idempotent!
|
||||||
feature_category :gitaly
|
feature_category :gitaly
|
||||||
|
urgency :throttled
|
||||||
|
|
||||||
def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil)
|
def perform(project_id, new_repository_storage_key, repository_storage_move_id = nil)
|
||||||
repository_storage_move =
|
repository_storage_move =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Throttle ProjectUpdateRepositoryStorageWorker Jobs
|
||||||
|
merge_request: 35230
|
||||||
|
author:
|
||||||
|
type: other
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Create associated routes when a new bot user is created
|
||||||
|
merge_request: 35711
|
||||||
|
author:
|
||||||
|
type: fixed
|
||||||
|
|
@ -10,6 +10,7 @@ Sidekiq::Testing.inline! do
|
||||||
# we use randomized approach (e.g. `Array#sample`).
|
# we use randomized approach (e.g. `Array#sample`).
|
||||||
return unless source_project
|
return unless source_project
|
||||||
|
|
||||||
|
Sidekiq::Worker.skipping_transaction_check do
|
||||||
fork_project = Projects::ForkService.new(
|
fork_project = Projects::ForkService.new(
|
||||||
source_project,
|
source_project,
|
||||||
user,
|
user,
|
||||||
|
|
@ -17,11 +18,23 @@ Sidekiq::Testing.inline! do
|
||||||
skip_disk_validation: true
|
skip_disk_validation: true
|
||||||
).execute
|
).execute
|
||||||
|
|
||||||
if fork_project.valid?
|
# Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
|
||||||
|
# hook won't run until after the fixture is loaded. That is too late
|
||||||
|
# since the Sidekiq::Testing block has already exited. Force clearing
|
||||||
|
# the `after_commit` queue to ensure the job is run now.
|
||||||
|
fork_project.send(:_run_after_commit_queue)
|
||||||
|
fork_project.import_state.send(:_run_after_commit_queue)
|
||||||
|
|
||||||
|
# Expire repository cache after import to ensure
|
||||||
|
# valid_repo? call below returns a correct answer
|
||||||
|
fork_project.repository.expire_all_method_caches
|
||||||
|
|
||||||
|
if fork_project.valid? && fork_project.valid_repo?
|
||||||
print '.'
|
print '.'
|
||||||
else
|
else
|
||||||
print 'F'
|
print 'F'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11310,6 +11310,56 @@ type RunDASTScanPayload {
|
||||||
pipelineUrl: String
|
pipelineUrl: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents a resource scanned by a security scan
|
||||||
|
"""
|
||||||
|
type ScannedResource {
|
||||||
|
"""
|
||||||
|
The HTTP request method used to access the URL
|
||||||
|
"""
|
||||||
|
requestMethod: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
The URL scanned by the scanner
|
||||||
|
"""
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The connection type for ScannedResource.
|
||||||
|
"""
|
||||||
|
type ScannedResourceConnection {
|
||||||
|
"""
|
||||||
|
A list of edges.
|
||||||
|
"""
|
||||||
|
edges: [ScannedResourceEdge]
|
||||||
|
|
||||||
|
"""
|
||||||
|
A list of nodes.
|
||||||
|
"""
|
||||||
|
nodes: [ScannedResource]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Information to aid in pagination.
|
||||||
|
"""
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
An edge in a connection.
|
||||||
|
"""
|
||||||
|
type ScannedResourceEdge {
|
||||||
|
"""
|
||||||
|
A cursor for use in pagination.
|
||||||
|
"""
|
||||||
|
cursor: String!
|
||||||
|
|
||||||
|
"""
|
||||||
|
The item at the end of the edge.
|
||||||
|
"""
|
||||||
|
node: ScannedResource
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Represents summary of a security report
|
Represents summary of a security report
|
||||||
"""
|
"""
|
||||||
|
|
@ -11349,6 +11399,31 @@ type SecurityReportSummary {
|
||||||
Represents a section of a summary of a security report
|
Represents a section of a summary of a security report
|
||||||
"""
|
"""
|
||||||
type SecurityReportSummarySection {
|
type SecurityReportSummarySection {
|
||||||
|
"""
|
||||||
|
A list of the first 20 scanned resources
|
||||||
|
"""
|
||||||
|
scannedResources(
|
||||||
|
"""
|
||||||
|
Returns the elements in the list that come after the specified cursor.
|
||||||
|
"""
|
||||||
|
after: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the elements in the list that come before the specified cursor.
|
||||||
|
"""
|
||||||
|
before: String
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the first _n_ elements from the list.
|
||||||
|
"""
|
||||||
|
first: Int
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the last _n_ elements from the list.
|
||||||
|
"""
|
||||||
|
last: Int
|
||||||
|
): ScannedResourceConnection
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Total number of scanned resources
|
Total number of scanned resources
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -33217,6 +33217,159 @@
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResource",
|
||||||
|
"description": "Represents a resource scanned by a security scan",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "requestMethod",
|
||||||
|
"description": "The HTTP request method used to access the URL",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "url",
|
||||||
|
"description": "The URL scanned by the scanner",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResourceConnection",
|
||||||
|
"description": "The connection type for ScannedResource.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "edges",
|
||||||
|
"description": "A list of edges.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "LIST",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResourceEdge",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nodes",
|
||||||
|
"description": "A list of nodes.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "LIST",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResource",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageInfo",
|
||||||
|
"description": "Information to aid in pagination.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "PageInfo",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResourceEdge",
|
||||||
|
"description": "An edge in a connection.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "cursor",
|
||||||
|
"description": "A cursor for use in pagination.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "node",
|
||||||
|
"description": "The item at the end of the edge.",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResource",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"enumValues": null,
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": "OBJECT",
|
"kind": "OBJECT",
|
||||||
"name": "SecurityReportSummary",
|
"name": "SecurityReportSummary",
|
||||||
|
|
@ -33319,6 +33472,59 @@
|
||||||
"name": "SecurityReportSummarySection",
|
"name": "SecurityReportSummarySection",
|
||||||
"description": "Represents a section of a summary of a security report",
|
"description": "Represents a section of a summary of a security report",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "scannedResources",
|
||||||
|
"description": "A list of the first 20 scanned resources",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "first",
|
||||||
|
"description": "Returns the first _n_ elements from the list.",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last",
|
||||||
|
"description": "Returns the last _n_ elements from the list.",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"defaultValue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "ScannedResourceConnection",
|
||||||
|
"ofType": null
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "scannedResourcesCount",
|
"name": "scannedResourcesCount",
|
||||||
"description": "Total number of scanned resources",
|
"description": "Total number of scanned resources",
|
||||||
|
|
|
||||||
|
|
@ -1637,6 +1637,15 @@ Autogenerated return type of RunDASTScan
|
||||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||||
| `pipelineUrl` | String | URL of the pipeline that was created. |
|
| `pipelineUrl` | String | URL of the pipeline that was created. |
|
||||||
|
|
||||||
|
## ScannedResource
|
||||||
|
|
||||||
|
Represents a resource scanned by a security scan
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | ---- | ---------- |
|
||||||
|
| `requestMethod` | String | The HTTP request method used to access the URL |
|
||||||
|
| `url` | String | The URL scanned by the scanner |
|
||||||
|
|
||||||
## SecurityReportSummary
|
## SecurityReportSummary
|
||||||
|
|
||||||
Represents summary of a security report
|
Represents summary of a security report
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter'
|
redirect_to: '../user/project/description_templates.md'
|
||||||
---
|
---
|
||||||
|
|
||||||
This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter).
|
This document was moved to [description_templates](../user/project/description_templates.md).
|
||||||
|
|
|
||||||
|
|
@ -666,8 +666,9 @@ appear to be associated to any of the services running, since they all appear to
|
||||||
| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | | | Unique clusters with Runner enabled |
|
| `clusters_applications_runner` | `usage_activity_by_stage` | `verify` | | | Unique clusters with Runner enabled |
|
||||||
| `projects_reporting_ci_cd_back_to_github: 0` | `usage_activity_by_stage` | `verify` | | | Unique projects with a GitHub pipeline enabled |
|
| `projects_reporting_ci_cd_back_to_github: 0` | `usage_activity_by_stage` | `verify` | | | Unique projects with a GitHub pipeline enabled |
|
||||||
| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | | Unique count of users who used a merge request |
|
| `merge_requests_users` | `usage_activity_by_stage_monthly` | `create` | | | Unique count of users who used a merge request |
|
||||||
| `nodes` | `topology` | `enablement` | | | The list of server nodes on which GitLab components are running |
|
|
||||||
| `duration_s` | `topology` | `enablement` | | | Time it took to collect topology data |
|
| `duration_s` | `topology` | `enablement` | | | Time it took to collect topology data |
|
||||||
|
| `application_requests_per_hour` | `topology` | `enablement` | | | Number of requests to the web application per hour |
|
||||||
|
| `nodes` | `topology` | `enablement` | | | The list of server nodes on which GitLab components are running |
|
||||||
| `node_memory_total_bytes` | `topology > nodes` | `enablement` | | | The total available memory of this node |
|
| `node_memory_total_bytes` | `topology > nodes` | `enablement` | | | The total available memory of this node |
|
||||||
| `node_cpus` | `topology > nodes` | `enablement` | | | The number of CPU cores of this node |
|
| `node_cpus` | `topology > nodes` | `enablement` | | | The number of CPU cores of this node |
|
||||||
| `node_services` | `topology > nodes` | `enablement` | | | The list of GitLab services running on this node |
|
| `node_services` | `topology > nodes` | `enablement` | | | The list of GitLab services running on this node |
|
||||||
|
|
@ -873,6 +874,8 @@ The following is example content of the Usage Ping payload.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"topology": {
|
"topology": {
|
||||||
|
"duration_s": 0.013836685999194742,
|
||||||
|
"application_requests_per_hour": 4224,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"node_memory_total_bytes": 33269903360,
|
"node_memory_total_bytes": 33269903360,
|
||||||
|
|
@ -897,8 +900,7 @@ The following is example content of the Usage Ping payload.
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
],
|
]
|
||||||
"duration_s": 0.013836685999194742
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ module Gitlab
|
||||||
|
|
||||||
MASS_INSERT_PROJECT_START = 'mass_insert_project_'
|
MASS_INSERT_PROJECT_START = 'mass_insert_project_'
|
||||||
MASS_INSERT_USER_START = 'mass_insert_user_'
|
MASS_INSERT_USER_START = 'mass_insert_user_'
|
||||||
|
REPORTED_USER_START = 'reported_user_'
|
||||||
ESTIMATED_INSERT_PER_MINUTE = 2_000_000
|
ESTIMATED_INSERT_PER_MINUTE = 2_000_000
|
||||||
MASS_INSERT_ENV = 'MASS_INSERT'
|
MASS_INSERT_ENV = 'MASS_INSERT'
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ module Gitlab
|
||||||
|
|
||||||
included do
|
included do
|
||||||
scope :not_mass_generated, -> do
|
scope :not_mass_generated, -> do
|
||||||
where.not("username LIKE '#{MASS_INSERT_USER_START}%'")
|
where.not("username LIKE '#{MASS_INSERT_USER_START}%' OR username LIKE '#{REPORTED_USER_START}%'")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,20 @@ module Gitlab
|
||||||
def topology_fetch_all_data
|
def topology_fetch_all_data
|
||||||
with_prometheus_client(fallback: {}) do |client|
|
with_prometheus_client(fallback: {}) do |client|
|
||||||
{
|
{
|
||||||
|
application_requests_per_hour: topology_app_requests_per_hour(client),
|
||||||
nodes: topology_node_data(client)
|
nodes: topology_node_data(client)
|
||||||
}
|
}.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def topology_app_requests_per_hour(client)
|
||||||
|
result = client.query(one_week_average('gitlab_usage_ping:ops:rate5m')).first
|
||||||
|
return unless result
|
||||||
|
|
||||||
|
# the metric is recorded as a per-second rate
|
||||||
|
(result['value'].last.to_f * 1.hour).to_i
|
||||||
|
end
|
||||||
|
|
||||||
def topology_node_data(client)
|
def topology_node_data(client)
|
||||||
# node-level data
|
# node-level data
|
||||||
by_instance_mem = topology_node_memory(client)
|
by_instance_mem = topology_node_memory(client)
|
||||||
|
|
|
||||||
|
|
@ -10964,6 +10964,9 @@ msgstr ""
|
||||||
msgid "Go to find file"
|
msgid "Go to find file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Go to fork"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Go to issue boards"
|
msgid "Go to issue boards"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -11531,6 +11534,9 @@ msgstr ""
|
||||||
msgid "Groups and projects"
|
msgid "Groups and projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Groups and subgroups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
|
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -15131,6 +15137,9 @@ msgstr ""
|
||||||
msgid "No authentication methods configured."
|
msgid "No authentication methods configured."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No available groups to fork the project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "No available namespaces to fork the project."
|
msgid "No available namespaces to fork the project."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -19831,6 +19840,9 @@ msgstr ""
|
||||||
msgid "Search by author"
|
msgid "Search by author"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Search by name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Search files"
|
msgid "Search files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -22979,6 +22991,9 @@ msgstr ""
|
||||||
msgid "There was a problem communicating with your device."
|
msgid "There was a problem communicating with your device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "There was a problem fetching groups."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "There was a problem fetching project branches."
|
msgid "There was a problem fetching project branches."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -26256,6 +26271,9 @@ msgstr ""
|
||||||
msgid "You must have maintainer access to force delete a lock"
|
msgid "You must have maintainer access to force delete a lock"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "You must have permission to create a project in a group before forking."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "You must have permission to create a project in a namespace before forking."
|
msgid "You must have permission to create a project in a namespace before forking."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
"@babel/plugin-syntax-import-meta": "^7.10.1",
|
"@babel/plugin-syntax-import-meta": "^7.10.1",
|
||||||
"@babel/preset-env": "^7.10.1",
|
"@babel/preset-env": "^7.10.1",
|
||||||
"@gitlab/at.js": "1.5.5",
|
"@gitlab/at.js": "1.5.5",
|
||||||
"@gitlab/svgs": "1.146.0",
|
"@gitlab/svgs": "1.147.0",
|
||||||
"@gitlab/ui": "17.10.1",
|
"@gitlab/ui": "17.10.1",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
"@rails/actioncable": "^6.0.3-1",
|
"@rails/actioncable": "^6.0.3-1",
|
||||||
|
|
|
||||||
|
|
@ -83,13 +83,13 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_get_from(get_path)
|
def api_get_from(get_path)
|
||||||
url = Runtime::API::Request.new(api_client, get_path).url
|
request = Runtime::API::Request.new(api_client, get_path)
|
||||||
response = get(url)
|
response = get(request.url)
|
||||||
|
|
||||||
if response.code == HTTP_STATUS_SERVER_ERROR
|
if response.code == HTTP_STATUS_SERVER_ERROR
|
||||||
raise InternalServerError, "Failed to GET #{url} - (#{response.code}): `#{response}`."
|
raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`."
|
||||||
elsif response.code != HTTP_STATUS_OK
|
elsif response.code != HTTP_STATUS_OK
|
||||||
raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`."
|
raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`."
|
||||||
end
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
@ -108,11 +108,11 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_delete
|
def api_delete
|
||||||
url = Runtime::API::Request.new(api_client, api_delete_path).url
|
request = Runtime::API::Request.new(api_client, api_delete_path)
|
||||||
response = delete(url)
|
response = delete(request.url)
|
||||||
|
|
||||||
unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code
|
unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code
|
||||||
raise ResourceNotDeletedError, "Resource at #{url} could not be deleted (#{response.code}): `#{response}`."
|
raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`."
|
||||||
end
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
context 'Plan', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225409', type: :bug } do
|
context 'Plan' do
|
||||||
describe 'Jira issue import', :jira, :orchestrated, :requires_admin do
|
describe 'Jira issue import', :jira, :orchestrated, :requires_admin do
|
||||||
let(:jira_project_key) { "JITD" }
|
let(:jira_project_key) { "JITD" }
|
||||||
let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" }
|
let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" }
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@ describe QA::Runtime::API::Request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#mask_url' do
|
||||||
|
it 'returns the full API request url with the token masked' do
|
||||||
|
expect(request.mask_url).to eq 'http://example.com/api/v4/users?private_token=[****]'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#request_path' do
|
describe '#request_path' do
|
||||||
it 'prepends the api path' do
|
it 'prepends the api path' do
|
||||||
expect(request.request_path('/users')).to eq '/api/v4/users'
|
expect(request.request_path('/users')).to eq '/api/v4/users'
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ module RuboCop
|
||||||
MSG = 'Add an `authorize :ability` call to the type: '\
|
MSG = 'Add an `authorize :ability` call to the type: '\
|
||||||
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
|
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
|
||||||
|
|
||||||
TYPES_DIR = 'app/graphql/types'
|
|
||||||
|
|
||||||
# We want to exclude our own basetypes and scalars
|
# We want to exclude our own basetypes and scalars
|
||||||
WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
|
WHITELISTED_TYPES = %w[BaseEnum BaseScalar BasePermissionType MutationType
|
||||||
QueryType GraphQL::Schema BaseUnion].freeze
|
QueryType GraphQL::Schema BaseUnion].freeze
|
||||||
|
|
@ -18,7 +16,6 @@ module RuboCop
|
||||||
PATTERN
|
PATTERN
|
||||||
|
|
||||||
def on_class(node)
|
def on_class(node)
|
||||||
return unless in_type?(node)
|
|
||||||
return if whitelisted?(class_constant(node))
|
return if whitelisted?(class_constant(node))
|
||||||
return if whitelisted?(superclass_constant(node))
|
return if whitelisted?(superclass_constant(node))
|
||||||
|
|
||||||
|
|
@ -27,12 +24,6 @@ module RuboCop
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def in_type?(node)
|
|
||||||
path = node.location.expression.source_buffer.name
|
|
||||||
|
|
||||||
path.include? TYPES_DIR
|
|
||||||
end
|
|
||||||
|
|
||||||
def whitelisted?(class_node)
|
def whitelisted?(class_node)
|
||||||
class_const = class_node&.const_name
|
class_const = class_node&.const_name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue';
|
import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue';
|
||||||
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
|
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
|
||||||
|
|
||||||
describe('Job Log Collapsible Section', () => {
|
describe('Job Log Collapsible Section', () => {
|
||||||
|
|
@ -11,7 +11,7 @@ describe('Job Log Collapsible Section', () => {
|
||||||
const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
|
const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
|
||||||
|
|
||||||
const createComponent = (props = {}) => {
|
const createComponent = (props = {}) => {
|
||||||
wrapper = mount(CollpasibleSection, {
|
wrapper = mount(CollapsibleSection, {
|
||||||
propsData: {
|
propsData: {
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ describe('Jobs Store Utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('collpasible section', () => {
|
describe('collapsible section', () => {
|
||||||
it('adds a `isClosed` property', () => {
|
it('adds a `isClosed` property', () => {
|
||||||
expect(result[1].isClosed).toEqual(false);
|
expect(result[1].isClosed).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
@ -190,7 +190,7 @@ describe('Jobs Store Utils', () => {
|
||||||
expect(result[1].isHeader).toEqual(true);
|
expect(result[1].isHeader).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a lines array property with the content of the collpasible section', () => {
|
it('creates a lines array property with the content of the collapsible section', () => {
|
||||||
expect(result[1].lines.length).toEqual(2);
|
expect(result[1].lines.length).toEqual(2);
|
||||||
expect(result[1].lines[0].content).toEqual(utilsMockData[2].content);
|
expect(result[1].lines[0].content).toEqual(utilsMockData[2].content);
|
||||||
expect(result[1].lines[1].content).toEqual(utilsMockData[3].content);
|
expect(result[1].lines[1].content).toEqual(utilsMockData[3].content);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { GlBadge, GlButton, GlLink } from '@gitlab/ui';
|
||||||
|
import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
|
||||||
|
|
||||||
|
describe('Fork groups list item component', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const DEFAULT_PROPS = {
|
||||||
|
hasReachedProjectLimit: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_GROUP_DATA = {
|
||||||
|
id: 22,
|
||||||
|
name: 'Gitlab Org',
|
||||||
|
description: 'Ad et ipsam earum id aut nobis.',
|
||||||
|
visibility: 'public',
|
||||||
|
full_name: 'Gitlab Org',
|
||||||
|
created_at: '2020-06-22T03:32:05.664Z',
|
||||||
|
updated_at: '2020-06-22T03:32:05.664Z',
|
||||||
|
avatar_url: null,
|
||||||
|
fork_path: '/twitter/typeahead-js/-/forks?namespace_key=22',
|
||||||
|
forked_project_path: null,
|
||||||
|
permission: 'Owner',
|
||||||
|
relative_path: '/gitlab-org',
|
||||||
|
markdown_description:
|
||||||
|
'<p data-sourcepos="1:1-1:31" dir="auto">Ad et ipsam earum id aut nobis.</p>',
|
||||||
|
can_create_project: true,
|
||||||
|
marked_for_deletion: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DUMMY_PATH = '/dummy/path';
|
||||||
|
|
||||||
|
const createWrapper = propsData => {
|
||||||
|
wrapper = shallowMount(ForkGroupsListItem, {
|
||||||
|
propsData: {
|
||||||
|
...DEFAULT_PROPS,
|
||||||
|
...propsData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
it('renders pending removal badge if applicable', () => {
|
||||||
|
createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } });
|
||||||
|
|
||||||
|
expect(wrapper.find(GlBadge).text()).toBe('pending removal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders go to fork button if has forked project', () => {
|
||||||
|
createWrapper({ group: { ...DEFAULT_GROUP_DATA, forked_project_path: DUMMY_PATH } });
|
||||||
|
|
||||||
|
expect(wrapper.find(GlButton).text()).toBe('Go to fork');
|
||||||
|
expect(wrapper.find(GlButton).attributes().href).toBe(DUMMY_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders select button if has no forked project', () => {
|
||||||
|
createWrapper({
|
||||||
|
group: { ...DEFAULT_GROUP_DATA, forked_project_path: null, fork_path: DUMMY_PATH },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find(GlButton).text()).toBe('Select');
|
||||||
|
expect(wrapper.find('form').attributes().action).toBe(DUMMY_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders link to current group', () => {
|
||||||
|
const DUMMY_FULL_NAME = 'dummy';
|
||||||
|
createWrapper({
|
||||||
|
group: { ...DEFAULT_GROUP_DATA, relative_path: DUMMY_PATH, full_name: DUMMY_FULL_NAME },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll(GlLink)
|
||||||
|
.filter(w => w.text() === DUMMY_FULL_NAME)
|
||||||
|
.at(0)
|
||||||
|
.attributes().href,
|
||||||
|
).toBe(DUMMY_PATH);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
|
||||||
|
import { nextTick } from 'vue';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue';
|
||||||
|
import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue';
|
||||||
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
|
||||||
|
jest.mock('~/flash', () => jest.fn());
|
||||||
|
|
||||||
|
describe('Fork groups list component', () => {
|
||||||
|
let wrapper;
|
||||||
|
let axiosMock;
|
||||||
|
|
||||||
|
const DEFAULT_PROPS = {
|
||||||
|
endpoint: '/dummy',
|
||||||
|
hasReachedProjectLimit: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args);
|
||||||
|
|
||||||
|
const createWrapper = propsData => {
|
||||||
|
wrapper = shallowMount(ForkGroupsList, {
|
||||||
|
propsData: {
|
||||||
|
...DEFAULT_PROPS,
|
||||||
|
...propsData,
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
GlTabs: {
|
||||||
|
template: '<div><slot></slot><slot name="tabs-end"></slot></div>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
axiosMock = new AxiosMockAdapter(axios);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
axiosMock.reset();
|
||||||
|
|
||||||
|
if (wrapper) {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fires load groups request on mount', async () => {
|
||||||
|
replyWith(200, { namespaces: [] });
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROPS.endpoint);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays flash if loading groups fails', async () => {
|
||||||
|
replyWith(500);
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(createFlash).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays loading indicator while loading groups', () => {
|
||||||
|
replyWith(() => new Promise(() => {}));
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays empty text if no groups are available', async () => {
|
||||||
|
const EMPTY_TEXT = 'No available groups to fork the project.';
|
||||||
|
replyWith(200, { namespaces: [] });
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain(EMPTY_TEXT);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays filter field when groups are available', async () => {
|
||||||
|
replyWith(200, { namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }] });
|
||||||
|
createWrapper();
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(wrapper.contains(GlSearchBoxByType)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders list items for each available group', async () => {
|
||||||
|
const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }];
|
||||||
|
const hasReachedProjectLimit = true;
|
||||||
|
|
||||||
|
replyWith(200, { namespaces });
|
||||||
|
createWrapper({ hasReachedProjectLimit });
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(namespaces.length);
|
||||||
|
|
||||||
|
namespaces.forEach((namespace, idx) => {
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll(ForkGroupsListItem)
|
||||||
|
.at(idx)
|
||||||
|
.props(),
|
||||||
|
).toStrictEqual({ group: namespace, hasReachedProjectLimit });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters repositories on the fly', async () => {
|
||||||
|
replyWith(200, {
|
||||||
|
namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }],
|
||||||
|
});
|
||||||
|
createWrapper();
|
||||||
|
await waitForPromises();
|
||||||
|
wrapper.find(GlSearchBoxByType).vm.$emit('input', 'other');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(1);
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll(ForkGroupsListItem)
|
||||||
|
.at(0)
|
||||||
|
.props().group.name,
|
||||||
|
).toBe('otherdummy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -22,6 +22,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do
|
||||||
context 'tracking node metrics' do
|
context 'tracking node metrics' do
|
||||||
it 'contains node level metrics for each instance' do
|
it 'contains node level metrics for each instance' do
|
||||||
expect_prometheus_api_to(
|
expect_prometheus_api_to(
|
||||||
|
receive_app_request_volume_query,
|
||||||
receive_node_memory_query,
|
receive_node_memory_query,
|
||||||
receive_node_cpu_count_query,
|
receive_node_cpu_count_query,
|
||||||
receive_node_service_memory_rss_query,
|
receive_node_service_memory_rss_query,
|
||||||
|
|
@ -32,6 +33,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do
|
||||||
|
|
||||||
expect(subject[:topology]).to eq({
|
expect(subject[:topology]).to eq({
|
||||||
duration_s: 0,
|
duration_s: 0,
|
||||||
|
application_requests_per_hour: 36,
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
node_memory_total_bytes: 512,
|
node_memory_total_bytes: 512,
|
||||||
|
|
@ -76,6 +78,7 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do
|
||||||
context 'and some node memory metrics are missing' do
|
context 'and some node memory metrics are missing' do
|
||||||
it 'removes the respective entries' do
|
it 'removes the respective entries' do
|
||||||
expect_prometheus_api_to(
|
expect_prometheus_api_to(
|
||||||
|
receive_app_request_volume_query(result: []),
|
||||||
receive_node_memory_query(result: []),
|
receive_node_memory_query(result: []),
|
||||||
receive_node_cpu_count_query,
|
receive_node_cpu_count_query,
|
||||||
receive_node_service_memory_rss_query(result: []),
|
receive_node_service_memory_rss_query(result: []),
|
||||||
|
|
@ -149,6 +152,17 @@ RSpec.describe Gitlab::UsageDataConcerns::Topology do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def receive_app_request_volume_query(result: nil)
|
||||||
|
receive(:query)
|
||||||
|
.with(/gitlab_usage_ping:ops:rate/)
|
||||||
|
.and_return(result || [
|
||||||
|
{
|
||||||
|
'metric' => { 'component' => 'http_requests', 'service' => 'workhorse' },
|
||||||
|
'value' => [1000, '0.01']
|
||||||
|
}
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
def receive_node_memory_query(result: nil)
|
def receive_node_memory_query(result: nil)
|
||||||
receive(:query)
|
receive(:query)
|
||||||
.with(/node_memory_total_bytes/, an_instance_of(Hash))
|
.with(/node_memory_total_bytes/, an_instance_of(Hash))
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,26 @@ RSpec.describe ProjectStatistics do
|
||||||
statistics.refresh!
|
statistics.refresh!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when snippets_size is updated' do
|
||||||
|
it 'schedules the aggregation worker' do
|
||||||
|
expect(Namespaces::ScheduleAggregationWorker)
|
||||||
|
.to receive(:perform_async)
|
||||||
|
|
||||||
|
statistics.refresh!(only: [:snippets_size])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when feature flag :namespace_snippets_size_stat is disabled' do
|
||||||
|
it 'does not schedules an aggregation worker' do
|
||||||
|
stub_feature_flags(namespace_snippets_size_stat: false)
|
||||||
|
|
||||||
|
expect(Namespaces::ScheduleAggregationWorker)
|
||||||
|
.not_to receive(:perform_async)
|
||||||
|
|
||||||
|
statistics.refresh!(only: [:snippets_size])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the column is not namespace relatable' do
|
context 'when the column is not namespace relatable' do
|
||||||
|
|
|
||||||
|
|
@ -4766,6 +4766,12 @@ RSpec.describe User do
|
||||||
end.to change { User.where(user_type: bot_type).count }.by(1)
|
end.to change { User.where(user_type: bot_type).count }.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates a route for the namespace of the created user' do
|
||||||
|
bot_user = described_class.public_send(bot_type)
|
||||||
|
|
||||||
|
expect(bot_user.namespace.route).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not create a new user if it already exists' do
|
it 'does not create a new user if it already exists' do
|
||||||
described_class.public_send(bot_type)
|
described_class.public_send(bot_type)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,28 +10,6 @@ RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do
|
||||||
|
|
||||||
subject(:cop) { described_class.new }
|
subject(:cop) { described_class.new }
|
||||||
|
|
||||||
context 'when NOT in a type folder' do
|
|
||||||
before do
|
|
||||||
allow(cop).to receive(:in_type?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not add an offense even though there is no authorize call' do
|
|
||||||
expect_no_offenses(<<~TYPE.strip)
|
|
||||||
module Types
|
|
||||||
class AType < BaseObject
|
|
||||||
field :a_thing
|
|
||||||
field :another_thing
|
|
||||||
end
|
|
||||||
end
|
|
||||||
TYPE
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when in a type folder' do
|
|
||||||
before do
|
|
||||||
allow(cop).to receive(:in_type?).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'adds an offense when there is no authorize call' do
|
it 'adds an offense when there is no authorize call' do
|
||||||
inspect_source(<<~TYPE)
|
inspect_source(<<~TYPE)
|
||||||
module Types
|
module Types
|
||||||
|
|
@ -88,5 +66,4 @@ RSpec.describe RuboCop::Cop::Graphql::AuthorizeTypes, type: :rubocop do
|
||||||
end
|
end
|
||||||
TYPE
|
TYPE
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -843,10 +843,10 @@
|
||||||
eslint-plugin-vue "^6.2.1"
|
eslint-plugin-vue "^6.2.1"
|
||||||
vue-eslint-parser "^7.0.0"
|
vue-eslint-parser "^7.0.0"
|
||||||
|
|
||||||
"@gitlab/svgs@1.146.0":
|
"@gitlab/svgs@1.147.0":
|
||||||
version "1.146.0"
|
version "1.147.0"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.146.0.tgz#c74118a3f1ab47ae77211d42597f553f395deb5d"
|
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.147.0.tgz#1b2cc986cb3219609136cab641e2c384d724700f"
|
||||||
integrity sha512-2/k9pAZPgHpZ5Ad0fz9i1109sWcShDE4XcjrjzltNNksbi86lqCKbsSe580ujtlG8KShgGMkDkmUa6AHZi64Xw==
|
integrity sha512-KnjN7ms7bEPajYl7q0nKv7HMKtqR/JxCVSBRGXH5ezkeGKy4wb4yEYtvRK8no7ix+Iw4rc0KTqOwKp9nkl/KdA==
|
||||||
|
|
||||||
"@gitlab/ui@17.10.1":
|
"@gitlab/ui@17.10.1":
|
||||||
version "17.10.1"
|
version "17.10.1"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue