diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-23 18:07:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-23 18:07:42 +0300 |
commit | bc0f141f2f073a971aad1eb5349bb718747df028 (patch) | |
tree | 72fcc48dfac8e3f3560e22014eacdd2eaae8bc89 /app | |
parent | 2c29837ce1692790dedccbc9b36b44dc8aaacbee (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
34 files changed, 341 insertions, 223 deletions
diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue index 411e482b0ce..c6aeb6c726d 100644 --- a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue +++ b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue @@ -185,7 +185,6 @@ export default { name="prometheus_metric[title]" class="form-control" :placeholder="s__('Metrics|e.g. Throughput')" - data-qa-selector="custom_metric_prometheus_title_field" required /> <span class="form-text text-muted">{{ s__('Metrics|Used as a title for the chart') }}</span> @@ -209,7 +208,6 @@ export default { <gl-form-input id="prometheus_metric_query" v-model.trim="query" - data-qa-selector="custom_metric_prometheus_query_field" name="prometheus_metric[query]" class="form-control" :placeholder="s__('Metrics|e.g. rate(http_requests_total[5m])')" @@ -247,7 +245,6 @@ export default { <gl-form-input id="prometheus_metric_y_label" v-model="yLabel" - data-qa-selector="custom_metric_prometheus_y_label_field" name="prometheus_metric[y_label]" class="form-control" :placeholder="s__('Metrics|e.g. Requests/second')" @@ -267,7 +264,6 @@ export default { <gl-form-input id="prometheus_metric_unit" v-model="unit" - data-qa-selector="custom_metric_prometheus_unit_label_field" name="prometheus_metric[unit]" class="form-control" :placeholder="s__('Metrics|e.g. req/sec')" @@ -282,7 +278,6 @@ export default { <gl-form-input id="prometheus_metric_legend" v-model="legend" - data-qa-selector="custom_metric_prometheus_legend_label_field" name="prometheus_metric[legend]" class="form-control" :placeholder="s__('Metrics|e.g. HTTP requests')" diff --git a/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue index 81d74c64124..48ab9ce0a3c 100644 --- a/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue +++ b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue @@ -13,27 +13,7 @@ import { createAlert, VARIANT_INFO } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { formatDate } from '~/lib/utils/datetime_utility'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import { s__ } from '~/locale'; - -function defaultData() { - return { - expiresAt: null, - name: '', - newTokenDetails: null, - readRepository: false, - writeRepository: false, - readRegistry: false, - writeRegistry: false, - readPackageRegistry: false, - writePackageRegistry: false, - username: '', - placeholders: { - link: { link: ['link_start', 'link_end'] }, - i: { i: ['i_start', 'i_end'] }, - code: { code: ['code_start', 'code_end'] }, - }, - }; -} +import translations from '../deploy_token_translations'; export default { components: { @@ -72,45 +52,9 @@ export default { }, data() { - return defaultData(); - }, - translations: { - addTokenButton: s__('DeployTokens|Create deploy token'), - addTokenExpiryLabel: s__('DeployTokens|Expiration date (optional)'), - addTokenExpiryDescription: s__( - 'DeployTokens|Enter an expiration date for your token. Defaults to never expire.', - ), - addTokenHeader: s__('DeployTokens|New deploy token'), - addTokenDescription: s__( - 'DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}', - ), - addTokenNameLabel: s__('DeployTokens|Name'), - addTokenNameDescription: s__('DeployTokens|Enter a unique name for your deploy token.'), - addTokenScopesLabel: s__('DeployTokens|Scopes (select at least one)'), - addTokenUsernameDescription: s__( - 'DeployTokens|Enter a username for your token. Defaults to %{code_start}gitlab+deploy-token-{n}%{code_end}.', - ), - addTokenUsernameLabel: s__('DeployTokens|Username (optional)'), - newTokenCopyMessage: s__('DeployTokens|Copy deploy token'), - newProjectTokenCreated: s__('DeployTokens|Your new project deploy token has been created.'), - newGroupTokenCreated: s__('DeployTokens|Your new group deploy token has been created.'), - newTokenDescription: s__( - 'DeployTokens|Use this token as a password. Save it. This password can %{i_start}not%{i_end} be recovered.', - ), - newTokenMessage: s__('DeployTokens|Your New Deploy Token'), - newTokenUsernameCopy: s__('DeployTokens|Copy username'), - newTokenUsernameDescription: s__( - 'DeployTokens|This username supports access. %{link_start}What kind of access?%{link_end}', - ), - readRepositoryHelp: s__('DeployTokens|Allows read-only access to the repository.'), - readRegistryHelp: s__('DeployTokens|Allows read-only access to registry images.'), - writeRegistryHelp: s__('DeployTokens|Allows read and write access to registry images.'), - readPackageRegistryHelp: s__('DeployTokens|Allows read-only access to the package registry.'), - writePackageRegistryHelp: s__( - 'DeployTokens|Allows read and write access to the package registry.', - ), - createTokenFailedAlert: s__('DeployTokens|Failed to create a new deployment token'), + return this.defaultData(); }, + translations, computed: { formattedExpiryDate() { return this.expiresAt ? formatDate(this.expiresAt, 'yyyy-mm-dd') : ''; @@ -122,20 +66,78 @@ export default { }, }, methods: { + defaultData() { + return { + expiresAt: null, + name: '', + newTokenDetails: null, + readRepository: false, + writeRepository: false, + readRegistry: false, + writeRegistry: false, + readPackageRegistry: false, + writePackageRegistry: false, + scopes: [ + { + id: 'deploy_token_read_repository', + isShown: true, + value: false, + helpText: this.$options.translations.readRepositoryHelp, + scopeName: 'read_repository', + }, + { + id: 'deploy_token_read_registry', + isShown: this.$props.containerRegistryEnabled, + value: false, + helpText: this.$options.translations.readRegistryHelp, + scopeName: 'read_registry', + }, + { + id: 'deploy_token_write_registry', + isShown: this.$props.containerRegistryEnabled, + value: false, + helpText: this.$options.translations.writeRegistryHelp, + scopeName: 'write_registry', + }, + { + id: 'deploy_token_read_package_registry', + isShown: this.$props.packagesRegistryEnabled, + value: false, + helpText: this.$options.translations.readPackageRegistryHelp, + scopeName: 'read_package_registry', + }, + { + id: 'deploy_token_write_package_registry', + isShown: this.$props.packagesRegistryEnabled, + value: false, + helpText: this.$options.translations.writePackageRegistryHelp, + scopeName: 'write_package_registry', + }, + ], + username: '', + placeholders: { + link: { link: ['link_start', 'link_end'] }, + i: { i: ['i_start', 'i_end'] }, + code: { code: ['code_start', 'code_end'] }, + }, + }; + }, createDeployToken() { + const scopes = {}; + this.scopes.forEach((scope) => { + scopes[scope.scopeName] = scope.value; + }); + const body = { + deploy_token: { + expires_at: this.expiresAt, + name: this.name, + username: this.username, + ...scopes, + }, + }; + return axios - .post(this.createNewTokenPath, { - deploy_token: { - expires_at: this.expiresAt, - name: this.name, - read_repository: this.readRepository, - read_registry: this.readRegistry, - write_registry: this.writeRegistry, - read_package_registry: this.readPackageRegistry, - write_package_registry: this.writePackageRegistry, - username: this.username, - }, - }) + .post(this.createNewTokenPath, body) .then((response) => { this.newTokenDetails = response.data; this.resetData(); @@ -152,7 +154,7 @@ export default { }); }, resetData() { - const newData = defaultData(); + const newData = this.defaultData(); delete newData.newTokenDetails; Object.keys(newData).forEach((k) => { this[k] = newData[k]; @@ -269,55 +271,19 @@ export default { > <div id="deploy-token-scopes"> <!-- eslint-disable @gitlab/vue-require-i18n-strings --> - <gl-form-checkbox - id="deploy_token_read_repository" - v-model="readRepository" - name="deploy_token_read_repository" - data-qa-selector="deploy_token_read_repository_checkbox" - > - read_repository - <template #help>{{ $options.translations.readRepositoryHelp }}</template> - </gl-form-checkbox> - <gl-form-checkbox - v-if="containerRegistryEnabled" - id="deploy_token_read_registry" - v-model="readRegistry" - name="deploy_token_read_registry" - data-qa-selector="deploy_token_read_registry_checkbox" - > - read_registry - <template #help>{{ $options.translations.readRegistryHelp }}</template> - </gl-form-checkbox> - <gl-form-checkbox - v-if="containerRegistryEnabled" - id="deploy_token_write_registry" - v-model="writeRegistry" - name="deploy_token_write_registry" - data-qa-selector="deploy_token_write_registry_checkbox" - > - write_registry - <template #help>{{ $options.translations.writeRegistryHelp }}</template> - </gl-form-checkbox> - <gl-form-checkbox - v-if="packagesRegistryEnabled" - id="deploy_token_read_package_registry" - v-model="readPackageRegistry" - name="deploy_token_read_package_registry" - data-qa-selector="deploy_token_read_package_registry_checkbox" - > - read_package_registry - <template #help>{{ $options.translations.readPackageRegistryHelp }}</template> - </gl-form-checkbox> - <gl-form-checkbox - v-if="packagesRegistryEnabled" - id="deploy_token_write_package_registry" - v-model="writePackageRegistry" - name="deploy_token_write_package_registry" - data-qa-selector="deploy_token_write_package_registry_checkbox" - > - write_package_registry - <template #help>{{ $options.translations.writePackageRegistryHelp }}</template> - </gl-form-checkbox> + <template v-for="scope in scopes"> + <gl-form-checkbox + v-if="scope.isShown" + :id="scope.id" + :key="scope.id" + v-model="scope.value" + :name="scope.id" + :data-qa-selector="`${scope.id}_checkbox`" + > + {{ scope.scopeName }} + <template #help>{{ scope.helpText }}</template> + </gl-form-checkbox> + </template> <!-- eslint-enable @gitlab/vue-require-i18n-strings --> </div> </gl-form-group> diff --git a/app/assets/javascripts/deploy_tokens/deploy_token_translations.js b/app/assets/javascripts/deploy_tokens/deploy_token_translations.js new file mode 100644 index 00000000000..3767e9e6170 --- /dev/null +++ b/app/assets/javascripts/deploy_tokens/deploy_token_translations.js @@ -0,0 +1,41 @@ +import { s__ } from '~/locale'; + +const translations = { + addTokenButton: s__('DeployTokens|Create deploy token'), + addTokenExpiryLabel: s__('DeployTokens|Expiration date (optional)'), + addTokenExpiryDescription: s__( + 'DeployTokens|Enter an expiration date for your token. Defaults to never expire.', + ), + addTokenHeader: s__('DeployTokens|New deploy token'), + addTokenDescription: s__( + 'DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}', + ), + addTokenNameLabel: s__('DeployTokens|Name'), + addTokenNameDescription: s__('DeployTokens|Enter a unique name for your deploy token.'), + addTokenScopesLabel: s__('DeployTokens|Scopes (select at least one)'), + addTokenUsernameDescription: s__( + 'DeployTokens|Enter a username for your token. Defaults to %{code_start}gitlab+deploy-token-{n}%{code_end}.', + ), + addTokenUsernameLabel: s__('DeployTokens|Username (optional)'), + newTokenCopyMessage: s__('DeployTokens|Copy deploy token'), + newProjectTokenCreated: s__('DeployTokens|Your new project deploy token has been created.'), + newGroupTokenCreated: s__('DeployTokens|Your new group deploy token has been created.'), + newTokenDescription: s__( + 'DeployTokens|Use this token as a password. Save it. This password can %{i_start}not%{i_end} be recovered.', + ), + newTokenMessage: s__('DeployTokens|Your New Deploy Token'), + newTokenUsernameCopy: s__('DeployTokens|Copy username'), + newTokenUsernameDescription: s__( + 'DeployTokens|This username supports access. %{link_start}What kind of access?%{link_end}', + ), + readRepositoryHelp: s__('DeployTokens|Allows read-only access to the repository.'), + readRegistryHelp: s__('DeployTokens|Allows read-only access to registry images.'), + writeRegistryHelp: s__('DeployTokens|Allows read and write access to registry images.'), + readPackageRegistryHelp: s__('DeployTokens|Allows read-only access to the package registry.'), + writePackageRegistryHelp: s__( + 'DeployTokens|Allows read and write access to the package registry.', + ), + createTokenFailedAlert: s__('DeployTokens|Failed to create a new deployment token'), +}; + +export default translations; diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index b6ad2d21757..2c185794d17 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -391,11 +391,7 @@ export default { }; </script> <template> - <div - class="prometheus-graphs" - data-qa-selector="prometheus_graphs_content" - data-testid="prometheus-graphs" - > + <div class="prometheus-graphs" data-testid="prometheus-graphs"> <div> <gl-alert v-if="!isDeprecationNoticeDismissed" diff --git a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue index 7f8fb3c223d..d67154b7697 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue @@ -146,7 +146,6 @@ export default { <gl-dropdown v-gl-tooltip data-testid="actions-menu" - data-qa-selector="actions_menu_dropdown" right no-caret toggle-class="gl-px-3!" @@ -223,7 +222,6 @@ export default { <gl-dropdown-item v-if="isMenuItemEnabled.editDashboard" :href="selectedDashboard ? selectedDashboard.project_blob_path : null" - data-qa-selector="edit_dashboard_button_enabled" data-testid="edit-dashboard-item-enabled" > {{ $options.i18n.editDashboard }} diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue index 90d2498ac19..7bb0d3874d1 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_header.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue @@ -173,7 +173,6 @@ export default { <div class="gl-mb-3 gl-mr-3 gl-display-flex gl-sm-display-block"> <dashboards-dropdown id="monitor-dashboards-dropdown" - data-qa-selector="dashboards_filter_dropdown" class="flex-grow-1" toggle-class="dropdown-menu-toggle" :default-branch="defaultBranch" @@ -188,7 +187,6 @@ export default { id="monitor-environments-dropdown" ref="monitorEnvironmentsDropdown" class="flex-grow-1" - data-qa-selector="environments_dropdown" data-testid="environments-dropdown" toggle-class="dropdown-menu-toggle" menu-class="monitor-environment-dropdown-menu" @@ -225,7 +223,6 @@ export default { <date-time-picker ref="dateTimePicker" class="flex-grow-1 show-last-dropdown" - data-qa-selector="range_picker_dropdown" :value="selectedTimeRange" :options="$options.timeRanges" :utc="displayUtc" diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue index 7e7dcef7639..9ad6da35d6b 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue @@ -292,11 +292,7 @@ export default { <div v-if="graphDataIsLoading" class="mx-1 mt-1"> <gl-loading-icon size="sm" /> </div> - <div - v-if="isContextualMenuShown" - ref="contextualMenu" - data-qa-selector="prometheus_graph_widgets" - > + <div v-if="isContextualMenuShown" ref="contextualMenu"> <div data-testid="dropdown-wrapper" class="d-flex align-items-center"> <!-- This component should be replaced with a variant developed @@ -310,7 +306,6 @@ export default { :text-sr-only="true" toggle-class="gl-px-3!" no-caret - data-qa-selector="prometheus_widgets_dropdown" right :title="__('More actions')" > @@ -339,7 +334,6 @@ export default { ref="copyChartLink" v-track-event="generateLinkToChartOptions(clipboardText)" :data-clipboard-text="clipboardText" - data-qa-selector="generate_chart_link_menu_item" @click="showToast(clipboardText)" > {{ __('Copy link to chart') }} diff --git a/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue b/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue index a63008aa382..9ad14b3d52e 100644 --- a/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue +++ b/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue @@ -104,13 +104,7 @@ export default { label-size="sm" label-for="fileName" > - <gl-form-input - id="fileName" - ref="fileName" - v-model="form.fileName" - data-qa-selector="duplicate_dashboard_filename_field" - :required="true" - /> + <gl-form-input id="fileName" ref="fileName" v-model="form.fileName" :required="true" /> </gl-form-group> <gl-form-group :label="__('Branch')" label-size="sm" label-for="branch"> <gl-form-radio-group diff --git a/app/assets/javascripts/monitoring/components/variables_section.vue b/app/assets/javascripts/monitoring/components/variables_section.vue index 493d37ce263..971f188e9f3 100644 --- a/app/assets/javascripts/monitoring/components/variables_section.vue +++ b/app/assets/javascripts/monitoring/components/variables_section.vue @@ -37,11 +37,7 @@ export default { }; </script> <template> - <div - ref="variablesSection" - class="d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 variables-section" - data-qa-selector="variables_content" - > + <div ref="variablesSection" class="d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 variables-section"> <div v-for="variable in variables" :key="variable.name" class="mb-1 pr-2 d-flex d-sm-block"> <component :is="variableField(variable.type)" @@ -50,7 +46,6 @@ export default { :value="variable.value" :name="variable.name" :options="variable.options" - data-qa-selector="variable_item" @input="refreshDashboard(variable, $event)" /> </div> diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue index 189690ce2c3..201fba837e2 100644 --- a/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue +++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue @@ -43,9 +43,7 @@ export default { </script> <template> - <div - class="gl-bg-gray-10 gl-display-flex gl-p-3 gl-gap-3 gl-border-solid gl-border-gray-100 gl-border-1" - > + <div class="gl-display-flex gl-p-3 gl-gap-3 gl-border-solid gl-border-gray-100 gl-border-1"> <gl-button :href="$options.TEMPLATE_REPOSITORY_URL" size="small" diff --git a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue index 65f399d1912..22b82f2e96f 100644 --- a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue +++ b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue @@ -41,7 +41,9 @@ import { __, s__ } from '~/locale'; export default { i18n: { - invalid: __('Your CI/CD configuration syntax is invalid. View Lint tab for more details.'), + invalid: __( + 'Your CI/CD configuration syntax is invalid. Select the Validate tab for more details.', + ), unavailable: __( "We're experiencing difficulties and this tab content is currently unavailable.", ), diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index eb11e17dd1b..f78222d54d4 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -4,6 +4,7 @@ import { sprintf } from '~/locale'; import { getParameterByName } from '~/lib/utils/url_utility'; import { helpPagePath } from '~/helpers/help_page_helper'; import branchRulesQuery from '../../queries/branch_rules_details.query.graphql'; +import { getAccessLevels } from '../../../utils'; import Protection from './protection.vue'; import { I18N, @@ -120,23 +121,7 @@ export default { }, }, methods: { - getAccessLevels(accessLevels = {}) { - const total = accessLevels.edges?.length; - const accessLevelTypes = { total, users: [], groups: [], roles: [] }; - - accessLevels.edges?.forEach(({ node }) => { - if (node.user) { - const src = node.user.avatarUrl; - accessLevelTypes.users.push({ src, ...node.user }); - } else if (node.group) { - accessLevelTypes.groups.push(node); - } else { - accessLevelTypes.roles.push(node); - } - }); - - return accessLevelTypes; - }, + getAccessLevels, }, }; </script> diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue index a9eb2a53fbf..ecf194ccc3a 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue @@ -58,6 +58,7 @@ export default { :branch-protection="rule.branchProtection" :status-checks-total="rule.externalStatusChecks.nodes.length" :approval-rules-total="rule.approvalRules.nodes.length" + :matching-branches-count="rule.matchingBranchesCount" /> <span v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</span> diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue index 78c824c66d1..41947834bdb 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue @@ -1,6 +1,7 @@ <script> import { GlBadge, GlButton } from '@gitlab/ui'; import { s__, sprintf, n__ } from '~/locale'; +import { getAccessLevels } from '../../../utils'; export const i18n = { defaultLabel: s__('BranchRules|default'), @@ -9,6 +10,9 @@ export const i18n = { codeOwnerApprovalRequired: s__('BranchRules|Requires CODEOWNERS approval'), statusChecks: s__('BranchRules|%{total} status %{subject}'), approvalRules: s__('BranchRules|%{total} approval %{subject}'), + matchingBranches: s__('BranchRules|%{total} matching %{subject}'), + pushAccessLevels: s__('BranchRules|Allowed to merge'), + mergeAccessLevels: s__('BranchRules|Allowed to push'), }; export default { @@ -48,8 +52,16 @@ export default { required: false, default: 0, }, + matchingBranchesCount: { + type: Number, + required: false, + default: 0, + }, }, computed: { + isWildcard() { + return this.name.includes('*'); + }, hasApprovalDetails() { return this.approvalDetails.length; }, @@ -68,8 +80,31 @@ export default { subject: n__('rule', 'rules', this.approvalRulesTotal), }); }, + matchingBranchesText() { + return sprintf(this.$options.i18n.matchingBranches, { + total: this.matchingBranchesCount, + subject: n__('branch', 'branches', this.matchingBranchesCount), + }); + }, + mergeAccessLevels() { + const { mergeAccessLevels } = this.branchProtection || {}; + return this.getAccessLevels(mergeAccessLevels); + }, + pushAccessLevels() { + const { pushAccessLevels } = this.branchProtection || {}; + return this.getAccessLevels(pushAccessLevels); + }, + pushAccessLevelsText() { + return this.getAccessLevelsText(this.$options.i18n.pushAccessLevels, this.pushAccessLevels); + }, + mergeAccessLevelsText() { + return this.getAccessLevelsText(this.$options.i18n.mergeAccessLevels, this.mergeAccessLevels); + }, approvalDetails() { const approvalDetails = []; + if (this.isWildcard) { + approvalDetails.push(this.matchingBranchesText); + } if (this.branchProtection.allowForcePush) { approvalDetails.push(this.$options.i18n.allowForcePush); } @@ -82,9 +117,31 @@ export default { if (this.approvalRulesTotal) { approvalDetails.push(this.approvalRulesText); } + if (this.mergeAccessLevels.total > 0) { + approvalDetails.push(this.mergeAccessLevelsText); + } + if (this.pushAccessLevels.total > 0) { + approvalDetails.push(this.pushAccessLevelsText); + } return approvalDetails; }, }, + methods: { + getAccessLevels, + getAccessLevelsText(beginString = '', accessLevels) { + const textParts = []; + if (accessLevels.roles.length) { + textParts.push(n__('1 role', '%d roles', accessLevels.roles.length)); + } + if (accessLevels.groups.length) { + textParts.push(n__('1 group', '%d groups', accessLevels.groups.length)); + } + if (accessLevels.users.length) { + textParts.push(n__('1 user', '%d users', accessLevels.users.length)); + } + return `${beginString}: ${textParts.join(', ')}`; + }, + }, }; </script> diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql b/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql index 49e089e7805..7bd8e6d4a64 100644 --- a/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql +++ b/app/assets/javascripts/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql @@ -5,9 +5,38 @@ query getBranchRules($projectPath: ID!) { nodes { name isDefault + matchingBranchesCount branchProtection { allowForcePush codeOwnerApprovalRequired + mergeAccessLevels { + edges { + node { + accessLevel + accessLevelDescription + group { + id + } + user { + id + } + } + } + } + pushAccessLevels { + edges { + node { + accessLevel + accessLevelDescription + group { + id + } + user { + id + } + } + } + } } externalStatusChecks { nodes { diff --git a/app/assets/javascripts/projects/settings/utils.js b/app/assets/javascripts/projects/settings/utils.js new file mode 100644 index 00000000000..7bcfde39178 --- /dev/null +++ b/app/assets/javascripts/projects/settings/utils.js @@ -0,0 +1,17 @@ +export const getAccessLevels = (accessLevels = {}) => { + const total = accessLevels.edges?.length; + const accessLevelTypes = { total, users: [], groups: [], roles: [] }; + + accessLevels.edges?.forEach(({ node }) => { + if (node.user) { + const src = node.user.avatarUrl; + accessLevelTypes.users.push({ src, ...node.user }); + } else if (node.group) { + accessLevelTypes.groups.push(node); + } else { + accessLevelTypes.roles.push(node); + } + }); + + return accessLevelTypes; +}; diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 912f0fdcbef..b58b3cb4df3 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -56,12 +56,14 @@ export default class SidebarMediator { } async saveReviewers(field) { - const selected = this.store.reviewers.map((u) => u.id); + const selectedReviewers = this.store.reviewers; + const selectedIds = selectedReviewers.map((u) => u.id); + const suggestedSelectedIds = selectedReviewers.filter((u) => u.suggested).map((u) => u.id); // If there are no ids, that means we have to unassign (which is id = 0) // And it only accepts an array, hence [0] - const reviewers = selected.length === 0 ? [0] : selected; - const data = { reviewer_ids: reviewers }; + const reviewers = selectedIds.length === 0 ? [0] : selectedIds; + const data = { reviewer_ids: reviewers, suggested_reviewer_ids: suggestedSelectedIds }; try { const res = await this.service.update(field, data); diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index 181c1b89e31..d8a2789a419 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -265,7 +265,6 @@ export default { <gl-dropdown-item v-for="(option, index) in options" :key="index" - data-qa-selector="quick_range_item" :active="isOptionActive(option)" active-class="active" @click="setQuickRange(option)" diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue index 428fa9f8279..28a16cd846a 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue @@ -1,6 +1,6 @@ <script> import { GlIntersectionObserver } from '@gitlab/ui'; -import { scrollToElement } from '~/lib/utils/common_utils'; +import LineHighlighter from '~/blob/line_highlighter'; import ChunkLine from './chunk_line.vue'; /* @@ -81,12 +81,14 @@ export default { return; } - window.requestIdleCallback(() => { + window.requestIdleCallback(async () => { this.isLoading = false; const { hash } = this.$route; if (hash && this.totalChunks > 0 && this.totalChunks === this.chunkIndex + 1) { // when the last chunk is loaded scroll to the hash - scrollToElement(hash, { behavior: 'auto' }); + await this.$nextTick(); + const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' }); + lineHighlighter.highlightHash(hash); } }); }, diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js index a6628fa0f9f..f3cb5fc16f0 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -29,7 +29,13 @@ export const fetchDiffData = (state, endpoint, category) => { */ export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) => feedback - .filter((fb) => fb.project_fingerprint === vulnerability.project_fingerprint) + .filter((fb) => + // Some records still have a `finding_uuid` with null, we need to fallback to using `project_fingerprint` in those cases. Once all entries have been fixed, we can remove the fallback. + // related epic: https://gitlab.com/groups/gitlab-org/-/epics/2791 + fb.finding_uuid !== null + ? fb.finding_uuid === vulnerability.finding_uuid + : fb.project_fingerprint === vulnerability.project_fingerprint, + ) .reduce((vuln, fb) => { if (fb.feedback_type === FEEDBACK_TYPE_DISMISSAL) { return { diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 817f82085e6..b4a36b7db22 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -23,6 +23,8 @@ module AuthenticatesWithTwoFactor session[:otp_user_id] = user.id session[:user_password_hash] = Digest::SHA256.hexdigest(user.encrypted_password) + + add_gon_variables push_frontend_feature_flag(:webauthn) if Feature.enabled?(:webauthn) diff --git a/app/controllers/projects/work_items_controller.rb b/app/controllers/projects/work_items_controller.rb index a7e59a28fb7..e9f5a7207bb 100644 --- a/app/controllers/projects/work_items_controller.rb +++ b/app/controllers/projects/work_items_controller.rb @@ -3,6 +3,7 @@ class Projects::WorkItemsController < Projects::ApplicationController before_action do push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?) + push_force_frontend_feature_flag(:work_items_mvc, project&.work_items_mvc_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?) push_frontend_feature_flag(:use_iid_in_work_items_path, project) end diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb index 81099c04e9f..1d532eb2486 100644 --- a/app/graphql/resolvers/concerns/looks_ahead.rb +++ b/app/graphql/resolvers/concerns/looks_ahead.rb @@ -39,7 +39,7 @@ module LooksAhead def filtered_preloads nodes = node_selection - return [] unless nodes + return [] unless nodes&.selected? selected_fields = nodes.selections.map(&:name) root_level_preloads = preloads_from_node_selection(selected_fields, preloads) @@ -65,13 +65,13 @@ module LooksAhead end.flatten end - def node_selection - return unless lookahead + def node_selection(selection = lookahead) + return selection unless selection&.selected? + return selection.selection(:edges).selection(:node) if selection.selects?(:edges) - if lookahead.selects?(:nodes) - lookahead.selection(:nodes) - elsif lookahead.selects?(:edges) - lookahead.selection(:edges).selection(:node) - end + # Will return a NullSelection object if :nodes is not a selection. This + # is better than returning nil as we can continue chaining selections on + # without raising errors. + selection.selection(:nodes) end end diff --git a/app/models/group.rb b/app/models/group.rb index edbc8825663..e50a886f9ba 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -892,6 +892,10 @@ class Group < Namespace feature_flag_enabled_for_self_or_ancestor?(:work_items) end + def work_items_mvc_feature_flag_enabled? + feature_flag_enabled_for_self_or_ancestor?(:work_items_mvc) + end + def work_items_mvc_2_feature_flag_enabled? feature_flag_enabled_for_self_or_ancestor?(:work_items_mvc_2) end diff --git a/app/models/merge_request/predictions.rb b/app/models/merge_request/predictions.rb index ef9e00b5f74..61494ca584c 100644 --- a/app/models/merge_request/predictions.rb +++ b/app/models/merge_request/predictions.rb @@ -4,4 +4,13 @@ class MergeRequest::Predictions < ApplicationRecord # rubocop:disable Style/Cla belongs_to :merge_request, inverse_of: :predictions validates :suggested_reviewers, json_schema: { filename: 'merge_request_predictions_suggested_reviewers' } + validates :accepted_reviewers, json_schema: { filename: 'merge_request_predictions_accepted_reviewers' } + + def suggested_reviewer_usernames + Array.wrap(suggested_reviewers['reviewers']) + end + + def accepted_reviewer_usernames + Array.wrap(accepted_reviewers['reviewers']) + end end diff --git a/app/models/project.rb b/app/models/project.rb index a07d4147228..d07e9ff759e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2994,6 +2994,10 @@ class Project < ApplicationRecord group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self) end + def work_items_mvc_feature_flag_enabled? + group&.work_items_mvc_feature_flag_enabled? || Feature.enabled?(:work_items_mvc) + end + def work_items_mvc_2_feature_flag_enabled? group&.work_items_mvc_2_feature_flag_enabled? || Feature.enabled?(:work_items_mvc_2) end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index e7ab2c062ee..fabf006979b 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -59,6 +59,8 @@ module MergeRequests merge_request_activity_counter.track_users_review_requested(users: new_reviewers) merge_request_activity_counter.track_reviewers_changed_action(user: current_user) trigger_merge_request_reviewers_updated(merge_request) + + capture_suggested_reviewers_accepted(merge_request) end def cleanup_environments(merge_request) @@ -161,6 +163,12 @@ module MergeRequests else params.delete(:reviewer_ids) end + + filter_suggested_reviewers + end + + def filter_suggested_reviewers + # Implemented in EE end def merge_request_metrics_service(merge_request) @@ -253,6 +261,10 @@ module MergeRequests def trigger_merge_request_merge_status_updated(merge_request) GraphqlTriggers.merge_request_merge_status_updated(merge_request) end + + def capture_suggested_reviewers_accepted(merge_request) + # Implemented in EE + end end end diff --git a/app/validators/json_schemas/merge_request_predictions_accepted_reviewers.json b/app/validators/json_schemas/merge_request_predictions_accepted_reviewers.json new file mode 100644 index 00000000000..fcbe8903df4 --- /dev/null +++ b/app/validators/json_schemas/merge_request_predictions_accepted_reviewers.json @@ -0,0 +1,10 @@ +{ + "description": "Merge request predictions accepted reviewers", + "type": "object", + "properties": { + "reviewers": { + "type": "array" + } + }, + "additionalProperties": true +}
\ No newline at end of file diff --git a/app/views/layouts/header/_marketing_links.html.haml b/app/views/layouts/header/_marketing_links.html.haml index 24069de394d..c33229e4ec4 100644 --- a/app/views/layouts/header/_marketing_links.html.haml +++ b/app/views/layouts/header/_marketing_links.html.haml @@ -6,29 +6,29 @@ .dropdown-menu %ul %li - = link_to 'https://about.gitlab.com/stages-devops-lifecycle/' do + = link_to Gitlab::Utils.append_path(promo_url, 'stages-devops-lifecycle') do = s_('LoggedOutMarketingHeader|GitLab: the DevOps platform') %li = link_to explore_root_path do = s_('LoggedOutMarketingHeader|Explore GitLab') %li - = link_to 'https://about.gitlab.com/install/' do + = link_to Gitlab::Utils.append_path(promo_url, 'install') do = s_('LoggedOutMarketingHeader|Install GitLab') %li - = link_to 'https://about.gitlab.com/is-it-any-good/' do + = link_to Gitlab::Utils.append_path(promo_url, 'is-it-any-good') do = s_('LoggedOutMarketingHeader|How GitLab compares') %li - = link_to 'https://about.gitlab.com/get-started/' do + = link_to Gitlab::Utils.append_path(promo_url, 'get-started') do = s_('LoggedOutMarketingHeader|Get started') %li - = link_to 'https://docs.gitlab.com/' do + = link_to Gitlab::Saas::doc_url do = s_('LoggedOutMarketingHeader|GitLab docs') %li - = link_to 'https://about.gitlab.com/learn/' do + = link_to Gitlab::Utils.append_path(promo_url, 'learn') do = s_('LoggedOutMarketingHeader|GitLab Learn') %li.gl-mr-3 - = link_to 'https://about.gitlab.com/pricing/' do + = link_to Gitlab::Utils.append_path(promo_url, 'pricing') do = s_('LoggedOutMarketingHeader|Pricing') %li.gl-mr-3 - = link_to 'https://about.gitlab.com/sales/' do + = link_to Gitlab::Utils.append_path(promo_url, 'sales') do = s_('LoggedOutMarketingHeader|Talk to an expert') diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 8bf397d0796..712d6fabf82 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -1,23 +1,23 @@ +- show_auto_devops_callout = show_auto_devops_callout?(@project) - is_project_overview = local_assigns.fetch(:is_project_overview, false) - ref = local_assigns.fetch(:ref) { current_ref } - project = local_assigns.fetch(:project) { @project } -- show_auto_devops_callout = show_auto_devops_callout?(@project) - add_page_startup_api_call logs_file_project_ref_path(@project, ref, @path, format: "json", offset: 0) - if readme_path = @project.repository.readme_path - add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json") #tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } } + .info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column + #js-last-commit.gl-m-auto + = gl_loading_icon(size: 'md') + #js-code-owners + .nav-block.gl-display-flex.gl-xs-flex-direction-column.gl-align-items-stretch = render 'projects/tree/tree_header', tree: @tree, is_project_overview: is_project_overview - if project.forked? && Feature.enabled?(:fork_divergence_counts, @project.fork_source) = render 'projects/fork_info' - .info-well.gl-display-none.gl-sm-display-flex.project-last-commit.gl-flex-direction-column - #js-last-commit.gl-m-auto - = gl_loading_icon(size: 'md') - #js-code-owners - - if is_project_overview .project-buttons.gl-mb-5.js-show-on-project-root{ data: { qa_selector: 'project_buttons' } } = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout), project_buttons: true diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml index e8e1bd87abb..2d9f7e49ddc 100644 --- a/app/views/projects/_flash_messages.html.haml +++ b/app/views/projects/_flash_messages.html.haml @@ -7,6 +7,8 @@ = render 'shared/no_password' - unless project.empty_repo? = render 'shared/auto_devops_implicitly_enabled_banner', project: project + - if show_auto_devops_callout?(@project) + = render 'shared/auto_devops_callout' = render_if_exists 'projects/above_size_limit_warning', project: project = render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)] = render_if_exists 'projects/terraform_banner', project: project diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 2c20a77937e..5fa70c3af32 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -25,8 +25,5 @@ - view_path = @project.default_view -- if show_auto_devops_callout?(@project) - = render 'shared/auto_devops_callout' - %div{ class: project_child_container_class(view_path) } = render view_path, is_project_overview: true diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml index c2b941c6106..93f919f01d9 100644 --- a/app/views/shared/_auto_devops_callout.html.haml +++ b/app/views/shared/_auto_devops_callout.html.haml @@ -1,13 +1,16 @@ -= render Pajamas::BannerComponent.new(button_text: s_('AutoDevOps|Enable in settings'), - button_link: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), - svg_path: 'illustrations/autodevops.svg', - banner_options: { class: 'js-autodevops-banner', data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }, - close_options: { 'aria-label' => s_('AutoDevOps|Dismiss Auto DevOps box'), class: 'js-close-callout' }) do |c| - - c.title do - = s_('AutoDevOps|Auto DevOps') +- container = @no_breadcrumb_container ? 'container-fluid' : container_class - %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') +%div{ class: [container, @content_class, 'gl-pt-5!'] } + = render Pajamas::BannerComponent.new(button_text: s_('AutoDevOps|Enable in settings'), + button_link: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), + svg_path: 'illustrations/autodevops.svg', + banner_options: { class: 'js-autodevops-banner auto-devops-callout', data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }, + close_options: { 'aria-label' => s_('AutoDevOps|Dismiss Auto DevOps box'), class: 'js-close-callout' }) do |c| + - c.title do + = s_('AutoDevOps|Auto DevOps') - %p - - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') - = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } + %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') + + %p + - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') + = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } diff --git a/app/views/shared/integrations/prometheus/_custom_metrics.html.haml b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml index 896249c6163..dda84e0fb9e 100644 --- a/app/views/shared/integrations/prometheus/_custom_metrics.html.haml +++ b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml @@ -6,12 +6,12 @@ = link_to s_('PrometheusService|More information'), help_page_path('operations/metrics/index.md', anchor: 'adding-custom-metrics'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 - .card.custom-monitored-metrics.js-panel-custom-monitored-metrics{ data: { qa_selector: 'custom_metrics_container', active_custom_metrics: project_prometheus_metrics_path(project), environments_data: environments_list_data, service_active: "#{integration.active}" } } + .card.custom-monitored-metrics.js-panel-custom-monitored-metrics{ data: { active_custom_metrics: project_prometheus_metrics_path(project), environments_data: environments_list_data, service_active: "#{integration.active}" } } .card-header %strong = s_('PrometheusService|Custom metrics') = gl_badge_tag 0, nil, class: 'js-custom-monitored-count' - = link_to s_('PrometheusService|New metric'), new_project_prometheus_metric_path(project), class: 'btn gl-button btn-confirm gl-ml-auto js-new-metric-button hidden', data: { qa_selector: 'new_metric_button' } + = link_to s_('PrometheusService|New metric'), new_project_prometheus_metric_path(project), class: 'btn gl-button btn-confirm gl-ml-auto js-new-metric-button hidden' .card-body .flash-container.hidden .flash-warning |