Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-23 00:08:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-23 00:08:01 +0300
commitc50e042a392687730db9b8c2607883485b258ae4 (patch)
tree519b069aa0a400241a2f8dc0f900f09625e3d8ed
parent7e2f555a6dc37839727dee130d8ed4421b680d42 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue2
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_header.vue3
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_pause_button.vue16
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_update_form.vue7
-rw-r--r--app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql (renamed from app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql)4
-rw-r--r--app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/ci/runner/runner_update_form_utils.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue90
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql6
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql17
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue25
-rw-r--r--app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql2
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/integrations/jira.rb4
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/views/layouts/group.html.haml1
-rw-r--r--app/views/layouts/project.html.haml1
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml8
-rw-r--r--doc/administration/monitoring/prometheus/index.md18
-rw-r--r--doc/administration/package_information/defaults.md2
-rw-r--r--doc/ci/environments/index.md181
-rw-r--r--doc/ci/yaml/artifacts_reports.md2
-rw-r--r--doc/development/vs_code_debugging.md20
-rw-r--r--doc/user/project/merge_requests/widgets.md2
-rw-r--r--doc/user/project/protected_branches.md47
-rw-r--r--doc/user/project/protected_tags.md6
-rw-r--r--doc/user/project/repository/code_suggestions.md17
-rw-r--r--lib/api/v3/github.rb10
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/sidebars/groups/menus/packages_registries_menu.rb2
-rw-r--r--lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb26
-rw-r--r--lib/sidebars/groups/super_sidebar_menus/operations_menu.rb3
-rw-r--r--lib/sidebars/groups/super_sidebar_panel.rb1
-rw-r--r--lib/sidebars/projects/menus/deployments_menu.rb6
-rw-r--r--lib/sidebars/projects/menus/packages_registries_menu.rb6
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/analyze_menu.rb3
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/build_menu.rb3
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/deploy_menu.rb30
-rw-r--r--lib/sidebars/projects/super_sidebar_menus/operations_menu.rb5
-rw-r--r--lib/sidebars/projects/super_sidebar_panel.rb1
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/features/nav/pinned_nav_items_spec.rb8
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_delete_button_spec.js2
-rw-r--r--spec/frontend/ci/runner/components/runner_list_spec.js2
-rw-r--r--spec/frontend/ci/runner/components/runner_pause_button_spec.js40
-rw-r--r--spec/frontend/ci/runner/components/runner_update_form_spec.js6
-rw-r--r--spec/frontend/ci/runner/runner_update_form_utils_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js206
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js15
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js12
-rw-r--r--spec/graphql/resolvers/users/participants_resolver_spec.rb67
-rw-r--r--spec/lib/sidebars/groups/super_sidebar_menus/deploy_menu_spec.rb21
-rw-r--r--spec/lib/sidebars/groups/super_sidebar_menus/operations_menu_spec.rb3
-rw-r--r--spec/lib/sidebars/groups/super_sidebar_panel_spec.rb1
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/analyze_menu_spec.rb3
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/build_menu_spec.rb3
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/deploy_menu_spec.rb25
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_menus/operations_menu_spec.rb5
-rw-r--r--spec/lib/sidebars/projects/super_sidebar_panel_spec.rb1
-rw-r--r--spec/models/integrations/jira_spec.rb12
-rw-r--r--spec/models/integrations/pipelines_email_spec.rb19
-rw-r--r--spec/requests/api/v3/github_spec.rb23
-rw-r--r--spec/support/shared_examples/models/concerns/participable_shared_examples.rb11
68 files changed, 690 insertions, 422 deletions
diff --git a/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue
index 4d04b5d4b14..149c69e5307 100644
--- a/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue
+++ b/app/assets/javascripts/ci/runner/components/cells/runner_status_cell.vue
@@ -20,7 +20,7 @@ export default {
},
computed: {
paused() {
- return !this.runner.active;
+ return this.runner.paused;
},
},
};
diff --git a/app/assets/javascripts/ci/runner/components/runner_header.vue b/app/assets/javascripts/ci/runner/components/runner_header.vue
index 874c234ca4c..9e29dc7a52e 100644
--- a/app/assets/javascripts/ci/runner/components/runner_header.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_header.vue
@@ -25,9 +25,6 @@ export default {
},
},
computed: {
- paused() {
- return !this.runner.active;
- },
heading() {
const id = getIdFromGraphQLId(this.runner.id);
return sprintf(I18N_DETAILS_TITLE, { runner_id: id });
diff --git a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
index a27af232e97..d16c8f98bad 100644
--- a/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_pause_button.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
+import runnerTogglePausedMutation from '~/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql';
import { createAlert } from '~/alert';
import { captureException } from '~/ci/runner/sentry_utils';
import { I18N_PAUSE, I18N_PAUSE_TOOLTIP, I18N_RESUME, I18N_RESUME_TOOLTIP } from '../constants';
@@ -31,14 +31,14 @@ export default {
};
},
computed: {
- isActive() {
- return this.runner.active;
+ isPaused() {
+ return this.runner.paused;
},
icon() {
- return this.isActive ? 'pause' : 'play';
+ return this.isPaused ? 'play' : 'pause';
},
label() {
- return this.isActive ? I18N_PAUSE : I18N_RESUME;
+ return this.isPaused ? I18N_RESUME : I18N_PAUSE;
},
buttonContent() {
if (this.compact) {
@@ -56,7 +56,7 @@ export default {
// Prevent a "sticky" tooltip: If this button is disabled,
// mouseout listeners don't run leaving the tooltip stuck
if (!this.updating) {
- return this.isActive ? I18N_PAUSE_TOOLTIP : I18N_RESUME_TOOLTIP;
+ return this.isPaused ? I18N_RESUME_TOOLTIP : I18N_PAUSE_TOOLTIP;
}
return '';
},
@@ -67,7 +67,7 @@ export default {
try {
const input = {
id: this.runner.id,
- active: !this.isActive,
+ paused: !this.isPaused,
};
const {
@@ -75,7 +75,7 @@ export default {
runnerUpdate: { errors },
},
} = await this.$apollo.mutate({
- mutation: runnerToggleActiveMutation,
+ mutation: runnerTogglePausedMutation,
variables: {
input,
},
diff --git a/app/assets/javascripts/ci/runner/components/runner_update_form.vue b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
index 0b05969a551..aebddc70646 100644
--- a/app/assets/javascripts/ci/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_update_form.vue
@@ -134,12 +134,7 @@ export default {
</template>
<template v-else>
<div class="gl-mb-5">
- <gl-form-checkbox
- v-model="model.active"
- data-testid="runner-field-paused"
- :value="false"
- :unchecked-value="true"
- >
+ <gl-form-checkbox v-model="model.paused" data-testid="runner-field-paused">
{{ __('Paused') }}
<template #help>
{{ s__('Runners|Stop the runner from accepting new jobs.') }}
diff --git a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
index d18b80511fb..41ec9967d90 100644
--- a/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/edit/runner_fields_shared.fragment.graphql
@@ -2,7 +2,7 @@ fragment RunnerFieldsShared on CiRunner {
id
shortSha
runnerType
- active
+ paused
accessLevel
runUntagged
locked
diff --git a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
index 4eebcd01be6..624980f58af 100644
--- a/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/list/list_item_shared.fragment.graphql
@@ -7,7 +7,7 @@ fragment ListItemShared on CiRunner {
shortSha
version
ipAddress
- active
+ paused
locked
jobCount
tagList
diff --git a/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql b/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql
index 9b15570dbc0..e862a20750f 100644
--- a/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql
@@ -1,11 +1,11 @@
# Mutation executed for the pause/resume button in the
# runner list and details views.
-mutation runnerToggleActive($input: RunnerUpdateInput!) {
+mutation runnerTogglePaused($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
id
- active
+ paused
}
errors
}
diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
index bd53fb29bd0..87d92b8e263 100644
--- a/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/show/runner_details_shared.fragment.graphql
@@ -2,7 +2,7 @@ fragment RunnerDetailsShared on CiRunner {
id
shortSha
runnerType
- active
+ paused
accessLevel
runUntagged
locked
diff --git a/app/assets/javascripts/ci/runner/runner_update_form_utils.js b/app/assets/javascripts/ci/runner/runner_update_form_utils.js
index 3b519fa7d71..6f6c9f64af0 100644
--- a/app/assets/javascripts/ci/runner/runner_update_form_utils.js
+++ b/app/assets/javascripts/ci/runner/runner_update_form_utils.js
@@ -4,7 +4,7 @@ export const runnerToModel = (runner) => {
description,
maximumTimeout,
accessLevel,
- active,
+ paused,
locked,
runUntagged,
tagList = [],
@@ -15,7 +15,7 @@ export const runnerToModel = (runner) => {
description,
maximumTimeout,
accessLevel,
- active,
+ paused,
locked,
runUntagged,
tagList: tagList.join(', '),
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
index 8eb8654cddd..10ac4c5383b 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
@@ -1,5 +1,14 @@
<script>
-import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlButton, GlFormCheckbox } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlLink,
+ GlTable,
+ GlDropdownItem,
+ GlDropdown,
+ GlButton,
+ GlFormCheckbox,
+ GlLoadingIcon,
+} from '@gitlab/ui';
import { last } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __, s__ } from '~/locale';
@@ -9,21 +18,26 @@ import { packageTypeToTrackCategory } from '~/packages_and_registries/package_re
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
+ FETCH_PACKAGE_FILES_ERROR_MESSAGE,
+ GRAPHQL_PACKAGE_FILES_PAGE_SIZE,
REQUEST_DELETE_SELECTED_PACKAGE_FILE_TRACKING_ACTION,
SELECT_PACKAGE_FILE_TRACKING_ACTION,
TRACKING_LABEL_PACKAGE_ASSET,
TRACKING_ACTION_EXPAND_PACKAGE_ASSET,
} from '~/packages_and_registries/package_registry/constants';
+import getPackageFilesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql';
export default {
name: 'PackageFiles',
components: {
+ GlAlert,
GlLink,
GlTable,
GlDropdown,
GlDropdownItem,
GlFormCheckbox,
GlButton,
+ GlLoadingIcon,
FileIcon,
TimeAgoTooltip,
FileSha,
@@ -40,14 +54,36 @@ export default {
required: false,
default: false,
},
+ packageId: {
+ type: String,
+ required: true,
+ },
+ packageType: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
packageFiles: {
- type: Array,
- required: false,
- default: () => [],
+ query: getPackageFilesQuery,
+ context: {
+ isSingleRequest: true,
+ },
+ variables() {
+ return this.queryVariables;
+ },
+ update(data) {
+ return data.package?.packageFiles?.nodes || [];
+ },
+ error() {
+ this.fetchPackageFilesError = true;
+ },
},
},
data() {
return {
+ fetchPackageFilesError: false,
+ packageFiles: [],
selectedReferences: [],
};
},
@@ -56,7 +92,7 @@ export default {
return this.selectedReferences.length > 0;
},
areAllFilesSelected() {
- return this.packageFiles.every(this.isSelected);
+ return this.packageFiles.length > 0 && this.packageFiles.every(this.isSelected);
},
filesTableRows() {
return this.packageFiles.map((pf) => ({
@@ -68,10 +104,8 @@ export default {
hasSelectedSomeFiles() {
return this.areFilesSelected && !this.areAllFilesSelected;
},
- showCommitColumn() {
- // note that this is always false for now since we do not return
- // pipelines associated to files for performance concerns
- return this.filesTableRows.some((row) => Boolean(row.pipeline?.id));
+ loading() {
+ return this.$apollo.queries.packageFiles.loading || this.isLoading;
},
filesTableHeaderFields() {
return [
@@ -86,11 +120,6 @@ export default {
label: __('Name'),
},
{
- key: 'commit',
- label: __('Commit'),
- hide: !this.showCommitColumn,
- },
- {
key: 'size',
label: __('Size'),
},
@@ -108,6 +137,12 @@ export default {
},
].filter((c) => !c.hide);
},
+ queryVariables() {
+ return {
+ id: this.packageId,
+ first: GRAPHQL_PACKAGE_FILES_PAGE_SIZE,
+ };
+ },
tracking() {
return {
category: packageTypeToTrackCategory(this.packageType),
@@ -142,6 +177,7 @@ export default {
deleteFile: __('Delete asset'),
deleteSelected: s__('PackageRegistry|Delete selected'),
moreActionsText: __('More actions'),
+ fetchPackageFilesErrorMessage: FETCH_PACKAGE_FILES_ERROR_MESSAGE,
},
};
</script>
@@ -151,8 +187,8 @@ export default {
<div class="gl-display-flex gl-align-items-center gl-justify-content-space-between">
<h3 class="gl-font-lg gl-mt-5">{{ __('Assets') }}</h3>
<gl-button
- v-if="canDelete"
- :disabled="isLoading || !areFilesSelected"
+ v-if="!fetchPackageFilesError && canDelete"
+ :disabled="loading || !areFilesSelected"
category="secondary"
variant="danger"
data-testid="delete-selected"
@@ -161,7 +197,16 @@ export default {
{{ $options.i18n.deleteSelected }}
</gl-button>
</div>
+ <gl-alert
+ v-if="fetchPackageFilesError"
+ variant="danger"
+ @dismiss="fetchPackageFilesError = false"
+ >
+ {{ $options.i18n.fetchPackageFilesErrorMessage }}
+ </gl-alert>
<gl-table
+ v-else
+ :busy="loading"
:fields="filesTableHeaderFields"
:items="filesTableRows"
show-empty
@@ -171,6 +216,9 @@ export default {
:tbody-tr-attr="{ 'data-testid': 'file-row' }"
@row-selected="updateSelectedReferences"
>
+ <template #table-busy>
+ <gl-loading-icon size="lg" class="gl-my-5" />
+ </template>
<template #head(checkbox)="{ selectAllRows, clearSelected }">
<gl-form-checkbox
v-if="canDelete"
@@ -218,16 +266,6 @@ export default {
</gl-link>
</template>
- <template #cell(commit)="{ item }">
- <gl-link
- v-if="item.pipeline && item.pipeline"
- :href="item.pipeline.commitPath"
- class="gl-text-gray-500"
- data-testid="commit-link"
- >{{ item.pipeline.sha }}
- </gl-link>
- </template>
-
<template #cell(created)="{ item }">
<time-ago-tooltip :time="item.createdAt" />
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index b4276d69ed6..80712c2991c 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -102,6 +102,9 @@ export const FETCH_PACKAGE_PIPELINES_ERROR_MESSAGE = s__(
export const FETCH_PACKAGE_METADATA_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while fetching the package metadata.',
);
+export const FETCH_PACKAGE_FILES_ERROR_MESSAGE = s__(
+ 'PackageRegistry|Something went wrong while fetching package assets.',
+);
export const DELETE_PACKAGES_TRACKING_ACTION = 'delete_packages';
export const REQUEST_DELETE_PACKAGES_TRACKING_ACTION = 'request_delete_packages';
@@ -232,3 +235,4 @@ export const REQUEST_FORWARDING_HELP_PAGE_PATH = helpPagePath(
);
export const GRAPHQL_PACKAGE_PIPELINES_PAGE_SIZE = 10;
+export const GRAPHQL_PACKAGE_FILES_PAGE_SIZE = 100;
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
index 39e5da54509..d05ff5daad4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
@@ -21,6 +21,9 @@ export const apolloProvider = new VueApollo({
keyArgs: false,
merge: mergeVariables,
},
+ packageFiles: {
+ merge: mergeVariables,
+ },
},
},
},
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 984996b829a..e5ef9265f3e 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -52,13 +52,7 @@ query getPackageDetails($id: PackagesPackageID!) {
}
nodes {
id
- fileMd5
- fileName
- fileSha1
- fileSha256
size
- createdAt
- downloadPath
}
}
versions {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql
new file mode 100644
index 00000000000..7851cd39200
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql
@@ -0,0 +1,17 @@
+query getPackageFiles($id: PackagesPackageID!, $first: Int) {
+ package(id: $id) {
+ id
+ packageFiles(first: $first) {
+ nodes {
+ id
+ fileMd5
+ fileName
+ fileSha1
+ fileSha256
+ size
+ createdAt
+ downloadPath
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
index 40e175fab99..48a45956ef1 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
@@ -158,8 +158,8 @@ export default {
isLoading() {
return this.$apollo.queries.packageEntity.loading;
},
- packageFilesLoading() {
- return this.isLoading || this.mutationLoading;
+ packageFilesMutationLoading() {
+ return this.mutationLoading;
},
isValidPackage() {
return this.isLoading || Boolean(this.packageEntity.name);
@@ -360,7 +360,7 @@ export default {
<gl-tabs>
<gl-tab :title="__('Detail')">
- <div v-if="!isLoading" data-qa-selector="package_information_content">
+ <div data-qa-selector="package_information_content">
<package-history :package-entity="packageEntity" :project-name="projectName" />
<installation-commands :package-entity="packageEntity" />
@@ -370,16 +370,17 @@ export default {
:package-id="packageEntity.id"
:package-type="packageType"
/>
- </div>
- <package-files
- v-if="showFiles"
- :can-delete="packageEntity.canDestroy"
- :is-loading="packageFilesLoading"
- :package-files="packageFiles"
- @download-file="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)"
- @delete-files="handleFileDelete"
- />
+ <package-files
+ v-if="showFiles"
+ :can-delete="packageEntity.canDestroy"
+ :is-loading="packageFilesMutationLoading"
+ :package-id="packageEntity.id"
+ :package-type="packageType"
+ @download-file="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)"
+ @delete-files="handleFileDelete"
+ />
+ </div>
</gl-tab>
<gl-tab v-if="showDependencies">
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
index 5bdafa15f72..c1f994ece24 100644
--- a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql
@@ -3,7 +3,7 @@ query getFailedJobs($fullPath: ID!, $pipelineIid: ID!) {
id
pipeline(iid: $pipelineIid) {
id
- jobs(statuses: FAILED, retried: false) {
+ jobs(statuses: FAILED, retried: false, jobKind: BUILD) {
nodes {
status
detailedStatus {
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index b1ec6b8ba32..94cc417af22 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -654,6 +654,7 @@ module Issuable
def read_ability_for(participable_source)
return super if participable_source == self
+ return super if participable_source.is_a?(Note) && participable_source.system?
name = participable_source.try(:issuable_ability_name) || :read_issuable_participables
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index 318d30d8b2e..b4f2282fd7a 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -374,9 +374,9 @@ module Integrations
private
def jira_issue_match_regex
- match_regex = (jira_issue_regex.presence || Gitlab::Regex.jira_issue_key_regex)
+ return /\b#{jira_issue_prefix}(?<issue>#{Gitlab::Regex.jira_issue_key_regex})/ if jira_issue_regex.blank?
- /\b#{jira_issue_prefix}(?<issue>#{match_regex})/
+ Gitlab::UntrustedRegexp.new("\\b#{jira_issue_prefix}(?P<issue>#{jira_issue_regex})")
end
def parse_project_from_issue_key(issue_key)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b7e39423e85..e4b2b81005c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -420,7 +420,7 @@ class MergeRequest < ApplicationRecord
includes(:metrics)
end
- scope :with_jira_issue_keys, -> { where('title ~ :regex OR merge_requests.description ~ :regex', regex: Gitlab::Regex.jira_issue_key_regex.source) }
+ scope :with_jira_issue_keys, -> { where('title ~ :regex OR merge_requests.description ~ :regex', regex: Gitlab::Regex.jira_issue_key_regex(expression_escape: '\m').source) }
scope :review_requested, -> do
where(reviewers_subquery.exists)
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 1f742279756..09eeaa8386d 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -20,6 +20,7 @@
= render 'groups/invite_members_modal', group: @group
= dispensable_render_if_exists "shared/web_hooks/group_web_hook_disabled_alert"
+= dispensable_render_if_exists "shared/code_suggestions_alert"
= dispensable_render_if_exists "shared/free_user_cap_alert", source: @group
= dispensable_render_if_exists "shared/unlimited_members_during_trial_alert", resource: @group
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 31d02324e68..bb67d8bce14 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -22,6 +22,7 @@
= render 'projects/invite_members_modal', project: @project
= dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert"
+= dispensable_render_if_exists "projects/code_suggestions_alert", project: @project
= dispensable_render_if_exists "projects/free_user_cap_alert", project: @project
= dispensable_render_if_exists 'shared/unlimited_members_during_trial_alert', resource: @project
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index b27fd8ab7d2..1dc24d205d0 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -6,12 +6,12 @@
- if @add_related_issue
.form-group
- .form-check
- = check_box_tag :add_related_issue, @add_related_issue.iid, true, class: 'form-check-input'
- = label_tag :add_related_issue, class: 'form-check-label' do
+ = render Pajamas::CheckboxTagComponent.new(name: :add_related_issue, value: @add_related_issue.iid, checked: true) do |c|
+ = c.label do
- add_related_issue_link = link_to "\##{@add_related_issue.iid}", issue_path(@add_related_issue), class: ['has-tooltip'], title: @add_related_issue.title
#{_('Relate to %{issuable_type} %{add_related_issue_link}').html_safe % { issuable_type: @add_related_issue.issue_type, add_related_issue_link: add_related_issue_link }}
- %p.text-muted= _('Adds this %{issuable_type} as related to the %{issuable_type} it was created from') % { issuable_type: @add_related_issue.issue_type }
+ = c.help_text do
+ = _('Adds this %{issuable_type} as related to the %{issuable_type} it was created from') % { issuable_type: @add_related_issue.issue_type }
- if issuable.respond_to?(:confidential) && can?(current_user, :set_confidentiality, issuable)
.form-group
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 013c4515268..3c8b9bf187f 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -190,6 +190,7 @@ To use an external Prometheus server:
# Rails nodes
gitlab_exporter['listen_address'] = '0.0.0.0'
gitlab_exporter['listen_port'] = '9168'
+ registry['debug_addr'] = '0.0.0.0:5001'
# Sidekiq nodes
sidekiq['listen_address'] = '0.0.0.0'
@@ -205,14 +206,12 @@ To use an external Prometheus server:
# ...
prometheus_listen_addr: '0.0.0.0:9236',
}
+
+ # Pgbouncer nodes
+ pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
```
1. Install and set up a dedicated Prometheus instance, if necessary, using the [official installation instructions](https://prometheus.io/docs/prometheus/latest/installation/).
-1. Add the Prometheus server IP address to the [monitoring IP allowlist](../ip_allowlist.md). For example:
-
- ```ruby
- gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
- ```
1. On **all** GitLab Rails(Puma, Sidekiq) servers, set the Prometheus server IP address and listen port. For example:
@@ -232,6 +231,15 @@ To use an external Prometheus server:
}
```
+1. To allow the Prometheus server to fetch from the [GitLab metrics](#gitlab-metrics) endpoint, add the Prometheus
+server IP address to the [monitoring IP allowlist](../ip_allowlist.md):
+
+ ```ruby
+ gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
+ ```
+
+1. As we are setting each bundled service's [exporter](#bundled-software-metrics) to listen on a network address,
+update the firewall on the instance to only allow traffic from your Prometheus IP for the exporters enabled. A full reference list of exporter services and their respective ports can be found [here](../../package_information/defaults.md#ports).
1. [Reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) to apply the changes.
1. Edit the Prometheus server's configuration file.
1. Add each node's exporters to the Prometheus server's
diff --git a/doc/administration/package_information/defaults.md b/doc/administration/package_information/defaults.md
index 96b56388ea9..ac183afdc2f 100644
--- a/doc/administration/package_information/defaults.md
+++ b/doc/administration/package_information/defaults.md
@@ -53,6 +53,8 @@ by default:
| Gitaly | Yes | Socket | Port (8075) | 8075 or 9999 (TLS) |
| Gitaly exporter | Yes | Port | X | 9236 |
| Praefect | No | Port | X | 2305 or 3305 (TLS) |
+| GitLab Workhorse exporter | Yes | Port | X | 9229 |
+| Registry exporter | No | Port | X | 5001 |
Legend:
diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md
index f4d155369e9..137bc883072 100644
--- a/doc/ci/environments/index.md
+++ b/doc/ci/environments/index.md
@@ -61,6 +61,12 @@ To search environments by name:
- For example when the name is `review/test-app`, search term `test` matches `review/test-app`.
- Also searching with the folder name prefixed like `review/test` matches `review/test-app`.
+## CI/CD variables
+
+To customize your environments and deployments, you can use any of the
+[predefined CI/CD variables](../../ci/variables/predefined_variables.md),
+and define custom CI/CD variables.
+
## Types of environments
An environment is either static or dynamic:
@@ -123,7 +129,8 @@ deploy_staging:
### Create a dynamic environment
-To create a dynamic environment, you use [CI/CD variables](../variables/index.md) that are unique to each pipeline.
+To create a dynamic environment, you use [CI/CD variables](#cicd-variables) that are
+unique to each pipeline.
Prerequisites:
@@ -158,6 +165,79 @@ deploy_review_app:
- main
```
+#### Set a dynamic environment URL
+
+Some external hosting platforms generate a random URL for each deployment, for example:
+`https://94dd65b.amazonaws.com/qa-lambda-1234567`. That makes it difficult to reference the URL in
+the `.gitlab-ci.yml` file.
+
+To address this problem, you can configure a deployment job to report back a set of
+variables. These variables include the URL that was dynamically generated by the external service.
+GitLab supports the [dotenv (`.env`)](https://github.com/bkeepers/dotenv) file format,
+and expands the `environment:url` value with variables defined in the `.env` file.
+
+To use this feature, specify the
+[`artifacts:reports:dotenv`](../yaml/artifacts_reports.md#artifactsreportsdotenv) keyword in `.gitlab-ci.yml`.
+
+You can also specify a static part of the URL at `environment:url`, such as
+`https://$DYNAMIC_ENVIRONMENT_URL`. If the value of `DYNAMIC_ENVIRONMENT_URL` is `example.com`, the
+final result is `https://example.com`.
+
+The assigned URL for the `review/your-branch-name` environment is visible in the UI.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, see [Set dynamic URLs after a job finished](https://youtu.be/70jDXtOf4Ig).
+
+In the following example a review app creates a new environment for each merge request:
+
+- The `review` job is triggered by every push, and creates or updates an environment named
+ `review/your-branch-name`. The environment URL is set to `$DYNAMIC_ENVIRONMENT_URL`.
+- When the `review` job finishes, GitLab updates the `review/your-branch-name` environment's URL.
+ It parses the `deploy.env` report artifact, registers a list of variables as runtime-created,
+ expands the `environment:url: $DYNAMIC_ENVIRONMENT_URL` and sets it to the environment
+ URL.
+
+```yaml
+review:
+ script:
+ - DYNAMIC_ENVIRONMENT_URL=$(deploy-script) # In script, get the environment URL.
+ - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env # Add the value to a dotenv file.
+ artifacts:
+ reports:
+ dotenv: deploy.env # Report back dotenv file to rails.
+ environment:
+ name: review/$CI_COMMIT_REF_SLUG
+ url: $DYNAMIC_ENVIRONMENT_URL # and set the variable produced in script to `environment:url`
+ on_stop: stop_review
+
+stop_review:
+ script:
+ - ./teardown-environment
+ when: manual
+ environment:
+ name: review/$CI_COMMIT_REF_SLUG
+ action: stop
+```
+
+Note the following:
+
+- `stop_review` doesn't generate a dotenv report artifact, so it doesn't recognize the
+ `DYNAMIC_ENVIRONMENT_URL` environment variable. Therefore you shouldn't set `environment:url` in the
+ `stop_review` job.
+- If the environment URL isn't valid (for example, the URL is malformed), the system doesn't update
+ the environment URL.
+- If the script that runs in `stop_review` exists only in your repository and therefore can't use
+ `GIT_STRATEGY: none`, configure [merge request pipelines](../../ci/pipelines/merge_request_pipelines.md)
+ for these jobs. This ensures that runners can fetch the repository even after a feature branch is
+ deleted. For more information, see [Ref Specs for Runners](../pipelines/index.md#ref-specs-for-runners).
+
+NOTE:
+For Windows runners, you should use the PowerShell `Add-Content` command to write to `.env` files.
+
+```powershell
+Add-Content -Path deploy.env -Value "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL"
+```
+
### Rename an environment
> - Renaming an environment by using the UI was [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68550) in GitLab 14.3.
@@ -268,105 +348,6 @@ job page, it's displayed above the job trace:
Learn how to release production changes to only a portion of your Kubernetes pods with
[incremental rollouts](../environments/incremental_rollouts.md).
-## CI/CD variables for environments and deployments
-
-When you create an environment, you specify the name and URL.
-
-If you want to use the name or URL in another job, you can use:
-
-- `$CI_ENVIRONMENT_NAME`. The name defined in the `.gitlab-ci.yml` file.
-- `$CI_ENVIRONMENT_SLUG`. A "cleaned-up" version of the name, suitable for use in URL and DNS, for example.
- This variable is guaranteed to be unique.
-- `$CI_ENVIRONMENT_URL`. The environment's URL, which was specified in the
- `.gitlab-ci.yml` file or automatically assigned.
-
-If you change the name of an existing environment, the:
-
-- `$CI_ENVIRONMENT_NAME` variable is updated with the new environment name.
-- `$CI_ENVIRONMENT_SLUG` variable remains unchanged to prevent unintended side
- effects.
-
-## Set dynamic environment URLs after a job finishes
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17066) in GitLab 12.9.
-
-In a job script, you can specify a static environment URL.
-However, there may be times when you want a dynamic URL. For example,
-if you deploy a Review App to an external hosting
-service that generates a random URL per deployment, like `https://94dd65b.amazonaws.com/qa-lambda-1234567`.
-In this case, you don't know the URL before the deployment script finishes.
-If you want to use the environment URL in GitLab, you would have to update it manually.
-
-To address this problem, you can configure a deployment job to report back a set of
-variables. These variables include the URL that was dynamically-generated by the external service.
-GitLab supports the [dotenv (`.env`)](https://github.com/bkeepers/dotenv) file format,
-and expands the `environment:url` value with variables defined in the `.env` file.
-
-To use this feature, specify the
-[`artifacts:reports:dotenv`](../yaml/artifacts_reports.md#artifactsreportsdotenv) keyword in `.gitlab-ci.yml`.
-
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For an overview, see [Set dynamic URLs after a job finished](https://youtu.be/70jDXtOf4Ig).
-
-### Example of setting dynamic environment URLs
-
-The following example shows a Review App that creates a new environment
-for each merge request. The `review` job is triggered by every push, and
-creates or updates an environment named `review/your-branch-name`.
-The environment URL is set to `$DYNAMIC_ENVIRONMENT_URL`:
-
-```yaml
-review:
- script:
- - DYNAMIC_ENVIRONMENT_URL=$(deploy-script) # In script, get the environment URL.
- - echo "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL" >> deploy.env # Add the value to a dotenv file.
- artifacts:
- reports:
- dotenv: deploy.env # Report back dotenv file to rails.
- environment:
- name: review/$CI_COMMIT_REF_SLUG
- url: $DYNAMIC_ENVIRONMENT_URL # and set the variable produced in script to `environment:url`
- on_stop: stop_review
-
-stop_review:
- script:
- - ./teardown-environment
- when: manual
- environment:
- name: review/$CI_COMMIT_REF_SLUG
- action: stop
-```
-
-As soon as the `review` job finishes, GitLab updates the `review/your-branch-name`
-environment's URL.
-It parses the `deploy.env` report artifact, registers a list of variables as runtime-created,
-uses it for expanding `environment:url: $DYNAMIC_ENVIRONMENT_URL` and sets it to the environment URL.
-You can also specify a static part of the URL at `environment:url`, such as
-`https://$DYNAMIC_ENVIRONMENT_URL`. If the value of `DYNAMIC_ENVIRONMENT_URL` is
-`example.com`, the final result is `https://example.com`.
-
-The assigned URL for the `review/your-branch-name` environment is visible in the UI.
-
-Note the following:
-
-- `stop_review` doesn't generate a dotenv report artifact, so it doesn't recognize the
- `DYNAMIC_ENVIRONMENT_URL` environment variable. Therefore you shouldn't set `environment:url` in the
- `stop_review` job.
-- If the environment URL isn't valid (for example, the URL is malformed), the system doesn't update
- the environment URL.
-- If the script that runs in `stop_review` exists only in your repository and therefore can't use
- `GIT_STRATEGY: none`, configure [merge request pipelines](../../ci/pipelines/merge_request_pipelines.md)
- for these jobs. This ensures that runners can fetch the repository even after a feature branch is
- deleted. For more information, see [Ref Specs for Runners](../pipelines/index.md#ref-specs-for-runners).
-
-NOTE:
-For Windows runners, using `echo` to write to `.env` files may fail. Using the PowerShell `Add-Content`command
-helps in such cases. For example:
-
-```powershell
-Add-Content -Path deploy.env -Value "DYNAMIC_ENVIRONMENT_URL=$DYNAMIC_ENVIRONMENT_URL"
-```
-
## Track newly included merge requests per deployment
GitLab can track newly included merge requests per deployment.
diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md
index 78c9e98c33f..37cb7efdf94 100644
--- a/doc/ci/yaml/artifacts_reports.md
+++ b/doc/ci/yaml/artifacts_reports.md
@@ -189,7 +189,7 @@ GitLab can display the results of one or more reports in:
The `dotenv` report collects a set of environment variables as artifacts.
The collected variables are registered as runtime-created variables of the job,
-which you can use to [set dynamic environment URLs after a job finishes](../environments/index.md#set-dynamic-environment-urls-after-a-job-finishes).
+which you can use to [set dynamic environment URLs after a job finishes](../environments/index.md#set-a-dynamic-environment-url).
If duplicate environment variables are present in a `dotenv` report:
diff --git a/doc/development/vs_code_debugging.md b/doc/development/vs_code_debugging.md
index 08aa4688bfd..129eddf853b 100644
--- a/doc/development/vs_code_debugging.md
+++ b/doc/development/vs_code_debugging.md
@@ -6,12 +6,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# VS Code debugging
-This document describes how to set up Rails debugging in [VS Code](https://code.visualstudio.com/).
+This document describes how to set up Rails debugging in [Visual Studio Code (VSCode)](https://code.visualstudio.com/) using the [GitLab Development Kit (GDK)](contributing/first_contribution.md#step-1-configure-the-gitlab-development-kit).
## Setup
1. Install the `debug` gem by running `gem install debug` inside your `gitlab` folder.
-1. Add the following configuration to your `.vscode/tasks.json` file:
+1. Install the [VSCode Ruby rdbg Debugger](https://marketplace.visualstudio.com/items?itemName=KoichiSasada.vscode-rdbg) extension to add support for the `rdbg` debugger type to VSCode.
+1. In case you want to automatically stop and start GitLab and its associated Ruby Rails server, you may add the following VSCode task to your configuration under the `.vscode/tasks.json` file:
```json
{
@@ -20,7 +21,7 @@ This document describes how to set up Rails debugging in [VS Code](https://code.
{
"label": "start rdbg",
"type": "shell",
- "command": "gdk stop rails-web && GITLAB_RAILS_RACK_TIMEOUT_ENABLE_LOGGING=false PUMA_SINGLE_MODE=true rdbg --open -c -- bin/rails s",
+ "command": "gdk stop rails-web && GITLAB_RAILS_RACK_TIMEOUT_ENABLE_LOGGING=false PUMA_SINGLE_MODE=true rdbg --open -c bin/rails server",
"isBackground": true,
"problemMatcher": {
"owner": "rails",
@@ -42,26 +43,29 @@ This document describes how to set up Rails debugging in [VS Code](https://code.
```json
{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, see https://go.microsoft.com/fwlink/?linkid=830387.
"version": "0.2.0",
"configurations": [
{
"type": "rdbg",
"name": "Attach with rdbg",
"request": "attach",
+
+ // remove the following "preLaunchTask" if you do not wish to stop and start
+ // GitLab via VS Code but manually on a separate terminal.
"preLaunchTask": "start rdbg"
}
]
}
```
+WARNING:
+The VSCode Ruby extension might have issues finding the correct Ruby installation and the appropriate `rdbg` command. In this case, add `"rdbgPath": "/home/user/.asdf/shims/` (in the case of asdf) to the launch configuration above.
+
## Debugging
-Prerequisite:
+### Prerequisites
-- You must have a running GDK instance.
+- You must have a running [GDK](contributing/first_contribution.md#step-1-configure-the-gitlab-development-kit) instance.
To start debugging, do one of the following:
diff --git a/doc/user/project/merge_requests/widgets.md b/doc/user/project/merge_requests/widgets.md
index 2c87aa4e1dd..639552ca75a 100644
--- a/doc/user/project/merge_requests/widgets.md
+++ b/doc/user/project/merge_requests/widgets.md
@@ -65,7 +65,7 @@ faster to preview proposed modifications.
## License compliance **(ULTIMATE)**
-If you have configured [License Compliance](../../compliance/license_compliance/index.md) for your project, then you can view a list of licenses that are detected for your project's dependencies.
+If you have configured [License Compliance](../../compliance/license_scanning_of_cyclonedx_files/index.md) for your project, then you can view a list of licenses that are detected for your project's dependencies.
![Merge request pipeline](img/license_compliance_widget_v15_3.png)
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index c7c41ee8558..8c2bcb250a9 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -81,6 +81,31 @@ Administrators can set a default branch protection level in the
Configure protected branches for all projects in a group, or just for a project.
+### For a project
+
+Prerequisites:
+
+- You must have at least the Maintainer role.
+- When granting a group **Allowed to merge** or **Allowed to push and merge** permissions
+ on a protected branch, the group must be added to the project.
+
+To protect a branch:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Repository**.
+1. Expand **Protected branches**.
+1. From the **Branch** dropdown list, select the branch you want to protect.
+1. From the **Allowed to merge** list, select a role that can merge into this branch.
+1. From the **Allowed to push and merge** list, select a role that can push to this branch.
+
+ NOTE:
+ In GitLab Premium and Ultimate, you can also add groups or individual users
+ to **Allowed to merge** and **Allowed to push and merge**.
+
+1. Select **Protect**.
+
+The protected branch displays in the list of protected branches.
+
### For all projects in a group **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106532) in GitLab 15.9 behind a feature flag, disabled by default.
@@ -108,28 +133,6 @@ To protect a branch for all the projects in a group:
The protected branch is added to the list of protected branches.
-### For a project
-
-Prerequisite:
-
-- You must have at least the Maintainer role.
-- When granting a group **Allowed to merge** or **Allowed to push and merge** permissions
- on a protected branch, the group must be added to the project.
-
-To protect a branch:
-
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > Repository**.
-1. Expand **Protected branches**.
-1. From the **Branch** dropdown list, select the branch you want to protect.
-1. From the **Allowed to merge** list, select a role that can merge into this branch.
- In GitLab Premium and Ultimate, you can also add groups or individual users.
-1. From the **Allowed to push and merge** list, select a role that can push to this branch.
- In GitLab Premium and Ultimate, you can also add groups or individual users.
-1. Select **Protect**.
-
-The protected branch displays in the list of protected branches.
-
## Configure multiple protected branches by using a wildcard
If both a specific rule and a wildcard rule apply to the same branch, the most
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index ac9f990a598..c8a02366e19 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -40,7 +40,11 @@ Prerequisites:
1. Enter the string to use for tag matching. Wildcards (`*`) are supported.
1. Select **Create wildcard**.
1. In **Allowed to create** , select roles that may create protected tags.
- In GitLab Premium and Ultimate, you can also select groups or individual users.
+
+ NOTE:
+ In GitLab Premium and Ultimate, you can also add groups or individual users
+ to **Allowed to create**.
+
1. Select **Protect**.
The protected tag (or wildcard) displays in the **Protected tags** list.
diff --git a/doc/user/project/repository/code_suggestions.md b/doc/user/project/repository/code_suggestions.md
index c3d29261f07..2c20ebbe08b 100644
--- a/doc/user/project/repository/code_suggestions.md
+++ b/doc/user/project/repository/code_suggestions.md
@@ -64,7 +64,7 @@ Each user can enable Code Suggestions for themselves:
1. On the top bar, in the upper-right corner, select your avatar.
1. On the left sidebar, select **Preferences**.
-1. In the **Code Suggestions** section, enable the setting.
+1. In the **Code Suggestions** section, select **Enable Code Suggestions**.
NOTE:
If Code Suggestions is [enabled for the group](../../group/manage.md#group-code-suggestions), the group setting overrides the user setting.
@@ -111,6 +111,21 @@ Start typing and receive suggestions for your GitLab projects.
<iframe src="https://www.youtube-nocookie.com/embed/WnxBYxN2-p4" frameborder="0" allowfullscreen> </iframe>
</figure>
+## Why can't I see any code suggestions?
+
+If Code Suggestions are not displayed, try the following troubleshooting steps.
+
+In GitLab, ensure Code Suggestions is enabled:
+
+- [For your user account](#enable-code-suggestions-for-an-individual-user).
+- [For *all* top-level groups your account belongs to](../../group/manage.md#group-code-suggestions). If you don't have a role that lets you view the top-level group's settings, contact a group owner.
+
+In VS Code:
+
+- Ensure [your IDE is configured properly](#enable-code-suggestions-in-vs-code).
+
+To confirm that your account is enabled, go to [https://gitlab.com/api/v4/ml/ai-assist](https://gitlab.com/api/v4/ml/ai-assist). A response of `user_is_allowed` should return `true`.
+
## Stability and performance
This feature is currently in [Beta](/ee/policy/alpha-beta-support.md#beta).
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index 7d8c37cd39b..7348ed612fc 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -29,10 +29,9 @@ module API
feature_category :integrations
before do
- reversible_end_of_life!
-
authorize_jira_user_agent!(request)
authenticate!
+ reversible_end_of_life!
end
helpers do
@@ -50,6 +49,13 @@ module API
# TODO Make the breaking change irreversible https://gitlab.com/gitlab-org/gitlab/-/issues/408148.
def reversible_end_of_life!
not_found! unless Feature.enabled?(:jira_dvcs_end_of_life_amnesty)
+
+ Gitlab::IntegrationsLogger.info(
+ user_id: current_user&.id,
+ namespace: params[:namespace],
+ project: params[:project],
+ message: 'Deprecated Jira DVCS endpoint request'
+ )
end
def authorize_jira_user_agent!(request)
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index eb99805e2e8..79f12ee13f7 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -548,8 +548,8 @@ module Gitlab
# https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html
# Avoids linking CVE IDs (https://cve.mitre.org/cve/identifiers/syntaxchange.html#new) as Jira issues.
# CVE IDs use the format of CVE-YYYY-NNNNNNN
- def jira_issue_key_regex
- @jira_issue_key_regex ||= /(?!CVE-\d+-\d+)[A-Z][A-Z_0-9]+-\d+/
+ def jira_issue_key_regex(expression_escape: '\b')
+ /#{expression_escape}(?!CVE-\d+-\d+)[A-Z][A-Z_0-9]+-\d+/
end
def jira_issue_key_project_key_extraction_regex
diff --git a/lib/sidebars/groups/menus/packages_registries_menu.rb b/lib/sidebars/groups/menus/packages_registries_menu.rb
index 68c8e9675a7..bd1cca6473a 100644
--- a/lib/sidebars/groups/menus/packages_registries_menu.rb
+++ b/lib/sidebars/groups/menus/packages_registries_menu.rb
@@ -36,7 +36,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Package Registry'),
link: group_packages_path(context.group),
- super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::OperationsMenu,
+ super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::DeployMenu,
active_routes: { controller: 'groups/packages' },
item_id: :packages_registry
)
diff --git a/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb b/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb
new file mode 100644
index 00000000000..fe9cc5280c7
--- /dev/null
+++ b/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Groups
+ module SuperSidebarMenus
+ class DeployMenu < ::Sidebars::Menu
+ override :title
+ def title
+ s_('Navigation|Deploy')
+ end
+
+ override :sprite_icon
+ def sprite_icon
+ 'deployments'
+ end
+
+ override :configure_menu_items
+ def configure_menu_items
+ [
+ :packages_registry
+ ].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb b/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
index fe17ada69e4..e716801486e 100644
--- a/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
+++ b/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
@@ -11,14 +11,13 @@ module Sidebars
override :sprite_icon
def sprite_icon
- 'deployments'
+ 'cloud-pod'
end
override :configure_menu_items
def configure_menu_items
[
:dependency_proxy,
- :packages_registry,
:container_registry,
:group_kubernetes_clusters
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
diff --git a/lib/sidebars/groups/super_sidebar_panel.rb b/lib/sidebars/groups/super_sidebar_panel.rb
index 03af904d99d..d1000ef0868 100644
--- a/lib/sidebars/groups/super_sidebar_panel.rb
+++ b/lib/sidebars/groups/super_sidebar_panel.rb
@@ -17,6 +17,7 @@ module Sidebars
add_menu(Sidebars::Groups::SuperSidebarMenus::PlanMenu.new(context))
add_menu(Sidebars::Groups::SuperSidebarMenus::CodeMenu.new(context))
add_menu(Sidebars::Groups::SuperSidebarMenus::BuildMenu.new(context))
+ add_menu(Sidebars::Groups::SuperSidebarMenus::DeployMenu.new(context))
add_menu(Sidebars::Groups::SuperSidebarMenus::SecureMenu.new(context))
add_menu(Sidebars::Groups::SuperSidebarMenus::OperationsMenu.new(context))
add_menu(Sidebars::Groups::SuperSidebarMenus::MonitorMenu.new(context))
diff --git a/lib/sidebars/projects/menus/deployments_menu.rb b/lib/sidebars/projects/menus/deployments_menu.rb
index 19612fcee85..0a411f075b7 100644
--- a/lib/sidebars/projects/menus/deployments_menu.rb
+++ b/lib/sidebars/projects/menus/deployments_menu.rb
@@ -49,7 +49,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: s_('FeatureFlags|Feature flags'),
link: project_feature_flags_path(context.project),
- super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::BuildMenu,
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::DeployMenu,
active_routes: { controller: :feature_flags },
container_html_options: { class: 'shortcuts-feature-flags' },
item_id: :feature_flags
@@ -64,7 +64,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Environments'),
link: project_environments_path(context.project),
- super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::BuildMenu,
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::OperationsMenu,
active_routes: { controller: :environments },
container_html_options: { class: 'shortcuts-environments' },
item_id: :environments
@@ -80,7 +80,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Releases'),
link: project_releases_path(context.project),
- super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::BuildMenu,
+ super_sidebar_parent: ::Sidebars::Projects::SuperSidebarMenus::DeployMenu,
item_id: :releases,
active_routes: { controller: :releases },
container_html_options: { class: 'shortcuts-deployments-releases' }
diff --git a/lib/sidebars/projects/menus/packages_registries_menu.rb b/lib/sidebars/projects/menus/packages_registries_menu.rb
index 31a1aa56ab5..39adbdce63b 100644
--- a/lib/sidebars/projects/menus/packages_registries_menu.rb
+++ b/lib/sidebars/projects/menus/packages_registries_menu.rb
@@ -39,7 +39,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Package Registry'),
link: project_packages_path(context.project),
- super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::OperationsMenu,
+ super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::DeployMenu,
active_routes: { controller: :packages },
item_id: :packages_registry,
container_html_options: { class: 'shortcuts-container-registry' }
@@ -54,7 +54,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Container Registry'),
link: project_container_registry_index_path(context.project),
- super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::OperationsMenu,
+ super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::DeployMenu,
active_routes: { controller: 'projects/registry/repositories' },
item_id: :container_registry
)
@@ -98,7 +98,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Model experiments'),
link: project_ml_experiments_path(context.project),
- super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::AnalyzeMenu,
+ super_sidebar_parent: Sidebars::Projects::SuperSidebarMenus::DeployMenu,
active_routes: { controller: %w[projects/ml/experiments projects/ml/candidates] },
item_id: :model_experiments
)
diff --git a/lib/sidebars/projects/super_sidebar_menus/analyze_menu.rb b/lib/sidebars/projects/super_sidebar_menus/analyze_menu.rb
index 58b231a269c..2c5dc8a08e7 100644
--- a/lib/sidebars/projects/super_sidebar_menus/analyze_menu.rb
+++ b/lib/sidebars/projects/super_sidebar_menus/analyze_menu.rb
@@ -25,8 +25,7 @@ module Sidebars
:code_review,
:merge_request_analytics,
:issues,
- :insights,
- :model_experiments
+ :insights
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
end
end
diff --git a/lib/sidebars/projects/super_sidebar_menus/build_menu.rb b/lib/sidebars/projects/super_sidebar_menus/build_menu.rb
index 30603e1deeb..119ddf28873 100644
--- a/lib/sidebars/projects/super_sidebar_menus/build_menu.rb
+++ b/lib/sidebars/projects/super_sidebar_menus/build_menu.rb
@@ -20,10 +20,7 @@ module Sidebars
:pipelines,
:jobs,
:pipelines_editor,
- :releases,
- :environments,
:pipeline_schedules,
- :feature_flags,
:test_cases,
:artifacts
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
diff --git a/lib/sidebars/projects/super_sidebar_menus/deploy_menu.rb b/lib/sidebars/projects/super_sidebar_menus/deploy_menu.rb
new file mode 100644
index 00000000000..49aa6a23a0e
--- /dev/null
+++ b/lib/sidebars/projects/super_sidebar_menus/deploy_menu.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Projects
+ module SuperSidebarMenus
+ class DeployMenu < ::Sidebars::Menu
+ override :title
+ def title
+ s_('Navigation|Deploy')
+ end
+
+ override :sprite_icon
+ def sprite_icon
+ 'deployments'
+ end
+
+ override :configure_menu_items
+ def configure_menu_items
+ [
+ :releases,
+ :feature_flags,
+ :packages_registry,
+ :container_registry,
+ :model_experiments
+ ].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/projects/super_sidebar_menus/operations_menu.rb b/lib/sidebars/projects/super_sidebar_menus/operations_menu.rb
index 64cf4aee9c5..85d60c0bad3 100644
--- a/lib/sidebars/projects/super_sidebar_menus/operations_menu.rb
+++ b/lib/sidebars/projects/super_sidebar_menus/operations_menu.rb
@@ -11,14 +11,13 @@ module Sidebars
override :sprite_icon
def sprite_icon
- 'deployments'
+ 'cloud-pod'
end
override :configure_menu_items
def configure_menu_items
[
- :packages_registry,
- :container_registry,
+ :environments,
:kubernetes,
:terraform_states,
:infrastructure_registry,
diff --git a/lib/sidebars/projects/super_sidebar_panel.rb b/lib/sidebars/projects/super_sidebar_panel.rb
index 640666fd968..c3dfd9804af 100644
--- a/lib/sidebars/projects/super_sidebar_panel.rb
+++ b/lib/sidebars/projects/super_sidebar_panel.rb
@@ -17,6 +17,7 @@ module Sidebars
add_menu(Sidebars::Projects::SuperSidebarMenus::PlanMenu.new(context))
add_menu(Sidebars::Projects::SuperSidebarMenus::CodeMenu.new(context))
add_menu(Sidebars::Projects::SuperSidebarMenus::BuildMenu.new(context))
+ add_menu(Sidebars::Projects::SuperSidebarMenus::DeployMenu.new(context))
add_menu(Sidebars::Projects::SuperSidebarMenus::SecureMenu.new(context))
add_menu(Sidebars::Projects::SuperSidebarMenus::OperationsMenu.new(context))
add_menu(Sidebars::Projects::SuperSidebarMenus::MonitorMenu.new(context))
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7a76d7ebb42..e445147d38f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10862,6 +10862,15 @@ msgstr ""
msgid "CodeQuality|New code quality degradations on this line"
msgstr ""
+msgid "CodeSuggestionsAlert|Code faster and more efficiently with AI-powered code suggestions in VS Code. 13 languages are supported, including Javascript, Python, Go, Java, and Kotlin. Enable Code Suggestions in your %{preference_link_start}user profile preferences%{link_end} or %{docs_link_start}see the documentation%{link_end} to learn more."
+msgstr ""
+
+msgid "CodeSuggestionsAlert|Enable Code Suggestions"
+msgstr ""
+
+msgid "CodeSuggestionsAlert|Get started with Code Suggestions, available for free during the beta period."
+msgstr ""
+
msgid "CodeSuggestions|%{link_start}What are code suggestions?%{link_end}"
msgstr ""
@@ -27707,6 +27716,9 @@ msgstr ""
msgid "MemberRole|maximum number of Member Roles are already in use by the group hierarchy. Please delete an existing Member Role."
msgstr ""
+msgid "MemberRole|minimal base access level must be %{min_access_level}."
+msgstr ""
+
msgid "MemberRole|must be top-level namespace"
msgstr ""
@@ -29514,6 +29526,9 @@ msgstr ""
msgid "Navigation|Context navigation"
msgstr ""
+msgid "Navigation|Deploy"
+msgstr ""
+
msgid "Navigation|Enter admin mode"
msgstr ""
@@ -32141,6 +32156,9 @@ msgstr ""
msgid "PackageRegistry|Something went wrong while deleting the package."
msgstr ""
+msgid "PackageRegistry|Something went wrong while fetching package assets."
+msgstr ""
+
msgid "PackageRegistry|Something went wrong while fetching the package history."
msgstr ""
diff --git a/spec/features/nav/pinned_nav_items_spec.rb b/spec/features/nav/pinned_nav_items_spec.rb
index 308350d5166..cf53e0a322a 100644
--- a/spec/features/nav/pinned_nav_items_spec.rb
+++ b/spec/features/nav/pinned_nav_items_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio
before do
within '#super-sidebar' do
click_on 'Operate'
- add_pin('Package Registry')
+ add_pin('Terraform states')
add_pin('Terraform modules')
wait_for_requests
end
@@ -97,8 +97,8 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio
it 'can be unpinned from within the pinned section' do
within '[data-testid="pinned-nav-items"]' do
- remove_pin('Package Registry')
- expect(page).not_to have_content 'Package Registry'
+ remove_pin('Terraform states')
+ expect(page).not_to have_content 'Terraform states'
end
end
@@ -117,7 +117,7 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio
it 'can be reordered' do
within '[data-testid="pinned-nav-items"]' do
pinned_items = page.find_all('a').map(&:text)
- item2 = page.find('a', text: 'Package Registry')
+ item2 = page.find('a', text: 'Terraform states')
item3 = page.find('a', text: 'Terraform modules')
expect(pinned_items[1..2]).to eq [item2.text, item3.text]
drag_item(item3, to: item2)
diff --git a/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
index c435dd57de2..88d4398aa70 100644
--- a/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
@@ -24,7 +24,7 @@ describe('RunnerStatusCell', () => {
propsData: {
runner: {
runnerType: INSTANCE_TYPE,
- active: true,
+ paused: false,
status: STATUS_ONLINE,
jobExecutionStatus: JOB_STATUS_IDLE,
...runner,
@@ -59,7 +59,7 @@ describe('RunnerStatusCell', () => {
it('Displays paused status', () => {
createComponent({
runner: {
- active: false,
+ paused: true,
status: STATUS_ONLINE,
},
});
diff --git a/spec/frontend/ci/runner/components/runner_delete_button_spec.js b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
index 3123f2894fb..3b3f3b1770d 100644
--- a/spec/frontend/ci/runner/components/runner_delete_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
@@ -236,7 +236,7 @@ describe('RunnerDeleteButton', () => {
createComponent({
props: {
runner: {
- active: true,
+ paused: false,
},
compact: true,
},
diff --git a/spec/frontend/ci/runner/components/runner_list_spec.js b/spec/frontend/ci/runner/components/runner_list_spec.js
index 0f4ec717c3e..9da640afeb7 100644
--- a/spec/frontend/ci/runner/components/runner_list_spec.js
+++ b/spec/frontend/ci/runner/components/runner_list_spec.js
@@ -18,7 +18,6 @@ import { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/ci/runner/cons
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = allRunnersData.data.runners.nodes;
-const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {
let wrapper;
@@ -44,7 +43,6 @@ describe('RunnerList', () => {
apolloProvider: createMockApollo([], {}, cacheConfig),
propsData: {
runners: mockRunners,
- activeRunnersCount: mockActiveRunnersCount,
...props,
},
provide: {
diff --git a/spec/frontend/ci/runner/components/runner_pause_button_spec.js b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
index 350d029f3fc..1ea870e004a 100644
--- a/spec/frontend/ci/runner/components/runner_pause_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
-import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
+import runnerTogglePausedMutation from '~/ci/runner/graphql/shared/runner_toggle_paused.mutation.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/ci/runner/sentry_utils';
import { createAlert } from '~/alert';
@@ -27,7 +27,7 @@ jest.mock('~/ci/runner/sentry_utils');
describe('RunnerPauseButton', () => {
let wrapper;
- let runnerToggleActiveHandler;
+ let runnerTogglePausedHandler;
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value;
const findBtn = () => wrapper.findComponent(GlButton);
@@ -39,12 +39,12 @@ describe('RunnerPauseButton', () => {
propsData: {
runner: {
id: mockRunner.id,
- active: mockRunner.active,
+ paused: mockRunner.paused,
...runner,
},
...propsData,
},
- apolloProvider: createMockApollo([[runnerToggleActiveMutation, runnerToggleActiveHandler]]),
+ apolloProvider: createMockApollo([[runnerTogglePausedMutation, runnerTogglePausedHandler]]),
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
@@ -57,13 +57,13 @@ describe('RunnerPauseButton', () => {
};
beforeEach(() => {
- runnerToggleActiveHandler = jest.fn().mockImplementation(({ input }) => {
+ runnerTogglePausedHandler = jest.fn().mockImplementation(({ input }) => {
return Promise.resolve({
data: {
runnerUpdate: {
runner: {
id: input.id,
- active: input.active,
+ paused: !input.paused,
},
errors: [],
},
@@ -76,15 +76,15 @@ describe('RunnerPauseButton', () => {
describe('Pause/Resume action', () => {
describe.each`
- runnerState | icon | content | tooltip | isActive | newActiveValue
- ${'paused'} | ${'play'} | ${I18N_RESUME} | ${I18N_RESUME_TOOLTIP} | ${false} | ${true}
- ${'active'} | ${'pause'} | ${I18N_PAUSE} | ${I18N_PAUSE_TOOLTIP} | ${true} | ${false}
- `('When the runner is $runnerState', ({ icon, content, tooltip, isActive, newActiveValue }) => {
+ runnerState | icon | content | tooltip | isPaused | newPausedValue
+ ${'paused'} | ${'play'} | ${I18N_RESUME} | ${I18N_RESUME_TOOLTIP} | ${true} | ${false}
+ ${'active'} | ${'pause'} | ${I18N_PAUSE} | ${I18N_PAUSE_TOOLTIP} | ${false} | ${true}
+ `('When the runner is $runnerState', ({ icon, content, tooltip, isPaused, newPausedValue }) => {
beforeEach(() => {
createComponent({
props: {
runner: {
- active: isActive,
+ paused: isPaused,
},
},
});
@@ -106,7 +106,7 @@ describe('RunnerPauseButton', () => {
describe(`Before the ${icon} button is clicked`, () => {
it('The mutation has not been called', () => {
- expect(runnerToggleActiveHandler).toHaveBeenCalledTimes(0);
+ expect(runnerTogglePausedHandler).not.toHaveBeenCalled();
});
});
@@ -134,12 +134,12 @@ describe('RunnerPauseButton', () => {
await clickAndWait();
});
- it(`The mutation to that sets active to ${newActiveValue} is called`, () => {
- expect(runnerToggleActiveHandler).toHaveBeenCalledTimes(1);
- expect(runnerToggleActiveHandler).toHaveBeenCalledWith({
+ it(`The mutation to that sets "paused" to ${newPausedValue} is called`, () => {
+ expect(runnerTogglePausedHandler).toHaveBeenCalledTimes(1);
+ expect(runnerTogglePausedHandler).toHaveBeenCalledWith({
input: {
id: mockRunner.id,
- active: newActiveValue,
+ paused: newPausedValue,
},
});
});
@@ -158,7 +158,7 @@ describe('RunnerPauseButton', () => {
const mockErrorMsg = 'Update error!';
beforeEach(async () => {
- runnerToggleActiveHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
+ runnerTogglePausedHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
await clickAndWait();
});
@@ -180,12 +180,12 @@ describe('RunnerPauseButton', () => {
const mockErrorMsg2 = 'User not allowed!';
beforeEach(async () => {
- runnerToggleActiveHandler.mockResolvedValueOnce({
+ runnerTogglePausedHandler.mockResolvedValueOnce({
data: {
runnerUpdate: {
runner: {
id: mockRunner.id,
- active: isActive,
+ paused: isPaused,
},
errors: [mockErrorMsg, mockErrorMsg2],
},
@@ -215,7 +215,7 @@ describe('RunnerPauseButton', () => {
createComponent({
props: {
runner: {
- active: true,
+ paused: false,
},
compact: true,
},
diff --git a/spec/frontend/ci/runner/components/runner_update_form_spec.js b/spec/frontend/ci/runner/components/runner_update_form_spec.js
index ee37d6241b5..d1d4e38f47c 100644
--- a/spec/frontend/ci/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/ci/runner/components/runner_update_form_spec.js
@@ -56,7 +56,7 @@ describe('RunnerUpdateForm', () => {
const submitFormAndWait = () => submitForm().then(waitForPromises);
const getFieldsModel = () => ({
- active: !findPausedCheckbox().element.checked,
+ paused: findPausedCheckbox().element.checked,
accessLevel: findProtectedCheckbox().element.checked
? ACCESS_LEVEL_REF_PROTECTED
: ACCESS_LEVEL_NOT_PROTECTED,
@@ -179,8 +179,8 @@ describe('RunnerUpdateForm', () => {
describe('On submit, runner gets updated', () => {
it.each`
test | initialValue | findCheckbox | checked | submitted
- ${'pauses'} | ${{ active: true }} | ${findPausedCheckbox} | ${true} | ${{ active: false }}
- ${'activates'} | ${{ active: false }} | ${findPausedCheckbox} | ${false} | ${{ active: true }}
+ ${'pauses'} | ${{ paused: false }} | ${findPausedCheckbox} | ${true} | ${{ paused: true }}
+ ${'activates'} | ${{ paused: true }} | ${findPausedCheckbox} | ${false} | ${{ paused: false }}
${'unprotects'} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }} | ${findProtectedCheckbox} | ${true} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }}
${'protects'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }} | ${findProtectedCheckbox} | ${false} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }}
${'"runs untagged jobs"'} | ${{ runUntagged: true }} | ${findRunUntaggedCheckbox} | ${false} | ${{ runUntagged: false }}
diff --git a/spec/frontend/ci/runner/runner_update_form_utils_spec.js b/spec/frontend/ci/runner/runner_update_form_utils_spec.js
index b2f7bbc49a9..80c492bb431 100644
--- a/spec/frontend/ci/runner/runner_update_form_utils_spec.js
+++ b/spec/frontend/ci/runner/runner_update_form_utils_spec.js
@@ -12,7 +12,7 @@ const mockRunner = {
description: mockDescription,
maximumTimeout: 100,
accessLevel: ACCESS_LEVEL_NOT_PROTECTED,
- active: true,
+ paused: false,
locked: true,
runUntagged: true,
tagList: ['tag-1', 'tag-2'],
@@ -79,7 +79,7 @@ describe('~/ci/runner/runner_update_form_utils', () => {
${',,,,, commas'} | ${['commas']}
${'more ,,,,, commas'} | ${['more', 'commas']}
${' trimmed , trimmed2 '} | ${['trimmed', 'trimmed2']}
- `('collect tags separated by commas for "$value"', ({ tagList, tagListInput }) => {
+ `('collect comma-separated tags "$tagList" as $tagListInput', ({ tagList, tagListInput }) => {
const variables = modelToUpdateMutationVariables({
...mockModel,
tagList,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
index 1dcac017ccf..5c36dbf9c9c 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
@@ -1,22 +1,34 @@
-import { GlDropdown, GlButton, GlFormCheckbox } from '@gitlab/ui';
-import { nextTick } from 'vue';
+import { GlAlert, GlDropdown, GlButton, GlFormCheckbox, GlLoadingIcon } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import { stubComponent } from 'helpers/stub_component';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { packageFiles as packageFilesMock } from 'jest/packages_and_registries/package_registry/mock_data';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { s__ } from '~/locale';
+import {
+ packageFiles as packageFilesMock,
+ packageFilesQuery,
+} from 'jest/packages_and_registries/package_registry/mock_data';
import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import getPackageFiles from '~/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql';
+
+Vue.use(VueApollo);
+
describe('Package Files', () => {
let wrapper;
+ let apolloProvider;
const findAllRows = () => wrapper.findAllByTestId('file-row');
const findDeleteSelectedButton = () => wrapper.findByTestId('delete-selected');
const findFirstRow = () => extendedWrapper(findAllRows().at(0));
const findSecondRow = () => extendedWrapper(findAllRows().at(1));
+ const findPackageFilesAlert = () => wrapper.findComponent(GlAlert);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findFirstRowDownloadLink = () => findFirstRow().findByTestId('download-link');
- const findFirstRowCommitLink = () => findFirstRow().findByTestId('commit-link');
- const findSecondRowCommitLink = () => findSecondRow().findByTestId('commit-link');
const findFirstRowFileIcon = () => findFirstRow().findComponent(FileIcon);
const findFirstRowCreatedAt = () => findFirstRow().findComponent(TimeAgoTooltip);
const findFirstActionMenu = () => extendedWrapper(findFirstRow().findComponent(GlDropdown));
@@ -30,16 +42,23 @@ describe('Package Files', () => {
const [file] = files;
const createComponent = ({
- packageFiles = [file],
+ packageId = '1',
+ packageType = 'NPM',
isLoading = false,
canDelete = true,
stubs,
+ resolver = jest.fn().mockResolvedValue(packageFilesQuery([file])),
} = {}) => {
+ const requestHandlers = [[getPackageFiles, resolver]];
+ apolloProvider = createMockApollo(requestHandlers);
+
wrapper = mountExtended(PackageFiles, {
+ apolloProvider,
propsData: {
canDelete,
isLoading,
- packageFiles,
+ packageId,
+ packageType,
},
stubs: {
GlTable: false,
@@ -49,35 +68,61 @@ describe('Package Files', () => {
};
describe('rows', () => {
- it('renders a single file for an npm package', () => {
+ it('do not get rendered when query is loading', () => {
createComponent();
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findDeleteSelectedButton().props('disabled')).toBe(true);
+ });
+
+ it('renders a single file for an npm package', async () => {
+ createComponent();
+ await waitForPromises();
+
expect(findAllRows()).toHaveLength(1);
+ expect(findLoadingIcon().exists()).toBe(false);
});
- it('renders multiple files for a package that contains more than one file', () => {
- createComponent({ packageFiles: files });
+ it('renders multiple files for a package that contains more than one file', async () => {
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery()) });
+ await waitForPromises();
expect(findAllRows()).toHaveLength(2);
});
+
+ it('does not render gl-alert', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(findPackageFilesAlert().exists()).toBe(false);
+ });
+
+ it('renders gl-alert if load fails', async () => {
+ createComponent({ resolver: jest.fn().mockRejectedValue() });
+ await waitForPromises();
+
+ expect(findPackageFilesAlert().exists()).toBe(true);
+ expect(findPackageFilesAlert().text()).toBe(
+ s__('PackageRegistry|Something went wrong while fetching package assets.'),
+ );
+ });
});
describe('link', () => {
- it('exists', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
+ it('exists', () => {
expect(findFirstRowDownloadLink().exists()).toBe(true);
});
it('has the correct attrs bound', () => {
- createComponent();
-
expect(findFirstRowDownloadLink().attributes('href')).toBe(file.downloadPath);
});
it('emits "download-file" event on click', () => {
- createComponent();
-
findFirstRowDownloadLink().vm.$emit('click');
expect(wrapper.emitted('download-file')).toEqual([[]]);
@@ -85,90 +130,43 @@ describe('Package Files', () => {
});
describe('file-icon', () => {
- it('exists', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
+ it('exists', () => {
expect(findFirstRowFileIcon().exists()).toBe(true);
});
it('has the correct props bound', () => {
- createComponent();
-
expect(findFirstRowFileIcon().props('fileName')).toBe(file.fileName);
});
});
describe('time-ago tooltip', () => {
- it('exists', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
+ it('exists', () => {
expect(findFirstRowCreatedAt().exists()).toBe(true);
});
it('has the correct props bound', () => {
- createComponent();
-
expect(findFirstRowCreatedAt().props('time')).toBe(file.createdAt);
});
});
- describe('commit', () => {
- const withPipeline = {
- ...file,
- pipelines: [
- {
- sha: 'sha',
- id: 1,
- commitPath: 'commitPath',
- },
- ],
- };
-
- describe('when package file has a pipeline associated', () => {
- it('exists', () => {
- createComponent({ packageFiles: [withPipeline] });
-
- expect(findFirstRowCommitLink().exists()).toBe(true);
- });
-
- it('the link points to the commit path', () => {
- createComponent({ packageFiles: [withPipeline] });
-
- expect(findFirstRowCommitLink().attributes('href')).toBe(
- withPipeline.pipelines[0].commitPath,
- );
- });
-
- it('the text is the pipeline sha', () => {
- createComponent({ packageFiles: [withPipeline] });
-
- expect(findFirstRowCommitLink().text()).toBe(withPipeline.pipelines[0].sha);
- });
- });
-
- describe('when package file has no pipeline associated', () => {
- it('does not exist', () => {
- createComponent();
-
- expect(findFirstRowCommitLink().exists()).toBe(false);
- });
- });
-
- describe('when only one file lacks an associated pipeline', () => {
- it('renders the commit when it exists and not otherwise', () => {
- createComponent({ packageFiles: [withPipeline, file] });
-
- expect(findFirstRowCommitLink().exists()).toBe(true);
- expect(findSecondRowCommitLink().exists()).toBe(false);
- });
- });
- });
-
describe('action menu', () => {
describe('when the user can delete', () => {
- it('exists', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
+ it('exists', () => {
expect(findFirstActionMenu().exists()).toBe(true);
expect(findFirstActionMenu().props('icon')).toBe('ellipsis_v');
expect(findFirstActionMenu().props('textSrOnly')).toBe(true);
@@ -178,14 +176,10 @@ describe('Package Files', () => {
describe('menu items', () => {
describe('delete file', () => {
it('exists', () => {
- createComponent();
-
expect(findActionMenuDelete().exists()).toBe(true);
});
it('emits a delete event when clicked', async () => {
- createComponent();
-
await findActionMenuDelete().trigger('click');
const [[items]] = wrapper.emitted('delete-files');
@@ -199,8 +193,9 @@ describe('Package Files', () => {
describe('when the user can not delete', () => {
const canDelete = false;
- it('does not exist', () => {
+ it('does not exist', async () => {
createComponent({ canDelete });
+ await waitForPromises();
expect(findFirstActionMenu().exists()).toBe(false);
});
@@ -209,22 +204,33 @@ describe('Package Files', () => {
describe('multi select', () => {
describe('when user can delete', () => {
- it('delete selected button exists & is disabled', () => {
+ it('delete selected button exists & is disabled', async () => {
createComponent();
+ await waitForPromises();
expect(findDeleteSelectedButton().exists()).toBe(true);
expect(findDeleteSelectedButton().text()).toMatchInterpolatedText('Delete selected');
expect(findDeleteSelectedButton().props('disabled')).toBe(true);
});
- it('delete selected button exists & is disabled when isLoading prop is true', () => {
- createComponent({ isLoading: true });
+ it('delete selected button exists & is disabled when isLoading prop is true', async () => {
+ createComponent();
+ await waitForPromises();
+ const first = findAllRowCheckboxes().at(0);
+
+ await first.setChecked(true);
+
+ expect(findDeleteSelectedButton().props('disabled')).toBe(false);
+
+ await wrapper.setProps({ isLoading: true });
expect(findDeleteSelectedButton().props('disabled')).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('checkboxes to select file are visible', () => {
- createComponent({ packageFiles: files });
+ it('checkboxes to select file are visible', async () => {
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery()) });
+ await waitForPromises();
expect(findCheckAllCheckbox().exists()).toBe(true);
expect(findAllRowCheckboxes()).toHaveLength(2);
@@ -232,6 +238,7 @@ describe('Package Files', () => {
it('selecting a checkbox enables delete selected button', async () => {
createComponent();
+ await waitForPromises();
const first = findAllRowCheckboxes().at(0);
@@ -244,7 +251,8 @@ describe('Package Files', () => {
it('will toggle between selecting all and deselecting all files', async () => {
const getChecked = () => findAllRowCheckboxes().filter((x) => x.element.checked === true);
- createComponent({ packageFiles: files });
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery()) });
+ await waitForPromises();
expect(getChecked()).toHaveLength(0);
@@ -262,9 +270,10 @@ describe('Package Files', () => {
expect(findCheckAllCheckbox().props('indeterminate')).toBe(state);
createComponent({
- packageFiles: files,
+ resolver: jest.fn().mockResolvedValue(packageFilesQuery()),
stubs: { GlFormCheckbox: stubComponent(GlFormCheckbox, { props: ['indeterminate'] }) },
});
+ await waitForPromises();
expectIndeterminateState(false);
@@ -288,6 +297,7 @@ describe('Package Files', () => {
it('emits a delete event when selected', async () => {
createComponent();
+ await waitForPromises();
const first = findAllRowCheckboxes().at(0);
@@ -301,7 +311,8 @@ describe('Package Files', () => {
});
it('emits delete event with both items when all are selected', async () => {
- createComponent({ packageFiles: files });
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery()) });
+ await waitForPromises();
await findCheckAllCheckbox().setChecked(true);
@@ -315,14 +326,16 @@ describe('Package Files', () => {
describe('when user cannot delete', () => {
const canDelete = false;
- it('delete selected button does not exist', () => {
+ it('delete selected button does not exist', async () => {
createComponent({ canDelete });
+ await waitForPromises();
expect(findDeleteSelectedButton().exists()).toBe(false);
});
- it('checkboxes to select file are not visible', () => {
- createComponent({ packageFiles: files, canDelete });
+ it('checkboxes to select file are not visible', async () => {
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery()), canDelete });
+ await waitForPromises();
expect(findCheckAllCheckbox().exists()).toBe(false);
expect(findAllRowCheckboxes()).toHaveLength(0);
@@ -332,24 +345,27 @@ describe('Package Files', () => {
describe('additional details', () => {
describe('details toggle button', () => {
- it('exists', () => {
+ it('exists', async () => {
createComponent();
+ await waitForPromises();
expect(findFirstToggleDetailsButton().exists()).toBe(true);
});
- it('is hidden when no details is present', () => {
+ it('is hidden when no details is present', async () => {
const { ...noShaFile } = file;
noShaFile.fileSha256 = null;
noShaFile.fileMd5 = null;
noShaFile.fileSha1 = null;
- createComponent({ packageFiles: [noShaFile] });
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery([noShaFile])) });
+ await waitForPromises();
expect(findFirstToggleDetailsButton().exists()).toBe(false);
});
it('toggles the details row', async () => {
createComponent();
+ await waitForPromises();
expect(findFirstToggleDetailsButton().props('icon')).toBe('chevron-down');
@@ -380,6 +396,7 @@ describe('Package Files', () => {
${'sha-1'} | ${'SHA-1'} | ${'be93151dc23ac34a82752444556fe79b32c7a1ad'}
`('has a $title row', async ({ selector, title, sha }) => {
createComponent();
+ await waitForPromises();
await showShaFiles();
@@ -393,7 +410,8 @@ describe('Package Files', () => {
const { ...missingMd5 } = file;
missingMd5.fileMd5 = null;
- createComponent({ packageFiles: [missingMd5] });
+ createComponent({ resolver: jest.fn().mockResolvedValue(packageFilesQuery([missingMd5])) });
+ await waitForPromises();
await showShaFiles();
diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js
index 5fb53566d4e..fa6a69b1a1f 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -257,7 +257,7 @@ export const packageDetailsQuery = ({
pageInfo: {
hasNextPage: true,
},
- nodes: packageFiles(),
+ nodes: packageFiles().map(({ id, size }) => ({ id, size })),
__typename: 'PackageFileConnection',
},
versions: {
@@ -285,6 +285,19 @@ export const packagePipelinesQuery = (pipelines = packagePipelines()) => ({
},
});
+export const packageFilesQuery = (files = packageFiles()) => ({
+ data: {
+ package: {
+ id: 'gid://gitlab/Packages::Package/111',
+ packageFiles: {
+ nodes: files,
+ __typename: 'PackageFileConnection',
+ },
+ __typename: 'PackageDetailsType',
+ },
+ },
+});
+
export const emptyPackageDetailsQuery = () => ({
data: {
package: {
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index 0962b4fa757..8b15dfd7d4a 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -328,18 +328,18 @@ describe('PackagesApp', () => {
describe('package files', () => {
it('renders the package files component and has the right props', async () => {
- const expectedFile = { ...packageFiles()[0] };
- // eslint-disable-next-line no-underscore-dangle
- delete expectedFile.__typename;
createComponent();
await waitForPromises();
expect(findPackageFiles().exists()).toBe(true);
- expect(findPackageFiles().props('packageFiles')[0]).toMatchObject(expectedFile);
- expect(findPackageFiles().props('canDelete')).toBe(packageData().canDestroy);
- expect(findPackageFiles().props('isLoading')).toEqual(false);
+ expect(findPackageFiles().props()).toMatchObject({
+ canDelete: packageData().canDestroy,
+ isLoading: false,
+ packageId: packageData().id,
+ packageType: packageData().packageType,
+ });
});
it('does not render the package files table when the package is composer', async () => {
diff --git a/spec/graphql/resolvers/users/participants_resolver_spec.rb b/spec/graphql/resolvers/users/participants_resolver_spec.rb
index 224213d1521..63a14daabba 100644
--- a/spec/graphql/resolvers/users/participants_resolver_spec.rb
+++ b/spec/graphql/resolvers/users/participants_resolver_spec.rb
@@ -8,39 +8,54 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
- let_it_be(:project) { create(:project, :public) }
+ let_it_be(:project) do
+ create(:project, :public).tap do |r|
+ r.add_developer(user)
+ r.add_guest(guest)
+ end
+ end
+
+ let_it_be(:private_project) { create(:project, :private).tap { |r| r.add_developer(user) } }
+
let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:private_issue) { create(:issue, project: private_project) }
let_it_be(:public_note_author) { create(:user) }
let_it_be(:public_reply_author) { create(:user) }
let_it_be(:internal_note_author) { create(:user) }
let_it_be(:internal_reply_author) { create(:user) }
+ let_it_be(:system_note_author) { create(:user) }
+ let_it_be(:internal_system_note_author) { create(:user) }
let_it_be(:public_note) { create(:note, project: project, noteable: issue, author: public_note_author) }
let_it_be(:internal_note) { create(:note, :confidential, project: project, noteable: issue, author: internal_note_author) }
- let_it_be(:public_reply) { create(:note, noteable: issue, in_reply_to: public_note, project: project, author: public_reply_author) }
- let_it_be(:internal_reply) { create(:note, :confidential, noteable: issue, in_reply_to: internal_note, project: project, author: internal_reply_author) }
-
- let_it_be(:note_metadata2) { create(:system_note_metadata, note: public_note) }
+ let_it_be(:public_reply) do
+ create(:note, noteable: issue, in_reply_to: public_note, project: project, author: public_reply_author)
+ end
- let_it_be(:issue_emoji) { create(:award_emoji, name: 'thumbsup', awardable: issue) }
- let_it_be(:note_emoji1) { create(:award_emoji, name: 'thumbsup', awardable: public_note) }
- let_it_be(:note_emoji2) { create(:award_emoji, name: 'thumbsup', awardable: internal_note) }
- let_it_be(:note_emoji3) { create(:award_emoji, name: 'thumbsup', awardable: public_reply) }
- let_it_be(:note_emoji4) { create(:award_emoji, name: 'thumbsup', awardable: internal_reply) }
+ let_it_be(:internal_reply) do
+ create(:note, :confidential, noteable: issue, in_reply_to: internal_note, project: project, author: internal_reply_author)
+ end
- let_it_be(:issue_emoji_author) { issue_emoji.user }
- let_it_be(:public_note_emoji_author) { note_emoji1.user }
- let_it_be(:internal_note_emoji_author) { note_emoji2.user }
- let_it_be(:public_reply_emoji_author) { note_emoji3.user }
- let_it_be(:internal_reply_emoji_author) { note_emoji4.user }
+ let_it_be(:issue_emoji_author) { create(:award_emoji, name: 'thumbsup', awardable: issue).user }
+ let_it_be(:public_note_emoji_author) { create(:award_emoji, name: 'thumbsup', awardable: public_note).user }
+ let_it_be(:internal_note_emoji_author) { create(:award_emoji, name: 'thumbsup', awardable: internal_note).user }
+ let_it_be(:public_reply_emoji_author) { create(:award_emoji, name: 'thumbsup', awardable: public_reply).user }
+ let_it_be(:internal_reply_emoji_author) { create(:award_emoji, name: 'thumbsup', awardable: internal_reply).user }
- subject(:resolved_items) { resolve(described_class, args: {}, ctx: { current_user: current_user }, obj: issue)&.items }
+ subject(:resolved_items) do
+ resolve(described_class, args: {}, ctx: { current_user: current_user }, obj: issue)&.items
+ end
- before do
- project.add_guest(guest)
- project.add_developer(user)
+ before_all do
+ create(:system_note, project: project, noteable: issue, author: system_note_author)
+ create(
+ :system_note,
+ note: "mentioned in issue #{private_issue.to_reference(full: true)}",
+ project: project, noteable: issue, author: internal_system_note_author
+ )
+ create(:system_note_metadata, note: public_note)
end
context 'when current user is not set' do
@@ -54,7 +69,8 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do
public_note_author,
public_note_emoji_author,
public_reply_author,
- public_reply_emoji_author
+ public_reply_emoji_author,
+ system_note_author
]
)
end
@@ -71,7 +87,8 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do
public_note_author,
public_note_emoji_author,
public_reply_author,
- public_reply_emoji_author
+ public_reply_emoji_author,
+ system_note_author
]
)
end
@@ -92,13 +109,17 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do
internal_note_emoji_author,
internal_reply_author,
public_reply_emoji_author,
- internal_reply_emoji_author
+ internal_reply_emoji_author,
+ system_note_author,
+ internal_system_note_author
]
)
end
context 'N+1 queries' do
- let(:query) { -> { resolve(described_class, args: {}, ctx: { current_user: current_user }, obj: issue)&.items } }
+ let(:query) do
+ -> { resolve(described_class, args: {}, ctx: { current_user: current_user }, obj: issue)&.items }
+ end
before do
# warm-up
diff --git a/spec/lib/sidebars/groups/super_sidebar_menus/deploy_menu_spec.rb b/spec/lib/sidebars/groups/super_sidebar_menus/deploy_menu_spec.rb
new file mode 100644
index 00000000000..ec3f911d8dc
--- /dev/null
+++ b/spec/lib/sidebars/groups/super_sidebar_menus/deploy_menu_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Groups::SuperSidebarMenus::DeployMenu, feature_category: :navigation do
+ subject { described_class.new({}) }
+
+ let(:items) { subject.instance_variable_get(:@items) }
+
+ it 'has title and sprite_icon' do
+ expect(subject.title).to eq(s_("Navigation|Deploy"))
+ expect(subject.sprite_icon).to eq("deployments")
+ end
+
+ it 'defines list of NilMenuItem placeholders' do
+ expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem])
+ expect(items.map(&:item_id)).to eq([
+ :packages_registry
+ ])
+ end
+end
diff --git a/spec/lib/sidebars/groups/super_sidebar_menus/operations_menu_spec.rb b/spec/lib/sidebars/groups/super_sidebar_menus/operations_menu_spec.rb
index e9c2701021c..df37d5f1b0d 100644
--- a/spec/lib/sidebars/groups/super_sidebar_menus/operations_menu_spec.rb
+++ b/spec/lib/sidebars/groups/super_sidebar_menus/operations_menu_spec.rb
@@ -9,14 +9,13 @@ RSpec.describe Sidebars::Groups::SuperSidebarMenus::OperationsMenu, feature_cate
it 'has title and sprite_icon' do
expect(subject.title).to eq(s_("Navigation|Operate"))
- expect(subject.sprite_icon).to eq("deployments")
+ expect(subject.sprite_icon).to eq("cloud-pod")
end
it 'defines list of NilMenuItem placeholders' do
expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem])
expect(items.map(&:item_id)).to eq([
:dependency_proxy,
- :packages_registry,
:container_registry,
:group_kubernetes_clusters
])
diff --git a/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb b/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb
index 5035da9c488..245d1eca0a4 100644
--- a/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb
+++ b/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Sidebars::Groups::SuperSidebarPanel, feature_category: :navigatio
Sidebars::Groups::SuperSidebarMenus::PlanMenu,
Sidebars::Groups::SuperSidebarMenus::CodeMenu,
Sidebars::Groups::SuperSidebarMenus::BuildMenu,
+ Sidebars::Groups::SuperSidebarMenus::DeployMenu,
Sidebars::Groups::SuperSidebarMenus::SecureMenu,
Sidebars::Groups::SuperSidebarMenus::OperationsMenu,
Sidebars::Groups::SuperSidebarMenus::MonitorMenu,
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/analyze_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/analyze_menu_spec.rb
index d459d47c31a..b7d05867d77 100644
--- a/spec/lib/sidebars/projects/super_sidebar_menus/analyze_menu_spec.rb
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/analyze_menu_spec.rb
@@ -23,8 +23,7 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::AnalyzeMenu, feature_categ
:code_review,
:merge_request_analytics,
:issues,
- :insights,
- :model_experiments
+ :insights
])
end
end
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/build_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/build_menu_spec.rb
index 3f2a40e1c7d..06b87003d83 100644
--- a/spec/lib/sidebars/projects/super_sidebar_menus/build_menu_spec.rb
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/build_menu_spec.rb
@@ -18,10 +18,7 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::BuildMenu, feature_categor
:pipelines,
:jobs,
:pipelines_editor,
- :releases,
- :environments,
:pipeline_schedules,
- :feature_flags,
:test_cases,
:artifacts
])
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/deploy_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/deploy_menu_spec.rb
new file mode 100644
index 00000000000..50eee173d31
--- /dev/null
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/deploy_menu_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Projects::SuperSidebarMenus::DeployMenu, feature_category: :navigation do
+ subject { described_class.new({}) }
+
+ let(:items) { subject.instance_variable_get(:@items) }
+
+ it 'has title and sprite_icon' do
+ expect(subject.title).to eq(s_("Navigation|Deploy"))
+ expect(subject.sprite_icon).to eq("deployments")
+ end
+
+ it 'defines list of NilMenuItem placeholders' do
+ expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem])
+ expect(items.map(&:item_id)).to eq([
+ :releases,
+ :feature_flags,
+ :packages_registry,
+ :container_registry,
+ :model_experiments
+ ])
+ end
+end
diff --git a/spec/lib/sidebars/projects/super_sidebar_menus/operations_menu_spec.rb b/spec/lib/sidebars/projects/super_sidebar_menus/operations_menu_spec.rb
index 6ab070c40ae..68ca4fe2aa0 100644
--- a/spec/lib/sidebars/projects/super_sidebar_menus/operations_menu_spec.rb
+++ b/spec/lib/sidebars/projects/super_sidebar_menus/operations_menu_spec.rb
@@ -9,14 +9,13 @@ RSpec.describe Sidebars::Projects::SuperSidebarMenus::OperationsMenu, feature_ca
it 'has title and sprite_icon' do
expect(subject.title).to eq(s_("Navigation|Operate"))
- expect(subject.sprite_icon).to eq("deployments")
+ expect(subject.sprite_icon).to eq("cloud-pod")
end
it 'defines list of NilMenuItem placeholders' do
expect(items.map(&:class).uniq).to eq([Sidebars::NilMenuItem])
expect(items.map(&:item_id)).to eq([
- :packages_registry,
- :container_registry,
+ :environments,
:kubernetes,
:terraform_states,
:infrastructure_registry,
diff --git a/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb b/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb
index 93f0072a111..9ed328f5090 100644
--- a/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb
+++ b/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb
@@ -47,6 +47,7 @@ RSpec.describe Sidebars::Projects::SuperSidebarPanel, feature_category: :navigat
Sidebars::Projects::SuperSidebarMenus::PlanMenu,
Sidebars::Projects::SuperSidebarMenus::CodeMenu,
Sidebars::Projects::SuperSidebarMenus::BuildMenu,
+ Sidebars::Projects::SuperSidebarMenus::DeployMenu,
Sidebars::Projects::SuperSidebarMenus::SecureMenu,
Sidebars::Projects::SuperSidebarMenus::OperationsMenu,
Sidebars::Projects::SuperSidebarMenus::MonitorMenu,
diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb
index d3cb386e8e0..71dd543b3ec 100644
--- a/spec/models/integrations/jira_spec.rb
+++ b/spec/models/integrations/jira_spec.rb
@@ -326,6 +326,18 @@ RSpec.describe Integrations::Jira, feature_category: :integrations do
end
end
end
+
+ context 'with long running regex' do
+ let(:key) { "JIRAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1\nanother line\n" }
+
+ before do
+ jira_integration.jira_issue_regex = '((a|b)+|c)+$'
+ end
+
+ it 'handles long inputs' do
+ expect(jira_integration.reference_pattern.match(key).to_s).to eq('')
+ end
+ end
end
describe '.valid_jira_cloud_url?' do
diff --git a/spec/models/integrations/pipelines_email_spec.rb b/spec/models/integrations/pipelines_email_spec.rb
index 37a3849a768..7e80defcb87 100644
--- a/spec/models/integrations/pipelines_email_spec.rb
+++ b/spec/models/integrations/pipelines_email_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do
end
it 'sends email' do
- emails = receivers.map { |r| double(notification_email_or_default: r) }
+ emails = receivers.map { |r| double(notification_email_or_default: r, username: r, id: r) }
should_only_email(*emails)
end
@@ -206,10 +206,6 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do
end
context 'with recipients' do
- context 'with failed pipeline' do
- it_behaves_like 'sending email'
- end
-
context 'with succeeded pipeline' do
before do
data[:object_attributes][:status] = 'success'
@@ -240,10 +236,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do
context 'when the pipeline failed' do
context 'on default branch' do
- before do
- data[:object_attributes][:ref] = project.default_branch
- pipeline.update!(ref: project.default_branch)
- end
+ it_behaves_like 'sending email'
context 'notifications are enabled only for default branch' do
it_behaves_like 'sending email', branches_to_be_notified: "default"
@@ -253,7 +246,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do
it_behaves_like 'not sending email', branches_to_be_notified: "protected"
end
- context 'notifications are enabled only for default and protected branches ' do
+ context 'notifications are enabled only for default and protected branches' do
it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected"
end
@@ -273,11 +266,13 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do
it_behaves_like 'not sending email', branches_to_be_notified: "default"
end
- context 'notifications are enabled only for protected branch' do
+ context 'notifications are enabled only for protected branch',
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/411331' do
it_behaves_like 'sending email', branches_to_be_notified: "protected"
end
- context 'notifications are enabled only for default and protected branches ' do
+ context 'notifications are enabled only for default and protected branches',
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/411331' do
it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected"
end
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index b6fccd9b7cb..fbda291e901 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -13,16 +13,33 @@ RSpec.describe API::V3::Github, :aggregate_failures, feature_category: :integrat
end
describe 'GET /orgs/:namespace/repos' do
+ let_it_be(:group) { create(:group) }
+
it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
subject do
- group = create(:group)
jira_get v3_api("/orgs/#{group.path}/repos", user)
end
end
- it 'returns an empty array' do
- group = create(:group)
+ it 'logs when the endpoint is hit and `jira_dvcs_end_of_life_amnesty` is enabled' do
+ expect(Gitlab::JsonLogger).to receive(:info).with(
+ including(
+ namespace: group.path,
+ user_id: user.id,
+ message: 'Deprecated Jira DVCS endpoint request'
+ )
+ )
+
+ jira_get v3_api("/orgs/#{group.path}/repos", user)
+
+ stub_feature_flags(jira_dvcs_end_of_life_amnesty: false)
+ expect(Gitlab::JsonLogger).not_to receive(:info)
+
+ jira_get v3_api("/orgs/#{group.path}/repos", user)
+ end
+
+ it 'returns an empty array' do
jira_get v3_api("/orgs/#{group.path}/repos", user)
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/support/shared_examples/models/concerns/participable_shared_examples.rb b/spec/support/shared_examples/models/concerns/participable_shared_examples.rb
index ec7a9105bb2..f772cfc6bbd 100644
--- a/spec/support/shared_examples/models/concerns/participable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/participable_shared_examples.rb
@@ -10,13 +10,14 @@ RSpec.shared_examples 'visible participants for issuable with read ability' do |
allow(model).to receive(:participant_attrs).and_return([:bar])
end
- shared_examples 'check for participables read ability' do |ability_name|
+ shared_examples 'check for participables read ability' do |ability_name, ability_source: nil|
it 'receives expected ability' do
instance = model.new
+ source = ability_source == :participable_source ? participable_source : instance
allow(instance).to receive(:bar).and_return(participable_source)
- expect(Ability).to receive(:allowed?).with(anything, ability_name, instance)
+ expect(Ability).to receive(:allowed?).with(anything, ability_name, source)
expect(instance.visible_participants(user1)).to be_empty
end
@@ -39,4 +40,10 @@ RSpec.shared_examples 'visible participants for issuable with read ability' do |
it_behaves_like 'check for participables read ability', :read_internal_note
end
+
+ context 'when source is a system note' do
+ let(:participable_source) { build(:system_note) }
+
+ it_behaves_like 'check for participables read ability', :read_note, ability_source: :participable_source
+ end
end