summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-09 21:09:18 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-09 21:09:18 +0000
commitab5672c13d7fe5c79fdeac10e7505187cf4ba606 (patch)
treeeb7036d6e4c8ce64c58f18185eced3a5e315c099
parentd23f33082ad893fad172b17f1ce66bd847671d56 (diff)
downloadgitlab-ce-ab5672c13d7fe5c79fdeac10e7505187cf4ba606.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml10
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/import/details/api.js11
-rw-r--r--app/assets/javascripts/import/details/components/import_details_app.vue7
-rw-r--r--app/assets/javascripts/import/details/components/import_details_table.vue78
-rw-r--r--app/assets/javascripts/import/details/index.js5
-rw-r--r--app/assets/javascripts/import_entities/components/import_status.vue23
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue8
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue7
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue18
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue19
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue22
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue9
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue101
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue18
-rw-r--r--app/assets/javascripts/labels/index.js5
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js2
-rw-r--r--app/assets/javascripts/projects/commit_box/info/index.js3
-rw-r--r--app/assets/javascripts/projects/commit_box/info/init_details_button.js18
-rw-r--r--app/assets/javascripts/projects/commit_box/info/load_branches.js3
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item.vue21
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item_link.vue35
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue37
-rw-r--r--app/assets/javascripts/super_sidebar/constants.js2
-rw-r--r--app/assets/javascripts/super_sidebar/utils.js2
-rw-r--r--app/controllers/oauth/jira_dvcs/authorizations_controller.rb12
-rw-r--r--app/graphql/types/user_interface.rb2
-rw-r--r--app/services/ci/pipelines/add_job_service.rb6
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml33
-rw-r--r--app/views/admin/groups/show.html.haml20
-rw-r--r--app/views/admin/labels/_label.html.haml24
-rw-r--r--app/views/admin/labels/index.html.haml58
-rw-r--r--app/views/admin/projects/show.html.haml24
-rw-r--r--app/views/admin/users/_profile.html.haml4
-rw-r--r--app/views/admin/users/_projects.html.haml8
-rw-r--r--app/views/admin/users/_user_detail_note.html.haml4
-rw-r--r--app/views/admin/users/projects.html.haml8
-rw-r--r--app/views/admin/users/show.html.haml8
-rw-r--r--app/views/import/github/details.html.haml2
-rw-r--r--config/feature_flags/development/ai_ci_config_generator.yml10
-rw-r--r--data/removals/16_0/16-0-external-field-grapql-release-asset-link-type.yml11
-rw-r--r--data/removals/16_0/16-0-external-field-releases-release-links-api.yml11
-rw-r--r--db/docs/p_ci_builds.yml14
-rw-r--r--db/post_migrate/20230501090213_convert_ci_builds_to_list_partitioning.rb78
-rw-r--r--db/structure.sql176
-rw-r--r--doc/api/graphql/reference/index.md23
-rw-r--r--doc/development/api_graphql_styleguide.md2
-rw-r--r--doc/development/navigation_sidebar.md5
-rw-r--r--doc/update/removals.md16
-rw-r--r--doc/user/compliance/compliance_report/index.md22
-rw-r--r--locale/gitlab.pot15
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/main/menu.rb12
-rw-r--r--spec/db/schema_spec.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb45
-rw-r--r--spec/features/admin/admin_labels_spec.rb7
-rw-r--r--spec/features/jira_oauth_provider_authorize_spec.rb35
-rw-r--r--spec/frontend/import/details/components/import_details_app_spec.js7
-rw-r--r--spec/frontend/import/details/components/import_details_table_spec.js88
-rw-r--r--spec/frontend/import/details/mock_data.js76
-rw-r--r--spec/frontend/import_entities/components/import_status_spec.js6
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js14
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js41
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/user_link_spec.js42
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js25
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js18
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js93
-rw-r--r--spec/frontend/projects/commit_box/info/init_details_button_spec.js32
-rw-r--r--spec/frontend/projects/commit_box/info/load_branches_spec.js10
-rw-r--r--spec/frontend/search/sidebar/components/scope_new_navigation_spec.js4
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_link_spec.js37
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_router_link_spec.js56
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_spec.js89
-rw-r--r--spec/frontend/super_sidebar/utils_spec.js11
-rw-r--r--spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb6
-rw-r--r--spec/models/environment_spec.rb25
-rw-r--r--spec/models/environment_status_spec.rb26
-rw-r--r--spec/models/event_collection_spec.rb3
-rw-r--r--spec/models/event_spec.rb26
-rw-r--r--spec/models/generic_commit_status_spec.rb3
-rw-r--r--spec/models/group_group_link_spec.rb15
-rw-r--r--spec/models/group_spec.rb3
-rw-r--r--spec/models/integrations/apple_app_store_spec.rb4
-rw-r--r--spec/models/integrations/buildkite_spec.rb9
-rw-r--r--spec/models/integrations/hangouts_chat_spec.rb17
-rw-r--r--spec/requests/jira_authorizations_spec.rb10
-rw-r--r--spec/services/ci/pipelines/add_job_service_spec.rb10
-rw-r--r--yarn.lock52
90 files changed, 1268 insertions, 761 deletions
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index b89002da1b2..20d2b04b359 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -2193,16 +2193,6 @@ Layout/ArgumentAlignment:
- 'spec/lib/security/weak_passwords_spec.rb'
- 'spec/lib/sidebars/projects/menus/repository_menu_spec.rb'
- 'spec/lib/uploaded_file_spec.rb'
- - 'spec/models/environment_spec.rb'
- - 'spec/models/environment_status_spec.rb'
- - 'spec/models/event_collection_spec.rb'
- - 'spec/models/event_spec.rb'
- - 'spec/models/generic_commit_status_spec.rb'
- - 'spec/models/group_group_link_spec.rb'
- - 'spec/models/group_spec.rb'
- - 'spec/models/integrations/apple_app_store_spec.rb'
- - 'spec/models/integrations/buildkite_spec.rb'
- - 'spec/models/integrations/hangouts_chat_spec.rb'
- 'spec/models/integrations/mattermost_slash_commands_spec.rb'
- 'spec/models/integrations/microsoft_teams_spec.rb'
- 'spec/models/issue_spec.rb'
diff --git a/Gemfile b/Gemfile
index 46a0b1bb014..b60a3e3181d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -433,7 +433,7 @@ group :development, :test do
end
group :development, :test, :danger do
- gem 'gitlab-dangerfiles', '~> 3.9.0', require: false
+ gem 'gitlab-dangerfiles', '~> 3.10.0', require: false
end
group :development, :test, :coverage do
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 7764eb68fd8..86fe71a7ddb 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -210,7 +210,7 @@
{"name":"gitaly","version":"15.9.0.pre.rc3","platform":"ruby","checksum":"6ac64320a70417131a4b97f5dd45d4e203d60703cc3cba156561e7f8c50a4abe"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
-{"name":"gitlab-dangerfiles","version":"3.9.0","platform":"ruby","checksum":"37baa9856861b1c6a91d44adcde8654117a5cf2804ad513feffa2a86bda0d78e"},
+{"name":"gitlab-dangerfiles","version":"3.10.0","platform":"ruby","checksum":"df4cfe051f52529c0256346d89d06d5ef2bb630928754eb620b5233eb9b14041"},
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
{"name":"gitlab-fog-azure-rm","version":"1.7.0","platform":"ruby","checksum":"969c67943c54ad4c259a6acd040493f13922fbdf2211bb4eca00e71505263dc2"},
{"name":"gitlab-labkit","version":"0.31.1","platform":"ruby","checksum":"3e3a39370966b5d2739c2d9d9005c0ea27541d32cb7292e856e8bd74c720bffb"},
diff --git a/Gemfile.lock b/Gemfile.lock
index c0944739b3e..ddd70e5f856 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -596,7 +596,7 @@ GEM
terminal-table (>= 1.5.1)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
- gitlab-dangerfiles (3.9.0)
+ gitlab-dangerfiles (3.10.0)
danger (>= 8.4.5)
danger-gitlab (>= 8.0.0)
rake
@@ -1752,7 +1752,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 15.9.0.pre.rc3)
gitlab-chronic (~> 0.10.5)
- gitlab-dangerfiles (~> 3.9.0)
+ gitlab-dangerfiles (~> 3.10.0)
gitlab-experiment (~> 0.7.1)
gitlab-fog-azure-rm (~> 1.7.0)
gitlab-labkit (~> 0.31.1)
diff --git a/app/assets/javascripts/import/details/api.js b/app/assets/javascripts/import/details/api.js
new file mode 100644
index 00000000000..1fb3ee526d7
--- /dev/null
+++ b/app/assets/javascripts/import/details/api.js
@@ -0,0 +1,11 @@
+import axios from '~/lib/utils/axios_utils';
+
+export const fetchImportFailures = (failuresPath, { projectId, page, perPage }) => {
+ return axios.get(failuresPath, {
+ params: {
+ project_id: projectId,
+ page,
+ per_page: perPage,
+ },
+ });
+};
diff --git a/app/assets/javascripts/import/details/components/import_details_app.vue b/app/assets/javascripts/import/details/components/import_details_app.vue
index 86820663025..13483fa8ba2 100644
--- a/app/assets/javascripts/import/details/components/import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/import_details_app.vue
@@ -4,13 +4,6 @@ import ImportDetailsTable from './import_details_table.vue';
export default {
components: { ImportDetailsTable },
- props: {
- project: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- },
i18n: {
pageTitle: s__('Import|GitHub import details'),
},
diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue
index 9ce58e8a9bc..b32b5778265 100644
--- a/app/assets/javascripts/import/details/components/import_details_table.vue
+++ b/app/assets/javascripts/import/details/components/import_details_table.vue
@@ -1,9 +1,13 @@
<script>
-import { GlEmptyState, GlIcon, GlLink, GlTable } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import { createAlert } from '~/alert';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { getParameterValues } from '~/lib/utils/url_utility';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
import { STATISTIC_ITEMS } from '../../constants';
+import { fetchImportFailures } from '../api';
const DEFAULT_PAGE_SIZE = 20;
@@ -12,6 +16,7 @@ export default {
GlEmptyState,
GlIcon,
GlLink,
+ GlLoadingIcon,
GlTable,
PaginationBar,
},
@@ -38,40 +43,85 @@ export default {
label: __('Details'),
},
],
+
+ i18n: {
+ fetchErrorMessage: s__('Import|An error occurred while fetching import details.'),
+ emptyText: s__('Import|No import details'),
+ },
+
+ inject: {
+ failuresPath: {
+ default: undefined,
+ },
+ },
+
data() {
return {
+ items: [],
+ loading: false,
page: 1,
perPage: DEFAULT_PAGE_SIZE,
+ totalPages: 0,
+ total: 0,
};
},
- computed: {
- items() {
- return [];
- },
+ computed: {
hasItems() {
return this.items.length > 0;
},
pageInfo() {
- const mockPageInfo = {
+ return {
page: this.page,
perPage: this.perPage,
- totalPages: this.page,
- total: this.items.length,
+ totalPages: this.totalPages,
+ total: this.total,
};
- return mockPageInfo;
},
},
+ mounted() {
+ this.loadImportFailures();
+ },
+
methods: {
setPage(page) {
this.page = page;
+ this.loadImportFailures();
},
setPageSize(size) {
this.perPage = size;
this.page = 1;
+ this.loadImportFailures();
+ },
+
+ async loadImportFailures() {
+ if (!this.failuresPath) {
+ return;
+ }
+
+ this.loading = true;
+ try {
+ const response = await fetchImportFailures(this.failuresPath, {
+ projectId: getParameterValues('project_id')[0],
+ page: this.page,
+ perPage: this.perPage,
+ });
+
+ const { page, perPage, totalPages, total } = parseIntPagination(
+ normalizeHeaders(response.headers),
+ );
+ this.page = page;
+ this.perPage = perPage;
+ this.totalPages = totalPages;
+ this.total = total;
+ this.items = response.data;
+ } catch (error) {
+ createAlert({ message: this.$options.i18n.fetchErrorMessage });
+ }
+ this.loading = false;
},
},
};
@@ -79,9 +129,13 @@ export default {
<template>
<div>
- <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" show-empty>
+ <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
+ <template #table-busy>
+ <gl-loading-icon size="lg" class="gl-my-5" />
+ </template>
+
<template #empty>
- <gl-empty-state :title="s__('Import|No import details')" />
+ <gl-empty-state :title="$options.i18n.emptyText" />
</template>
<template #cell(type)="{ item: { type } }">
diff --git a/app/assets/javascripts/import/details/index.js b/app/assets/javascripts/import/details/index.js
index 70850d947e2..7421846f103 100644
--- a/app/assets/javascripts/import/details/index.js
+++ b/app/assets/javascripts/import/details/index.js
@@ -8,9 +8,14 @@ export default () => {
return null;
}
+ const { failuresPath } = el.dataset;
+
return new Vue({
el,
name: 'ImportDetailsRoot',
+ provide: {
+ failuresPath,
+ },
render(createElement) {
return createElement(ImportDetailsApp);
},
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue
index 96d07803545..6c84684dedc 100644
--- a/app/assets/javascripts/import_entities/components/import_status.vue
+++ b/app/assets/javascripts/import_entities/components/import_status.vue
@@ -65,6 +65,11 @@ export default {
},
},
props: {
+ projectId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
status: {
type: String,
required: true,
@@ -111,7 +116,19 @@ export default {
},
showDetails() {
- return Boolean(this.detailsPath) && this.glFeatures.importDetailsPage && this.isIncomplete;
+ return (
+ Boolean(this.detailsPathForProject) &&
+ this.glFeatures.importDetailsPage &&
+ this.isIncomplete
+ );
+ },
+
+ detailsPathForProject() {
+ if (!this.projectId || !this.detailsPath) {
+ return null;
+ }
+
+ return `${this.detailsPath}?project_id=${this.projectId}`;
},
},
@@ -163,7 +180,9 @@ export default {
</div>
</li>
</ul>
- <gl-link v-if="showDetails" :href="detailsPath">{{ $options.i18n.detailsLink }}</gl-link>
+ <gl-link v-if="showDetails" :href="detailsPathForProject">{{
+ $options.i18n.detailsLink
+ }}</gl-link>
</gl-accordion-item>
</gl-accordion>
</div>
diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
index b20309baac7..735939f991f 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
@@ -70,7 +70,7 @@ export default {
...mapGetters(['getImportTarget']),
displayFullPath() {
- return this.repo.importedProject.fullPath.replace(/^\//, '');
+ return this.repo.importedProject?.fullPath.replace(/^\//, '');
},
isFinished() {
@@ -105,6 +105,10 @@ export default {
return this.getImportTarget(this.repo.importSource.id);
},
+ importedProjectId() {
+ return this.repo.importedProject?.id;
+ },
+
importButtonText() {
if (this.ciCdOnly) {
return __('Connect');
@@ -220,7 +224,7 @@ export default {
</div>
</td>
<td data-qa-selector="import_status_indicator">
- <import-status :status="importStatus" :stats="stats" />
+ <import-status :project-id="importedProjectId" :status="importStatus" :stats="stats" />
</td>
<td data-testid="actions" class="gl-white-space-nowrap">
<gl-tooltip :target="() => $refs.cancelButton.$el">
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 15e61ff1cd9..7e79572f76d 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -100,12 +100,7 @@ export default {
<gl-link :href="gitlabUrl" target="_blank">
<img :src="gitlabLogo" class="gl-h-6" :alt="__('GitLab')" />
</gl-link>
- <user-link
- :user-signed-in="userSignedIn"
- :has-subscriptions="hasSubscriptions"
- :user="currentUser"
- class="gl-fixed gl-right-4"
- />
+ <user-link v-if="userSignedIn" :user="currentUser" class="gl-fixed gl-right-4" />
</header>
<main class="jira-connect-app gl-px-5 gl-pt-7 gl-mx-auto">
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue b/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
index d05e0d8610e..cc0af0b9ab7 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
@@ -6,7 +6,6 @@ export default {
components: {
GlLink,
GlSprintf,
- SignInOauthButton: () => import('./sign_in_oauth_button.vue'),
},
inject: {
gitlabUserPath: {
@@ -14,14 +13,6 @@ export default {
},
},
props: {
- userSignedIn: {
- type: Boolean,
- required: true,
- },
- hasSubscriptions: {
- type: Boolean,
- required: true,
- },
user: {
type: Object,
required: false,
@@ -45,7 +36,6 @@ export default {
},
},
i18n: {
- signInText: __('Sign in to GitLab'),
signedInAsUserText: __('Signed in to GitLab as %{user_link}'),
signedInText: __('Signed in to GitLab'),
},
@@ -53,18 +43,12 @@ export default {
</script>
<template>
<div class="gl-font-base">
- <gl-sprintf v-if="userSignedIn" :message="signedInText">
+ <gl-sprintf :message="signedInText">
<template #user_link>
<gl-link data-testid="gitlab-user-link" :href="gitlabUserLink" target="_blank">
{{ gitlabUserHandle }}
</gl-link>
</template>
</gl-sprintf>
-
- <template v-else-if="hasSubscriptions">
- <sign-in-oauth-button category="tertiary">
- {{ $options.i18n.signInText }}
- </sign-in-oauth-button>
- </template>
</div>
</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
index 373c5970e64..8cc107930d1 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
@@ -12,7 +12,6 @@ import {
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import SignInOauthButton from '../../../components/sign_in_oauth_button.vue';
-import SetupInstructions from './setup_instructions.vue';
import VersionSelectForm from './version_select_form.vue';
export default {
@@ -20,31 +19,23 @@ export default {
components: {
GlButton,
SignInOauthButton,
- SetupInstructions,
VersionSelectForm,
},
data() {
return {
gitlabBasePath: null,
loadingVersionSelect: false,
- showSetupInstructions: false,
};
},
computed: {
hasSelectedVersion() {
return this.gitlabBasePath !== null;
},
- subtitle() {
- return this.hasSelectedVersion
- ? this.$options.i18n.signInSubtitle
- : this.$options.i18n.versionSelectSubtitle;
- },
},
mounted() {
this.gitlabBasePath = retrieveBaseUrl();
if (this.gitlabBasePath !== GITLAB_COM_BASE_PATH) {
setApiBaseURL(this.gitlabBasePath);
- this.showSetupInstructions = true;
}
},
methods: {
@@ -70,9 +61,6 @@ export default {
this.loadingVersionSelect = false;
});
},
- onSetupNext() {
- this.showSetupInstructions = false;
- },
onSignInError() {
this.$emit('error');
},
@@ -80,7 +68,6 @@ export default {
i18n: {
title: s__('JiraService|Welcome to GitLab for Jira'),
signInSubtitle: s__('JiraService|Sign in to GitLab to link namespaces.'),
- versionSelectSubtitle: s__('JiraService|What version of GitLab are you using?'),
changeVersionButtonText: s__('JiraService|Change GitLab version'),
},
};
@@ -90,7 +77,6 @@ export default {
<div>
<div class="gl-text-center">
<h2>{{ $options.i18n.title }}</h2>
- <p data-testid="subtitle">{{ subtitle }}</p>
</div>
<version-select-form
@@ -101,9 +87,8 @@ export default {
/>
<template v-else>
- <setup-instructions v-if="showSetupInstructions" @next="onSetupNext" />
-
- <div v-else class="gl-text-center">
+ <div class="gl-text-center">
+ <p data-testid="subtitle">{{ $options.i18n.signInSubtitle }}</p>
<sign-in-oauth-button
class="gl-mb-5"
:gitlab-base-path="gitlabBasePath"
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue
new file mode 100644
index 00000000000..8ddbbffa708
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue
@@ -0,0 +1,22 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlAlert,
+ },
+ i18n: {
+ title: s__('JiraService|Are you a GitLab administrator?'),
+ body: s__(
+ "JiraService|Setting up this integration is only possible if you're a GitLab administrator.",
+ ),
+ },
+};
+</script>
+
+<template>
+ <gl-alert variant="warning" :title="$options.i18n.title" :dismissible="false">
+ {{ $options.i18n.body }}
+ </gl-alert>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
index 00fa739b518..621bcccd19a 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
@@ -12,7 +12,7 @@ export default {
</script>
<template>
- <div class="gl-max-w-62 gl-mx-auto gl-mt-7">
+ <div class="gl-mt-5">
<h3>{{ s__('JiraService|Continue setup in GitLab') }}</h3>
<p>
{{
@@ -28,8 +28,9 @@ export default {
>
</p>
- <gl-button variant="confirm" @click="$emit('next')">
- {{ __('Next') }}
- </gl-button>
+ <div class="gl-display-flex gl-justify-content-space-between">
+ <gl-button @click="$emit('back')">{{ __('Back') }}</gl-button>
+ <gl-button variant="confirm" @click="$emit('next')">{{ __('Next') }}</gl-button>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
index 37a65946b3f..3a080afd3c5 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
@@ -10,6 +10,8 @@ import {
import { __, s__ } from '~/locale';
import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants';
+import SelfManagedAlert from './self_managed_alert.vue';
+import SetupInstructions from './setup_instructions.vue';
const RADIO_OPTIONS = {
saas: 'saas',
@@ -27,6 +29,8 @@ export default {
GlFormInput,
GlFormRadio,
GlButton,
+ SelfManagedAlert,
+ SetupInstructions,
},
props: {
loading: {
@@ -39,25 +43,54 @@ export default {
return {
selected: DEFAULT_RADIO_OPTION,
selfManagedBasePathInput: '',
+ showSetupInstructions: false,
+ showSelfManagedInstanceInput: false,
};
},
computed: {
isSelfManagedSelected() {
return this.selected === RADIO_OPTIONS.selfManaged;
},
+ submitText() {
+ return this.isSelfManagedSelected
+ ? this.$options.i18n.buttonNext
+ : this.$options.i18n.buttonSave;
+ },
+ showVersonSelect() {
+ return !this.showSetupInstructions && !this.showSelfManagedInstanceInput;
+ },
},
methods: {
onSubmit() {
- const gitlabBasePath =
- this.selected === RADIO_OPTIONS.saas ? GITLAB_COM_BASE_PATH : this.selfManagedBasePathInput;
+ if (this.isSelfManagedSelected && !this.showSelfManagedInstanceInput) {
+ this.showSetupInstructions = true;
+ return;
+ }
+
+ const gitlabBasePath = this.isSelfManagedSelected
+ ? this.selfManagedBasePathInput
+ : GITLAB_COM_BASE_PATH;
this.$emit('submit', gitlabBasePath);
},
+
+ onSetupNext() {
+ this.showSetupInstructions = false;
+ this.showSelfManagedInstanceInput = true;
+ },
+
+ onSetupBack() {
+ this.showSetupInstructions = false;
+ this.showSelfManagedInstanceInput = false;
+ },
},
radioOptions: RADIO_OPTIONS,
i18n: {
+ title: s__('JiraService|What version of GitLab are you using?'),
saasRadioLabel: __('GitLab.com (SaaS)'),
saasRadioHelp: __('Most common'),
selfManagedRadioLabel: __('GitLab (self-managed)'),
+ buttonNext: __('Next'),
+ buttonSave: __('Save'),
instanceURLInputLabel: s__('JiraService|GitLab instance URL'),
instanceURLInputDescription: s__('JiraService|For example: https://gitlab.example.com'),
},
@@ -66,30 +99,50 @@ export default {
<template>
<gl-form class="gl-max-w-62 gl-mx-auto" @submit.prevent="onSubmit">
- <gl-form-radio-group v-model="selected" class="gl-mb-3" name="gitlab_version">
- <gl-form-radio :value="$options.radioOptions.saas">
- {{ $options.i18n.saasRadioLabel }}
- <template #help>
- {{ $options.i18n.saasRadioHelp }}
- </template>
- </gl-form-radio>
- <gl-form-radio :value="$options.radioOptions.selfManaged">
- {{ $options.i18n.selfManagedRadioLabel }}
- </gl-form-radio>
- </gl-form-radio-group>
+ <div v-if="showVersonSelect">
+ <h5 class="gl-mb-5">{{ $options.i18n.title }}</h5>
+ <gl-form-radio-group v-model="selected" class="gl-mb-3" name="gitlab_version">
+ <gl-form-radio :value="$options.radioOptions.saas">
+ {{ $options.i18n.saasRadioLabel }}
+ <template #help>
+ {{ $options.i18n.saasRadioHelp }}
+ </template>
+ </gl-form-radio>
+ <gl-form-radio :value="$options.radioOptions.selfManaged">
+ {{ $options.i18n.selfManagedRadioLabel }}
+ </gl-form-radio>
+ </gl-form-radio-group>
+ <self-managed-alert v-if="isSelfManagedSelected" />
+
+ <div class="gl-display-flex gl-justify-content-end gl-mt-5">
+ <gl-button variant="confirm" type="submit" :loading="loading" data-testid="submit-button">{{
+ submitText
+ }}</gl-button>
+ </div>
+ </div>
- <gl-form-group
- v-if="isSelfManagedSelected"
- class="gl-ml-6"
- :label="$options.i18n.instanceURLInputLabel"
- :description="$options.i18n.instanceURLInputDescription"
- label-for="self-managed-instance-input"
- >
- <gl-form-input id="self-managed-instance-input" v-model="selfManagedBasePathInput" required />
- </gl-form-group>
+ <setup-instructions v-else-if="showSetupInstructions" @next="onSetupNext" @back="onSetupBack" />
- <div class="gl-display-flex gl-justify-content-end">
- <gl-button variant="confirm" type="submit" :loading="loading">{{ __('Save') }}</gl-button>
+ <div v-else-if="showSelfManagedInstanceInput">
+ <gl-form-group
+ :label="$options.i18n.instanceURLInputLabel"
+ :description="$options.i18n.instanceURLInputDescription"
+ label-for="self-managed-instance-input"
+ >
+ <gl-form-input
+ id="self-managed-instance-input"
+ v-model="selfManagedBasePathInput"
+ required
+ />
+ </gl-form-group>
+ <div class="gl-display-flex gl-justify-content-space-between">
+ <gl-button data-testid="back-button" @click.prevent="onSetupBack">{{
+ __('Back')
+ }}</gl-button>
+ <gl-button variant="confirm" type="submit" :loading="loading">{{
+ $options.i18n.buttonSave
+ }}</gl-button>
+ </div>
</div>
</gl-form>
</template>
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index 17766b4d162..f4061f3d375 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -1,5 +1,12 @@
<script>
-import { GlButton, GlButtonGroup, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import {
+ GlButton,
+ GlButtonGroup,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import { redirectTo } from '~/lib/utils/url_utility';
import {
@@ -49,6 +56,7 @@ export default {
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
inject: {
admin: {
@@ -178,11 +186,12 @@ export default {
<template v-if="canReadJob && canUpdateJob">
<gl-button
v-if="isActive"
- data-testid="cancel-button"
+ v-gl-tooltip
icon="cancel"
:title="$options.CANCEL"
:aria-label="$options.CANCEL"
:disabled="cancelBtnDisabled"
+ data-testid="cancel-button"
@click="cancelJob()"
/>
<template v-else-if="isScheduled">
@@ -191,6 +200,7 @@ export default {
</gl-button>
<gl-button
v-gl-modal-directive="$options.playJobModalId"
+ v-gl-tooltip
icon="play"
:title="$options.ACTIONS_START_NOW"
:aria-label="$options.ACTIONS_START_NOW"
@@ -206,6 +216,7 @@ export default {
</gl-sprintf>
</gl-modal>
<gl-button
+ v-gl-tooltip
icon="time-out"
:title="$options.ACTIONS_UNSCHEDULE"
:aria-label="$options.ACTIONS_UNSCHEDULE"
@@ -218,6 +229,7 @@ export default {
<!--Note: This is the manual job play button -->
<gl-button
v-if="manualJobPlayable"
+ v-gl-tooltip
icon="play"
:title="$options.ACTIONS_PLAY"
:aria-label="$options.ACTIONS_PLAY"
@@ -227,6 +239,7 @@ export default {
/>
<gl-button
v-else-if="isRetryable"
+ v-gl-tooltip
icon="retry"
:title="retryButtonTitle"
:aria-label="retryButtonTitle"
@@ -239,6 +252,7 @@ export default {
</template>
<gl-button
v-if="shouldDisplayArtifacts"
+ v-gl-tooltip
icon="download"
:title="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
:aria-label="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
diff --git a/app/assets/javascripts/labels/index.js b/app/assets/javascripts/labels/index.js
index 0d4113bba4c..c7c17607af6 100644
--- a/app/assets/javascripts/labels/index.js
+++ b/app/assets/javascripts/labels/index.js
@@ -120,16 +120,15 @@ export function initAdminLabels() {
const emptyState = document.querySelector('.js-admin-labels-empty-state');
function removeLabelSuccessCallback() {
- this.closest('li').classList.add('gl-display-none!');
+ this.closest('li.label-list-item').classList.add('gl-display-none!');
const labelsCount = document.querySelectorAll(
- 'ul.manage-labels-list li:not(.gl-display-none\\!)',
+ 'ul.manage-labels-list li.label-list-item:not(.gl-display-none\\!)',
).length;
// display the empty state if there are no more labels
if (labelsCount < 1 && !pagination && emptyState) {
emptyState.classList.remove('gl-display-none');
- labelsContainer.classList.add('gl-display-none');
}
}
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 317c401e404..198f2da385c 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -39,7 +39,7 @@ export const toggleContainerClasses = (containerEl, classList) => {
* Return a object mapping element dataset names to booleans.
*
* This is useful for data- attributes whose presense represent
- * a truthiness, no matter the value of the attribute. The absense of the
+ * a truthiness, no matter the value of the attribute. The absence of the
* attribute represents falsiness.
*
* This can be useful when Rails-provided boolean-like values are passed
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js
index 7500c152b6a..7c4b76fd62f 100644
--- a/app/assets/javascripts/projects/commit_box/info/index.js
+++ b/app/assets/javascripts/projects/commit_box/info/index.js
@@ -1,6 +1,5 @@
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
-import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
import initCommitPipelineStatus from './init_commit_pipeline_status';
@@ -14,7 +13,5 @@ export const initCommitBoxInfo = () => {
// Display pipeline mini graph for this commit
initCommitPipelineMiniGraph();
- initDetailsButton();
-
initCommitPipelineStatus();
};
diff --git a/app/assets/javascripts/projects/commit_box/info/init_details_button.js b/app/assets/javascripts/projects/commit_box/info/init_details_button.js
index bc2c16b9e83..667d6bd0250 100644
--- a/app/assets/javascripts/projects/commit_box/info/init_details_button.js
+++ b/app/assets/javascripts/projects/commit_box/info/init_details_button.js
@@ -1,7 +1,17 @@
export const initDetailsButton = () => {
- document.querySelector('.commit-info').addEventListener('click', function expand(e) {
- e.preventDefault();
- this.querySelector('.js-details-content').classList.remove('hide');
- this.querySelector('.js-details-expand').classList.add('gl-display-none');
+ const expandButton = document.querySelector('.js-details-expand');
+
+ if (!expandButton) {
+ return;
+ }
+
+ expandButton.addEventListener('click', (event) => {
+ const btn = event.target;
+ const contentEl = btn.parentElement.querySelector('.js-details-content');
+
+ if (contentEl) {
+ contentEl.classList.remove('hide');
+ btn.classList.add('gl-display-none');
+ }
});
};
diff --git a/app/assets/javascripts/projects/commit_box/info/load_branches.js b/app/assets/javascripts/projects/commit_box/info/load_branches.js
index d1136817cb3..8333e70b951 100644
--- a/app/assets/javascripts/projects/commit_box/info/load_branches.js
+++ b/app/assets/javascripts/projects/commit_box/info/load_branches.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import { sanitize } from '~/lib/dompurify';
import { __ } from '~/locale';
+import { initDetailsButton } from './init_details_button';
export const loadBranches = (containerSelector = '.js-commit-box-info') => {
const containerEl = document.querySelector(containerSelector);
@@ -14,6 +15,8 @@ export const loadBranches = (containerSelector = '.js-commit-box-info') => {
.get(commitPath)
.then(({ data }) => {
branchesEl.innerHTML = sanitize(data);
+
+ initDetailsButton();
})
.catch(() => {
branchesEl.textContent = __('Failed to load branches. Please try again.');
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue
index 9dc26ad10f5..8382ae8987b 100644
--- a/app/assets/javascripts/super_sidebar/components/nav_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue
@@ -7,6 +7,8 @@ import {
TRACKING_UNKNOWN_ID,
TRACKING_UNKNOWN_PANEL,
} from '~/super_sidebar/constants';
+import NavItemLink from './nav_item_link.vue';
+import NavItemRouterLink from './nav_item_router_link.vue';
export default {
i18n: {
@@ -18,6 +20,8 @@ export default {
GlButton,
GlIcon,
GlBadge,
+ NavItemLink,
+ NavItemRouterLink,
},
inject: {
pinnedItemIds: { default: { ids: [] } },
@@ -55,9 +59,6 @@ export default {
(typeof this.pillData === 'string' && this.pillData !== '')
);
},
- isActive() {
- return this.item.is_active;
- },
isPinnable() {
return this.panelSupportsPins && !this.isStatic;
},
@@ -86,27 +87,30 @@ export default {
return {
...this.$attrs,
...this.trackingProps,
- href: this.item.link,
- 'aria-current': this.isActive ? 'page' : null,
+ item: this.item,
'data-qa-submenu-item': this.item.title,
};
},
computedLinkClasses() {
return {
- 'gl-bg-t-gray-a-08': this.isActive,
'gl-py-2': this.isPinnable,
'gl-py-3': !this.isPinnable,
[this.item.link_classes]: this.item.link_classes,
...this.linkClasses,
};
},
+ navItemLinkComponent() {
+ return this.item.to ? NavItemRouterLink : NavItemLink;
+ },
},
};
</script>
<template>
<li>
- <a
+ <component
+ :is="navItemLinkComponent"
+ #default="{ isActive }"
v-bind="linkProps"
class="nav-item-link gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus"
:class="computedLinkClasses"
@@ -118,6 +122,7 @@ export default {
class="gl-absolute gl-left-2 gl-top-2 gl-bottom-2 gl-transition-slow"
aria-hidden="true"
style="width: 3px; border-radius: 3px; margin-right: 1px"
+ data-testid="active-indicator"
></div>
<div class="gl-flex-shrink-0 gl-w-6 gl-mx-3">
<slot name="icon">
@@ -162,6 +167,6 @@ export default {
@click.prevent="$emit('pin-remove', item.id)"
/>
</span>
- </a>
+ </component>
</li>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item_link.vue b/app/assets/javascripts/super_sidebar/components/nav_item_link.vue
new file mode 100644
index 00000000000..8358e96db94
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/nav_item_link.vue
@@ -0,0 +1,35 @@
+<script>
+import { NAV_ITEM_LINK_ACTIVE_CLASS } from '../constants';
+import { ariaCurrent } from '../utils';
+
+export default {
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isActive() {
+ return this.item.is_active;
+ },
+ linkProps() {
+ return {
+ href: this.item.link,
+ 'aria-current': ariaCurrent(this.isActive),
+ };
+ },
+ computedLinkClasses() {
+ return {
+ [NAV_ITEM_LINK_ACTIVE_CLASS]: this.isActive,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <a v-bind="linkProps" :class="computedLinkClasses">
+ <slot :is-active="isActive"></slot>
+ </a>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue b/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue
new file mode 100644
index 00000000000..78aca24d9a6
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue
@@ -0,0 +1,37 @@
+<script>
+import { NAV_ITEM_LINK_ACTIVE_CLASS } from '../constants';
+import { ariaCurrent } from '../utils';
+
+export default {
+ NAV_ITEM_LINK_ACTIVE_CLASS,
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ linkProps() {
+ return {
+ to: this.item.to,
+ };
+ },
+ },
+ methods: {
+ ariaCurrent,
+ },
+};
+</script>
+
+<template>
+ <router-link
+ #default="{ href, navigate, isActive }"
+ v-bind="linkProps"
+ :active-class="$options.NAV_ITEM_LINK_ACTIVE_CLASS"
+ custom
+ >
+ <a :href="href" :aria-current="ariaCurrent(isActive)" @click="navigate">
+ <slot :is-active="isActive"></slot>
+ </a>
+ </router-link>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/constants.js b/app/assets/javascripts/super_sidebar/constants.js
index 4a5d0bf637f..00ceaebe2cc 100644
--- a/app/assets/javascripts/super_sidebar/constants.js
+++ b/app/assets/javascripts/super_sidebar/constants.js
@@ -50,3 +50,5 @@ export const SIDEBAR_PINS_EXPANDED_COOKIE = 'sidebar_pinned_section_expanded';
export const SIDEBAR_COOKIE_EXPIRATION = 365 * 10;
export const DROPDOWN_Y_OFFSET = 4;
+
+export const NAV_ITEM_LINK_ACTIVE_CLASS = 'gl-bg-t-gray-a-08';
diff --git a/app/assets/javascripts/super_sidebar/utils.js b/app/assets/javascripts/super_sidebar/utils.js
index 4bbef67742c..3b17a35c5bc 100644
--- a/app/assets/javascripts/super_sidebar/utils.js
+++ b/app/assets/javascripts/super_sidebar/utils.js
@@ -83,3 +83,5 @@ export const formatContextSwitcherItems = (items) =>
avatar,
link,
}));
+
+export const ariaCurrent = (isActive) => (isActive ? 'page' : null);
diff --git a/app/controllers/oauth/jira_dvcs/authorizations_controller.rb b/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
index 82a6784d2d1..ba587944a36 100644
--- a/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
+++ b/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
@@ -8,6 +8,7 @@ class Oauth::JiraDvcs::AuthorizationsController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
+ before_action :reversible_end_of_life!
before_action :validate_redirect_uri, only: :new
feature_category :integrations
@@ -55,6 +56,17 @@ class Oauth::JiraDvcs::AuthorizationsController < ApplicationController
private
+ # The endpoints in this controller have been deprecated since 15.1.
+ #
+ # Due to uncertainty about the impact of a full removal in 16.0, all endpoints return `404`
+ # by default but we allow customers to toggle a flag to reverse this breaking change.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/362168#note_1347692683.
+ #
+ # TODO Make the breaking change irreversible https://gitlab.com/gitlab-org/gitlab/-/issues/408148.
+ def reversible_end_of_life!
+ render_404 unless Feature.enabled?(:jira_dvcs_end_of_life_amnesty)
+ end
+
# When using the GitHub Enterprise connector in Jira we receive the "repo" scope,
# this doesn't exist in GitLab but we can map it to our "api" scope.
def normalize_scope(scope)
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 64fc069b508..b4950cc60e3 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -164,7 +164,7 @@ module Types
definition_methods do
def resolve_type(object, context)
- # in the absense of other information, we cannot tell - just default to
+ # in the absence of other information, we cannot tell - just default to
# the core user type.
::Types::UserType
end
diff --git a/app/services/ci/pipelines/add_job_service.rb b/app/services/ci/pipelines/add_job_service.rb
index 1a5c8d0dccf..dfbb37cf0dc 100644
--- a/app/services/ci/pipelines/add_job_service.rb
+++ b/app/services/ci/pipelines/add_job_service.rb
@@ -18,12 +18,6 @@ module Ci
in_lock("ci:pipelines:#{pipeline.id}:add-job", ttl: LOCK_TIMEOUT, sleep_sec: LOCK_SLEEP, retries: LOCK_RETRIES) do
Ci::Pipeline.transaction do
- # This is used to reduce the deadlocks when partitioning `ci_builds`
- # since inserting into this table requires locks on all foreign keys
- # and we need to lock all the tables in a specific order for the
- # migration to succeed.
- Ci::Pipeline.connection.execute('LOCK "ci_pipelines", "ci_stages" IN ROW SHARE MODE;')
-
yield(job)
job.update_older_statuses_retried!
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index dde0b301c63..aa5543700a7 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -11,26 +11,29 @@
- else
= _('(removed)')
%td
- %strong.subheading.d-block.d-sm-none
- = _('Reported by %{reporter}').html_safe % { reporter: reporter ? link_to(reporter.name, reporter) : _('(removed)') }
- .light.gl-display-none.gl-sm-display-block
- = link_to(reporter.name, reporter)
- .light.small
- = time_ago_with_tooltip(abuse_report.created_at)
+ - if reporter
+ %strong.subheading.d-block.d-sm-none
+ = _('Reported by %{reporter}').html_safe % { reporter: reporter ? link_to(reporter.name, reporter) : _('(removed)') }
+ .light.gl-display-none.gl-sm-display-block
+ = link_to(reporter.name, reporter)
+ .light.small
+ = time_ago_with_tooltip(abuse_report.created_at)
+ - else
+ = _('(removed)')
%td
%strong.subheading.d-block.d-sm-none
= _('Message')
.message
= markdown_field(abuse_report, :message)
%td
- - if user
+ - if user && user != current_user
= render Pajamas::ButtonComponent.new(href: admin_abuse_report_path(abuse_report, remove_user: true), variant: :danger, block: true, button_options: { data: { confirm: _("USER %{user} WILL BE REMOVED! Are you sure?") % { user: user.name }, confirm_btn_variant: "danger", remote: true, method: :delete }, class: "js-remove-tr" }) do
= _('Remove user & report')
- - if user && !user.blocked?
- = render Pajamas::ButtonComponent.new(href: block_admin_user_path(user), block: true, button_options: { data: { confirm: _('USER WILL BE BLOCKED! Are you sure?'), method: :put } }) do
- = _('Block user')
- - else
- = render Pajamas::ButtonComponent.new(href: block_admin_user_path(user), block: true, disabled: true, button_options: { data: { confirm: _('USER WILL BE BLOCKED! Are you sure?'), method: :put } }) do
- = _('Already blocked')
- = render Pajamas::ButtonComponent.new(href: [:admin, abuse_report], block: true, button_options: { data: { remote: true, method: :delete }, class: "js-remove-tr" }) do
- = _('Remove report')
+ - if user.blocked?
+ = render Pajamas::ButtonComponent.new(href: block_admin_user_path(user), block: true, disabled: true, button_options: { data: { confirm: _('USER WILL BE BLOCKED! Are you sure?'), method: :put } }) do
+ = _('Already blocked')
+ - else
+ = render Pajamas::ButtonComponent.new(href: block_admin_user_path(user), block: true, button_options: { data: { confirm: _('USER WILL BE BLOCKED! Are you sure?'), method: :put } }) do
+ = _('Block user')
+ = render Pajamas::ButtonComponent.new(href: [:admin, abuse_report], block: true, button_options: { data: { remote: true, method: :delete }, class: "js-remove-tr" }) do
+ = _('Remove report')
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 73fe1941818..4ba69126906 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -15,9 +15,9 @@
.row
.col-md-6
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Group info:')
- - c.body do
+ - c.with_body do
%ul.content-list.content-list-items-padding
%li
= render Pajamas::AvatarComponent.new(@group, size: 64, alt: '')
@@ -69,10 +69,10 @@
= render_if_exists 'ldap_group_links/ldap_group_links_show', group: @group
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Projects')
= gl_badge_tag @group.projects.count
- - c.body do
+ - c.with_body do
%ul.content-list.content-list-items-padding
- @projects.each do |project|
%li
@@ -82,16 +82,16 @@
%span.float-right.light
%span.monospace= project.full_path + '.git'
- unless @projects.size < Kaminari.config.default_per_page
- - c.footer do
+ - c.with_footer do
= paginate @projects, param_name: 'projects_page', theme: 'gitlab'
- shared_projects = @group.shared_projects.sort_by(&:name)
- unless shared_projects.empty?
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Projects shared with %{group_name}') % { group_name: @group.name }
= gl_badge_tag shared_projects.size
- - c.body do
+ - c.with_body do
%ul.content-list.content-list-items-padding
- shared_projects.each do |project|
%li
@@ -108,11 +108,11 @@
= render 'shared/members/requests', membership_source: @group, group: @group, requesters: @requesters, force_mobile_view: true
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c|
- - c.header do
+ - c.with_header do
= html_escape(_("%{group_name} group members")) % { group_name: "<strong>#{html_escape(@group.name)}</strong>".html_safe }
= gl_badge_tag @group.users_count
= render 'shared/members/manage_access_button', path: group_group_members_path(@group)
- - c.body do
+ - c.with_body do
%ul.content-list.group-users-list.members-list
= render partial: 'shared/members/member',
collection: @members, as: :member,
@@ -120,5 +120,5 @@
group: @group,
current_user_is_group_owner: current_user_is_group_owner }
- unless @members.size < Kaminari.config.default_per_page
- - c.footer do
+ - c.with_footer do
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 68f0ed8a680..f4f64eadf21 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,7 +1,17 @@
-%li.label-list-item.gl-border-b.gl-py-3{ id: dom_id(label) }
- = render "shared/label_row", label: label.present(issuable_subject: nil)
- .label-actions-list
- = link_to edit_admin_label_path(label), class: 'btn btn-default gl-button btn-default-tertiary label-action has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria: { label: _('Edit') } do
- = sprite_icon('pencil')
- = link_to admin_label_path(label), class: 'btn btn-default gl-button btn-default-tertiary hover-red js-remove-label label-action has-tooltip', title: _('Delete'), data: { placement: 'bottom', confirm: _('Are you sure you want to delete this label?'), confirm_btn_variant: 'danger' }, aria: { label: _('Delete label') }, method: :delete, remote: true do
- = sprite_icon('remove')
+%li.label-list-item.gl-list-style-none.gl-py-3{ id: dom_id(label) }
+ .label-content.gl-px-3.gl-py-2.gl-rounded-base
+ = render "shared/label_row", label: label.present(issuable_subject: nil)
+ .label-actions-list.gl-display-inline-block
+ .dropdown
+ = render Pajamas::ButtonComponent.new(category: :tertiary,
+ size: :small,
+ icon: 'ellipsis_v',
+ button_options: { class: 'js-label-options-dropdown gl-ml-3', 'aria_label': _('Label actions dropdown'), title: _('Label actions dropdown'), data: { toggle: 'dropdown' } })
+ .dropdown-menu.dropdown-menu-right
+ %ul
+ %li
+ = link_to edit_admin_label_path(label), class: 'btn gl-btn label-action dropdown-item btn-link' do
+ = _('Edit')
+ %li
+ = link_to admin_label_path(label), class: 'btn gl-btn js-remove-label dropdown-item btn-link gl-text-red-500!', data: { confirm: _('Are you sure you want to delete this label?'), confirm_btn_variant: 'danger' }, aria: { label: _('Delete label') }, method: :delete, remote: true do
+ = _('Delete')
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index f559c2ec278..8680bae5207 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -1,32 +1,34 @@
- page_title _("Labels")
-%div
- = render Pajamas::ButtonComponent.new(variant: :confirm,
- href: new_admin_label_path,
- button_options: { class: 'float-right' }) do
- = _('New label')
- %h1.page-title.gl-font-size-h-display
- = _('Labels')
-- if @labels.present?
- .labels.labels-container.admin-labels.js-admin-labels-container
- %ul.manage-labels-list
- = render @labels
+.gl-sm-display-flex.gl-border-bottom-0.gl-mt-4.gl-lg-align-items-center
+ .gl-text-gray-600.gl-flex-grow-1
+ = s_('AdminLabels|Labels created here will be automatically added to new projects.')
+ .nav-controls.gl-mt-2.gl-sm-mt-0.gl-display-flex.gl-align-items-center
+ = render Pajamas::ButtonComponent.new(variant: :confirm,
+ href: new_admin_label_path) do
+ = _('New label')
- = paginate @labels, theme: 'gitlab'
+.labels.labels-container.admin-labels.js-admin-labels-container.gl-mt-4
+ .other-labels.gl-rounded-base.gl-border.gl-bg-gray-10
+ .gl-px-5.gl-py-4.gl-bg-white.gl-rounded-base.gl-border-b{ class: 'gl-rounded-bottom-left-none! gl-rounded-bottom-right-none!' }
+ %h3.card-title.h5.gl-m-0.gl-relative.gl-line-height-24
+ = _('Labels')
+ %ul.manage-labels-list.js-other-labels.gl-px-3.gl-rounded-base
+ - if @labels.present?
+ = render @labels
+ .js-admin-labels-empty-state{ class: ('gl-display-none' if @labels.present?) }
+ %section.row.empty-state.gl-text-center
+ .col-12
+ .svg-content.svg-150
+ = image_tag 'illustrations/empty-state/empty-labels-md.svg'
+ .col-12
+ .gl-mx-auto.gl-my-0.gl-p-5
+ %h1.gl-font-size-h-display.gl-line-height-36.h4
+ = s_('AdminLabels|Define your default set of project labels')
+ %p
+ = s_('AdminLabels|They can be used to categorize issues and merge requests.')
+ .gl-display-flex.gl-flex-wrap.gl-justify-content-center
+ = render Pajamas::ButtonComponent.new(href: new_admin_label_path) do
+ = _('New label')
-.js-admin-labels-empty-state{ class: ('gl-display-none' if @labels.present?) }
- %section.row.empty-state.gl-text-center
- .col-12
- .svg-content.svg-150
- = image_tag 'illustrations/empty-state/empty-labels-md.svg'
- .col-12
- .gl-mx-auto.gl-my-0.gl-p-5
- %h1.gl-font-size-h-display.gl-line-height-36.h4
- = s_('AdminLabels|Define your default set of project labels')
- %p.gl-mb-0
- = s_('AdminLabels|Labels created here will be automatically added to new projects.')
- %p
- = s_('AdminLabels|They can be used to categorize issues and merge requests.')
- .gl-display-flex.gl-flex-wrap.gl-justify-content-center
- = render Pajamas::ButtonComponent.new(href: new_admin_label_path) do
- = _('New label')
+ = paginate @labels, theme: 'gitlab'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 2803bec49c3..2ba305ebaea 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -24,9 +24,9 @@
.row
.col-md-6
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Project info:')
- - c.body do
+ - c.with_body do
%ul.content-list
%li{ class: 'gl-px-5!' }
%span.light
@@ -134,9 +134,9 @@
= render_if_exists 'admin/projects/geo_status_widget', locals: { project: @project }
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }) do |c|
- - c.header do
+ - c.with_header do
= s_('ProjectSettings|Transfer project')
- - c.body do
+ - c.with_body do
= gitlab_ui_form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row
.col-sm-3.col-form-label
@@ -149,9 +149,9 @@
= f.submit _('Transfer'), pajamas_button: true
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5 repository-check' }) do |c|
- - c.header do
+ - c.with_header do
= _("Repository check")
- - c.body do
+ - c.with_body do
= gitlab_ui_form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f|
.form-group
- if @project.last_repository_check_at.nil?
@@ -171,35 +171,35 @@
.col-md-6
- if @group
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }, footer_options: { class: 'gl-p-4' }) do |c|
- - c.header do
+ - c.with_header do
%strong= @group.name
= _('group members')
= gl_badge_tag @group_members.size
= render 'shared/members/manage_access_button', path: group_group_members_path(@group)
- - c.body do
+ - c.with_body do
%ul.content-list.members-list
= render partial: 'shared/members/member',
collection: @group_members, as: :member,
locals: { membership_source: @project,
group: @group,
current_user_is_group_owner: current_user_is_group_owner }
- - c.footer do
+ - c.with_footer do
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
= render 'shared/members/requests', membership_source: @project, group: @group, requesters: @requesters
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-p-0' }, footer_options: { class: 'gl-p-4' }) do |c|
- - c.header do
+ - c.with_header do
%strong= @project.name
= _('project members')
= gl_badge_tag @project.users.size
= render 'shared/members/manage_access_button', path: project_project_members_path(@project)
- - c.body do
+ - c.with_body do
%ul.content-list.project_members.members-list
= render partial: 'shared/members/member',
collection: @project_members, as: :member,
locals: { membership_source: @project,
group: @group,
current_user_is_group_owner: current_user_is_group_owner }
- - c.footer do
+ - c.with_footer do
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml
index de48129dfe6..b4f61a1b665 100644
--- a/app/views/admin/users/_profile.html.haml
+++ b/app/views/admin/users/_profile.html.haml
@@ -1,7 +1,7 @@
= render Pajamas::CardComponent.new(body_options: { class: 'gl-py-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Profile')
- - c.body do
+ - c.with_body do
%ul.content-list
%li
%span.light= _('Member since')
diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml
index 2f77e83ac49..ce2d684b046 100644
--- a/app/views/admin/users/_projects.html.haml
+++ b/app/views/admin/users/_projects.html.haml
@@ -1,17 +1,17 @@
- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Projects contributed to')
- - c.body do
+ - c.with_body do
= render 'shared/projects/list',
projects: contributed_projects.sort_by(&:star_count).reverse,
projects_limit: 5, stars: true, avatar: false, compact_mode: true
- if local_assigns.has_key?(:projects) && projects.present?
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-0' }) do |c|
- - c.header do
+ - c.with_header do
= _('Personal projects')
- - c.body do
+ - c.with_body do
= render 'shared/projects/list',
projects: projects.sort_by(&:star_count).reverse,
projects_limit: 10, stars: true, avatar: false, compact_mode: true
diff --git a/app/views/admin/users/_user_detail_note.html.haml b/app/views/admin/users/_user_detail_note.html.haml
index c8625833a70..65ef1c0710d 100644
--- a/app/views/admin/users/_user_detail_note.html.haml
+++ b/app/views/admin/users/_user_detail_note.html.haml
@@ -1,7 +1,7 @@
- if @user.note.present?
- text = @user.note
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-pb-0'}) do |c|
- - c.header do
+ - c.with_header do
= _('Admin Note')
- - c.body do
+ - c.with_body do
%p= text
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index ff87cf8f866..1f3e8f4bba2 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -5,9 +5,9 @@
- if @user.groups.any?
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-0 gl-px-0'}) do |c|
- - c.header do
+ - c.with_header do
= _('Groups')
- - c.body do
+ - c.with_body do
%ul.hover-list
- @user.group_members.includes(:source).each do |group_member| # rubocop: disable CodeReuse/ActiveRecord
- group = group_member.group
@@ -31,9 +31,9 @@
.col-md-6
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-0 gl-px-0'}) do |c|
- - c.header do
+ - c.with_header do
= _('Joined projects (%{projects_count})') % { projects_count: @joined_projects.count }
- - c.body do
+ - c.with_body do
%ul.hover-list
- @joined_projects.sort_by(&:full_name).each do |project|
- member = project.team.find_member(@user.id)
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index f7d4121e6e0..ea6525e1b96 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -6,9 +6,9 @@
.row
.col-md-6
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-2'}) do |c|
- - c.header do
+ - c.with_header do
= @user.name
- - c.body do
+ - c.with_body do
%ul.content-list
%li
= render Pajamas::AvatarComponent.new(@user, size: 64, class: 'gl-mr-3')
@@ -22,9 +22,9 @@
= render 'admin/users/profile', user: @user
= render 'admin/users/user_detail_note'
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-py-2'}) do |c|
- - c.header do
+ - c.with_header do
= _('Account:')
- - c.body do
+ - c.with_body do
%ul.content-list
%li
%span.light= _('Name:')
diff --git a/app/views/import/github/details.html.haml b/app/views/import/github/details.html.haml
index 9056af1f129..d5d36a9b581 100644
--- a/app/views/import/github/details.html.haml
+++ b/app/views/import/github/details.html.haml
@@ -1,4 +1,4 @@
- add_to_breadcrumbs _('Create a new project'), new_project_path
- page_title s_('Import|GitHub import details')
-.js-import-details
+.js-import-details{ data: { failures_path: failures_import_github_path } }
diff --git a/config/feature_flags/development/ai_ci_config_generator.yml b/config/feature_flags/development/ai_ci_config_generator.yml
new file mode 100644
index 00000000000..51e5f539233
--- /dev/null
+++ b/config/feature_flags/development/ai_ci_config_generator.yml
@@ -0,0 +1,10 @@
+---
+name: ai_ci_config_generator
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120141
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/407799
+milestone: '16.0'
+type: development
+# This is an ai experiment related to the pipeline authoring groups features
+# We use the authors group so the MR label matches for danger
+group: group::pipeline execution
+default_enabled: false
diff --git a/data/removals/16_0/16-0-external-field-grapql-release-asset-link-type.yml b/data/removals/16_0/16-0-external-field-grapql-release-asset-link-type.yml
new file mode 100644
index 00000000000..310415ad845
--- /dev/null
+++ b/data/removals/16_0/16-0-external-field-grapql-release-asset-link-type.yml
@@ -0,0 +1,11 @@
+- title: "Removed `external` field from GraphQL `ReleaseAssetLink` type"
+ announcement_milestone: "15.9"
+ announcement_date: "2023-02-22"
+ removal_milestone: "16.0"
+ removal_date: "2023-05-22"
+ breaking_change: true
+ reporter: missy-davies
+ stage: Deploy
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388983
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ From GitLab 15.9, all Release links are external. The `external` field of the `ReleaseAssetLink` type was deprecated in 15.9, and removed in GitLab 16.0.
diff --git a/data/removals/16_0/16-0-external-field-releases-release-links-api.yml b/data/removals/16_0/16-0-external-field-releases-release-links-api.yml
new file mode 100644
index 00000000000..9047bfd4adb
--- /dev/null
+++ b/data/removals/16_0/16-0-external-field-releases-release-links-api.yml
@@ -0,0 +1,11 @@
+- title: "Removed `external` field from Releases and Release link APIs"
+ announcement_milestone: "15.9"
+ announcement_date: "2023-02-22"
+ removal_milestone: "16.0"
+ removal_date: "2023-05-22"
+ breaking_change: true
+ reporter: missy-davies
+ stage: Deploy
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388984
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ From GitLab 15.9, all Release links are external. The `external` field in the Releases and Release link APIs was deprecated in 15.9, and removed in GitLab 16.0.
diff --git a/db/docs/p_ci_builds.yml b/db/docs/p_ci_builds.yml
deleted file mode 100644
index 3fafd1e177f..00000000000
--- a/db/docs/p_ci_builds.yml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-table_name: p_ci_builds
-classes:
-- Ci::Bridge
-- Ci::Build
-- Ci::Processable
-- CommitStatus
-- GenericCommitStatus
-feature_categories:
-- continuous_integration
-description: Routing table for ci_builds
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115485
-milestone: '16.0'
-gitlab_schema: gitlab_ci
diff --git a/db/post_migrate/20230501090213_convert_ci_builds_to_list_partitioning.rb b/db/post_migrate/20230501090213_convert_ci_builds_to_list_partitioning.rb
index bfc0b43f8f5..2361ee1b97e 100644
--- a/db/post_migrate/20230501090213_convert_ci_builds_to_list_partitioning.rb
+++ b/db/post_migrate/20230501090213_convert_ci_builds_to_list_partitioning.rb
@@ -1,85 +1,11 @@
# frozen_string_literal: true
class ConvertCiBuildsToListPartitioning < Gitlab::Database::Migration[2.1]
- include Gitlab::Database::PartitioningMigrationHelpers
-
- disable_ddl_transaction!
-
- TABLE_NAME = :ci_builds
- PARENT_TABLE_NAME = :p_ci_builds
- FIRST_PARTITION = 100
- PARTITION_COLUMN = :partition_id
- FOREIGN_KEYS = {
- p_ci_builds_metadata: :fk_e20479742e_p,
- p_ci_runner_machine_builds: :fk_bb490f12fe_p
- }
-
def up
- convert_table_to_first_list_partition(
- table_name: TABLE_NAME,
- partitioning_column: PARTITION_COLUMN,
- parent_table_name: PARENT_TABLE_NAME,
- initial_partitioning_value: FIRST_PARTITION,
- lock_tables: %w[ci_pipelines ci_stages ci_builds ci_resource_groups]
- )
+ # no-op to mitigate https://gitlab.com/gitlab-com/gl-infra/production/-/issues/13818
end
def down
- # rubocop:disable Migration/WithLockRetriesDisallowedMethod
- with_lock_retries(raise_on_exhaustion: true) do
- drop_foreign_keys
-
- execute(<<~SQL)
- ALTER TABLE #{PARENT_TABLE_NAME} DETACH PARTITION #{TABLE_NAME};
- ALTER SEQUENCE ci_builds_id_seq OWNED BY #{TABLE_NAME}.id;
- SQL
-
- drop_table PARENT_TABLE_NAME
- recreate_partition_foreign_keys
- end
- # rubocop:enable Migration/WithLockRetriesDisallowedMethod
-
- finalize_foreign_keys_creation
-
- prepare_constraint_for_list_partitioning(
- table_name: TABLE_NAME,
- partitioning_column: PARTITION_COLUMN,
- parent_table_name: PARENT_TABLE_NAME,
- initial_partitioning_value: FIRST_PARTITION
- )
- end
-
- private
-
- def drop_foreign_keys
- FOREIGN_KEYS.each do |source, name|
- remove_foreign_key_if_exists source, name: name
- end
- end
-
- def recreate_partition_foreign_keys
- FOREIGN_KEYS.each do |source, name|
- Gitlab::Database::PostgresPartitionedTable.each_partition(source) do |partition|
- execute(<<~SQL)
- ALTER TABLE #{partition.identifier}
- ADD CONSTRAINT #{name} FOREIGN KEY (partition_id, build_id)
- REFERENCES #{TABLE_NAME}(partition_id, id)
- ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
- SQL
- end
- end
- end
-
- def finalize_foreign_keys_creation
- FOREIGN_KEYS.each do |source, name|
- add_concurrent_partitioned_foreign_key(source, TABLE_NAME,
- column: [:partition_id, :build_id],
- target_column: [:partition_id, :id],
- reverse_lock_order: true,
- on_update: :cascade,
- on_delete: :cascade,
- name: name
- )
- end
+ # no-op to mitigate https://gitlab.com/gitlab-com/gl-infra/production/-/issues/13818
end
end
diff --git a/db/structure.sql b/db/structure.sql
index cce1aac348a..db1260e527c 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -13073,7 +13073,7 @@ CREATE TABLE ci_build_trace_metadata (
partition_id bigint DEFAULT 100 NOT NULL
);
-CREATE TABLE p_ci_builds (
+CREATE TABLE ci_builds (
status character varying,
finished_at timestamp without time zone,
created_at timestamp without time zone,
@@ -13118,9 +13118,9 @@ CREATE TABLE p_ci_builds (
id bigint NOT NULL,
stage_id bigint,
partition_id bigint NOT NULL,
- CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
-)
-PARTITION BY LIST (partition_id);
+ CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL)),
+ CONSTRAINT partitioning_constraint CHECK ((partition_id = 100))
+);
CREATE SEQUENCE ci_builds_id_seq
START WITH 1
@@ -13129,56 +13129,7 @@ CREATE SEQUENCE ci_builds_id_seq
NO MAXVALUE
CACHE 1;
-ALTER SEQUENCE ci_builds_id_seq OWNED BY p_ci_builds.id;
-
-CREATE TABLE ci_builds (
- status character varying,
- finished_at timestamp without time zone,
- created_at timestamp without time zone,
- updated_at timestamp without time zone,
- started_at timestamp without time zone,
- runner_id integer,
- coverage double precision,
- commit_id integer,
- name character varying,
- options text,
- allow_failure boolean DEFAULT false NOT NULL,
- stage character varying,
- trigger_request_id integer,
- stage_idx integer,
- tag boolean,
- ref character varying,
- user_id integer,
- type character varying,
- target_url character varying,
- description character varying,
- project_id integer,
- erased_by_id integer,
- erased_at timestamp without time zone,
- artifacts_expire_at timestamp without time zone,
- environment character varying,
- "when" character varying,
- yaml_variables text,
- queued_at timestamp without time zone,
- lock_version integer DEFAULT 0,
- coverage_regex character varying,
- auto_canceled_by_id integer,
- retried boolean,
- protected boolean,
- failure_reason integer,
- scheduled_at timestamp with time zone,
- token_encrypted character varying,
- upstream_pipeline_id integer,
- resource_group_id bigint,
- waiting_for_resource_at timestamp with time zone,
- processed boolean,
- scheduling_type smallint,
- id bigint DEFAULT nextval('ci_builds_id_seq'::regclass) NOT NULL,
- stage_id bigint,
- partition_id bigint NOT NULL,
- CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
-);
-ALTER TABLE ONLY p_ci_builds ATTACH PARTITION ci_builds FOR VALUES IN ('100');
+ALTER SEQUENCE ci_builds_id_seq OWNED BY ci_builds.id;
CREATE TABLE p_ci_builds_metadata (
project_id integer NOT NULL,
@@ -24886,6 +24837,8 @@ ALTER TABLE ONLY ci_build_pending_states ALTER COLUMN id SET DEFAULT nextval('ci
ALTER TABLE ONLY ci_build_trace_chunks ALTER COLUMN id SET DEFAULT nextval('ci_build_trace_chunks_id_seq'::regclass);
+ALTER TABLE ONLY ci_builds ALTER COLUMN id SET DEFAULT nextval('ci_builds_id_seq'::regclass);
+
ALTER TABLE ONLY ci_builds_runner_session ALTER COLUMN id SET DEFAULT nextval('ci_builds_runner_session_id_seq'::regclass);
ALTER TABLE ONLY ci_daily_build_group_report_results ALTER COLUMN id SET DEFAULT nextval('ci_daily_build_group_report_results_id_seq'::regclass);
@@ -25398,8 +25351,6 @@ ALTER TABLE ONLY operations_user_lists ALTER COLUMN id SET DEFAULT nextval('oper
ALTER TABLE ONLY organizations ALTER COLUMN id SET DEFAULT nextval('organizations_id_seq'::regclass);
-ALTER TABLE ONLY p_ci_builds ALTER COLUMN id SET DEFAULT nextval('ci_builds_id_seq'::regclass);
-
ALTER TABLE ONLY p_ci_builds_metadata ALTER COLUMN id SET DEFAULT nextval('ci_builds_metadata_id_seq'::regclass);
ALTER TABLE ONLY packages_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_build_infos_id_seq'::regclass);
@@ -26715,9 +26666,6 @@ ALTER TABLE ONLY p_ci_builds_metadata
ALTER TABLE ONLY ci_builds_metadata
ADD CONSTRAINT ci_builds_metadata_pkey PRIMARY KEY (id, partition_id);
-ALTER TABLE ONLY p_ci_builds
- ADD CONSTRAINT p_ci_builds_pkey PRIMARY KEY (id, partition_id);
-
ALTER TABLE ONLY ci_builds
ADD CONSTRAINT ci_builds_pkey PRIMARY KEY (id, partition_id);
@@ -29332,8 +29280,6 @@ CREATE INDEX ca_aggregations_last_full_run_at ON analytics_cycle_analytics_aggre
CREATE INDEX ca_aggregations_last_incremental_run_at ON analytics_cycle_analytics_aggregations USING btree (last_incremental_run_at NULLS FIRST) WHERE (enabled IS TRUE);
-CREATE INDEX p_ci_builds_status_created_at_project_id_idx ON ONLY p_ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text);
-
CREATE INDEX ci_builds_gitlab_monitor_metrics ON ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text);
CREATE UNIQUE INDEX ci_job_token_scope_links_source_and_target_project_direction ON ci_job_token_project_scope_links USING btree (source_project_id, target_project_id, direction);
@@ -29946,76 +29892,40 @@ CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata US
CREATE INDEX index_ci_builds_metadata_on_project_id ON ci_builds_metadata USING btree (project_id);
-CREATE INDEX p_ci_builds_auto_canceled_by_id_idx ON ONLY p_ci_builds USING btree (auto_canceled_by_id);
-
CREATE INDEX index_ci_builds_on_auto_canceled_by_id ON ci_builds USING btree (auto_canceled_by_id);
-CREATE INDEX p_ci_builds_commit_id_stage_idx_created_at_idx ON ONLY p_ci_builds USING btree (commit_id, stage_idx, created_at);
-
CREATE INDEX index_ci_builds_on_commit_id_and_stage_idx_and_created_at ON ci_builds USING btree (commit_id, stage_idx, created_at);
-CREATE INDEX p_ci_builds_commit_id_status_type_idx ON ONLY p_ci_builds USING btree (commit_id, status, type);
-
CREATE INDEX index_ci_builds_on_commit_id_and_status_and_type ON ci_builds USING btree (commit_id, status, type);
-CREATE INDEX p_ci_builds_commit_id_type_name_ref_idx ON ONLY p_ci_builds USING btree (commit_id, type, name, ref);
-
CREATE INDEX index_ci_builds_on_commit_id_and_type_and_name_and_ref ON ci_builds USING btree (commit_id, type, name, ref);
-CREATE INDEX p_ci_builds_commit_id_type_ref_idx ON ONLY p_ci_builds USING btree (commit_id, type, ref);
-
CREATE INDEX index_ci_builds_on_commit_id_and_type_and_ref ON ci_builds USING btree (commit_id, type, ref);
-CREATE INDEX p_ci_builds_commit_id_artifacts_expire_at_id_idx ON ONLY p_ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
-
CREATE INDEX index_ci_builds_on_commit_id_artifacts_expired_at_and_id ON ci_builds USING btree (commit_id, artifacts_expire_at, id) WHERE (((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])));
-CREATE INDEX p_ci_builds_project_id_id_idx ON ONLY p_ci_builds USING btree (project_id, id);
-
CREATE INDEX index_ci_builds_on_project_id_and_id ON ci_builds USING btree (project_id, id);
-CREATE INDEX p_ci_builds_project_id_name_ref_idx ON ONLY p_ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL)));
-
CREATE INDEX index_ci_builds_on_project_id_and_name_and_ref ON ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL)));
-CREATE INDEX p_ci_builds_resource_group_id_status_commit_id_idx ON ONLY p_ci_builds USING btree (resource_group_id, status, commit_id) WHERE (resource_group_id IS NOT NULL);
-
CREATE INDEX index_ci_builds_on_resource_group_and_status_and_commit_id ON ci_builds USING btree (resource_group_id, status, commit_id) WHERE (resource_group_id IS NOT NULL);
-CREATE INDEX p_ci_builds_runner_id_id_idx ON ONLY p_ci_builds USING btree (runner_id, id DESC);
-
CREATE INDEX index_ci_builds_on_runner_id_and_id_desc ON ci_builds USING btree (runner_id, id DESC);
-CREATE INDEX p_ci_builds_stage_id_idx ON ONLY p_ci_builds USING btree (stage_id);
-
CREATE INDEX index_ci_builds_on_stage_id ON ci_builds USING btree (stage_id);
-CREATE INDEX p_ci_builds_status_type_runner_id_idx ON ONLY p_ci_builds USING btree (status, type, runner_id);
-
CREATE INDEX index_ci_builds_on_status_and_type_and_runner_id ON ci_builds USING btree (status, type, runner_id);
-CREATE INDEX p_ci_builds_updated_at_idx ON ONLY p_ci_builds USING btree (updated_at);
-
CREATE INDEX index_ci_builds_on_updated_at ON ci_builds USING btree (updated_at);
-CREATE INDEX p_ci_builds_upstream_pipeline_id_idx ON ONLY p_ci_builds USING btree (upstream_pipeline_id) WHERE (upstream_pipeline_id IS NOT NULL);
-
CREATE INDEX index_ci_builds_on_upstream_pipeline_id ON ci_builds USING btree (upstream_pipeline_id) WHERE (upstream_pipeline_id IS NOT NULL);
-CREATE INDEX p_ci_builds_user_id_idx ON ONLY p_ci_builds USING btree (user_id);
-
CREATE INDEX index_ci_builds_on_user_id ON ci_builds USING btree (user_id);
-CREATE INDEX p_ci_builds_user_id_created_at_idx ON ONLY p_ci_builds USING btree (user_id, created_at) WHERE ((type)::text = 'Ci::Build'::text);
-
CREATE INDEX index_ci_builds_on_user_id_and_created_at_and_type_eq_ci_build ON ci_builds USING btree (user_id, created_at) WHERE ((type)::text = 'Ci::Build'::text);
-CREATE INDEX p_ci_builds_project_id_status_idx ON ONLY p_ci_builds USING btree (project_id, status) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])));
-
CREATE INDEX index_ci_builds_project_id_and_status_for_live_jobs_partial2 ON ci_builds USING btree (project_id, status) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])));
-CREATE INDEX p_ci_builds_runner_id_idx ON ONLY p_ci_builds USING btree (runner_id) WHERE (((status)::text = 'running'::text) AND ((type)::text = 'Ci::Build'::text));
-
CREATE INDEX index_ci_builds_runner_id_running ON ci_builds USING btree (runner_id) WHERE (((status)::text = 'running'::text) AND ((type)::text = 'Ci::Build'::text));
CREATE UNIQUE INDEX index_ci_builds_runner_session_on_build_id ON ci_builds_runner_session USING btree (build_id);
@@ -31830,8 +31740,6 @@ CREATE INDEX index_pages_domains_on_verified_at_and_enabled_until ON pages_domai
CREATE INDEX index_pages_domains_on_wildcard ON pages_domains USING btree (wildcard);
-CREATE INDEX p_ci_builds_user_id_name_idx ON ONLY p_ci_builds USING btree (user_id, name) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('secret_detection'::character varying)::text])));
-
CREATE INDEX index_partial_ci_builds_on_user_id_name_parser_features ON ci_builds USING btree (user_id, name) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('secret_detection'::character varying)::text])));
CREATE INDEX index_pat_on_user_id_and_expires_at ON personal_access_tokens USING btree (user_id, expires_at);
@@ -32366,12 +32274,8 @@ CREATE UNIQUE INDEX index_search_namespace_index_assignments_uniqueness_index_ty
CREATE UNIQUE INDEX index_search_namespace_index_assignments_uniqueness_on_index_id ON search_namespace_index_assignments USING btree (namespace_id, search_index_id);
-CREATE INDEX p_ci_builds_user_id_name_created_at_idx ON ONLY p_ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
-
CREATE INDEX index_secure_ci_builds_on_user_id_name_created_at ON ci_builds USING btree (user_id, name, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text, ('secret_detection'::character varying)::text])));
-CREATE INDEX p_ci_builds_name_id_idx ON ONLY p_ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
-
CREATE INDEX index_security_ci_builds_on_name_and_id_parser_features ON ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('coverage_fuzzing'::character varying)::text, ('license_scanning'::character varying)::text, ('apifuzzer_fuzz'::character varying)::text, ('apifuzzer_fuzz_dnd'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_security_policy_configurations_on_bot_user_id ON security_orchestration_policy_configurations USING btree (bot_user_id) WHERE (bot_user_id IS NOT NULL);
@@ -33074,10 +32978,6 @@ CREATE INDEX note_mentions_temp_index ON notes USING btree (id, noteable_type) W
CREATE UNIQUE INDEX one_canonical_wiki_page_slug_per_metadata ON wiki_page_slugs USING btree (wiki_page_meta_id) WHERE (canonical = true);
-CREATE INDEX p_ci_builds_scheduled_at_idx ON ONLY p_ci_builds USING btree (scheduled_at) WHERE ((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text));
-
-CREATE UNIQUE INDEX p_ci_builds_token_encrypted_partition_id_idx ON ONLY p_ci_builds USING btree (token_encrypted, partition_id) WHERE (token_encrypted IS NOT NULL);
-
CREATE INDEX package_name_index ON packages_packages USING btree (name);
CREATE INDEX packages_packages_failed_verification ON packages_package_files USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3);
@@ -34498,12 +34398,8 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p
ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
-ALTER INDEX p_ci_builds_status_created_at_project_id_idx ATTACH PARTITION ci_builds_gitlab_monitor_metrics;
-
ALTER INDEX p_ci_builds_metadata_pkey ATTACH PARTITION ci_builds_metadata_pkey;
-ALTER INDEX p_ci_builds_pkey ATTACH PARTITION ci_builds_pkey;
-
ALTER INDEX p_ci_builds_metadata_build_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts;
ALTER INDEX p_ci_builds_metadata_build_id_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_and_id_and_interruptible;
@@ -34512,52 +34408,6 @@ ALTER INDEX p_ci_builds_metadata_build_id_partition_id_idx ATTACH PARTITION inde
ALTER INDEX p_ci_builds_metadata_project_id_idx ATTACH PARTITION index_ci_builds_metadata_on_project_id;
-ALTER INDEX p_ci_builds_auto_canceled_by_id_idx ATTACH PARTITION index_ci_builds_on_auto_canceled_by_id;
-
-ALTER INDEX p_ci_builds_commit_id_stage_idx_created_at_idx ATTACH PARTITION index_ci_builds_on_commit_id_and_stage_idx_and_created_at;
-
-ALTER INDEX p_ci_builds_commit_id_status_type_idx ATTACH PARTITION index_ci_builds_on_commit_id_and_status_and_type;
-
-ALTER INDEX p_ci_builds_commit_id_type_name_ref_idx ATTACH PARTITION index_ci_builds_on_commit_id_and_type_and_name_and_ref;
-
-ALTER INDEX p_ci_builds_commit_id_type_ref_idx ATTACH PARTITION index_ci_builds_on_commit_id_and_type_and_ref;
-
-ALTER INDEX p_ci_builds_commit_id_artifacts_expire_at_id_idx ATTACH PARTITION index_ci_builds_on_commit_id_artifacts_expired_at_and_id;
-
-ALTER INDEX p_ci_builds_project_id_id_idx ATTACH PARTITION index_ci_builds_on_project_id_and_id;
-
-ALTER INDEX p_ci_builds_project_id_name_ref_idx ATTACH PARTITION index_ci_builds_on_project_id_and_name_and_ref;
-
-ALTER INDEX p_ci_builds_resource_group_id_status_commit_id_idx ATTACH PARTITION index_ci_builds_on_resource_group_and_status_and_commit_id;
-
-ALTER INDEX p_ci_builds_runner_id_id_idx ATTACH PARTITION index_ci_builds_on_runner_id_and_id_desc;
-
-ALTER INDEX p_ci_builds_stage_id_idx ATTACH PARTITION index_ci_builds_on_stage_id;
-
-ALTER INDEX p_ci_builds_status_type_runner_id_idx ATTACH PARTITION index_ci_builds_on_status_and_type_and_runner_id;
-
-ALTER INDEX p_ci_builds_updated_at_idx ATTACH PARTITION index_ci_builds_on_updated_at;
-
-ALTER INDEX p_ci_builds_upstream_pipeline_id_idx ATTACH PARTITION index_ci_builds_on_upstream_pipeline_id;
-
-ALTER INDEX p_ci_builds_user_id_idx ATTACH PARTITION index_ci_builds_on_user_id;
-
-ALTER INDEX p_ci_builds_user_id_created_at_idx ATTACH PARTITION index_ci_builds_on_user_id_and_created_at_and_type_eq_ci_build;
-
-ALTER INDEX p_ci_builds_project_id_status_idx ATTACH PARTITION index_ci_builds_project_id_and_status_for_live_jobs_partial2;
-
-ALTER INDEX p_ci_builds_runner_id_idx ATTACH PARTITION index_ci_builds_runner_id_running;
-
-ALTER INDEX p_ci_builds_user_id_name_idx ATTACH PARTITION index_partial_ci_builds_on_user_id_name_parser_features;
-
-ALTER INDEX p_ci_builds_user_id_name_created_at_idx ATTACH PARTITION index_secure_ci_builds_on_user_id_name_created_at;
-
-ALTER INDEX p_ci_builds_name_id_idx ATTACH PARTITION index_security_ci_builds_on_name_and_id_parser_features;
-
-ALTER INDEX p_ci_builds_scheduled_at_idx ATTACH PARTITION partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs;
-
-ALTER INDEX p_ci_builds_token_encrypted_partition_id_idx ATTACH PARTITION unique_ci_builds_token_encrypted_and_partition_id;
-
CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
CREATE TRIGGER ci_builds_loose_fk_trigger AFTER DELETE ON ci_builds REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
@@ -34576,8 +34426,6 @@ CREATE TRIGGER namespaces_loose_fk_trigger AFTER DELETE ON namespaces REFERENCIN
CREATE TRIGGER nullify_merge_request_metrics_build_data_on_update BEFORE UPDATE ON merge_request_metrics FOR EACH ROW EXECUTE FUNCTION nullify_merge_request_metrics_build_data();
-CREATE TRIGGER p_ci_builds_loose_fk_trigger AFTER DELETE ON p_ci_builds REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
-
CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
CREATE TRIGGER push_rules_loose_fk_trigger AFTER DELETE ON push_rules REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
@@ -34910,7 +34758,7 @@ ALTER TABLE ONLY incident_management_timeline_events
ALTER TABLE ONLY bulk_import_exports
ADD CONSTRAINT fk_39c726d3b5 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-ALTER TABLE p_ci_builds
+ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_3a9eaa254d FOREIGN KEY (stage_id) REFERENCES ci_stages(id) ON DELETE CASCADE;
ALTER TABLE ONLY agent_activity_events
@@ -35051,7 +34899,7 @@ ALTER TABLE ONLY vulnerability_reads
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_641731faff FOREIGN KEY (updated_by_id) REFERENCES users(id) ON DELETE SET NULL;
-ALTER TABLE p_ci_builds
+ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_6661f4f0e8 FOREIGN KEY (resource_group_id) REFERENCES ci_resource_groups(id) ON DELETE SET NULL;
ALTER TABLE ONLY project_wiki_repository_states
@@ -35171,7 +35019,7 @@ ALTER TABLE ONLY geo_event_log
ALTER TABLE ONLY packages_package_files
ADD CONSTRAINT fk_86f0f182f8 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
-ALTER TABLE p_ci_builds
+ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_87f4cefcda FOREIGN KEY (upstream_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerabilities
@@ -35273,7 +35121,7 @@ ALTER TABLE ONLY issues
ALTER TABLE ONLY ml_candidates
ADD CONSTRAINT fk_a1d5f1bc45 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE SET NULL;
-ALTER TABLE p_ci_builds
+ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_a2141b1522 FOREIGN KEY (auto_canceled_by_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY bulk_import_entities
@@ -35489,7 +35337,7 @@ ALTER TABLE ONLY project_mirror_data
ALTER TABLE ONLY environments
ADD CONSTRAINT fk_d1c8c1da6a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-ALTER TABLE p_ci_builds
+ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_d3130c9a7f FOREIGN KEY (commit_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_pipelines
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d6187ea13ea..ed67905e6b1 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1441,6 +1441,29 @@ Input type: `CatalogResourcesCreateInput`
| <a id="mutationcatalogresourcescreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcatalogresourcescreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+### `Mutation.ciAiGenerateConfig`
+
+WARNING:
+**Introduced** in 16.0.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Input type: `CiAiGenerateConfigInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationciaigenerateconfigclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationciaigenerateconfigprojectpath"></a>`projectPath` | [`ID!`](#id) | Project path for the project related to the open config editor. |
+| <a id="mutationciaigenerateconfigusercontent"></a>`userContent` | [`String!`](#string) | Content of the user message to be sent to the language model. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationciaigenerateconfigclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationciaigenerateconfigerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.ciCdSettingsUpdate`
WARNING:
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 3ce794514a3..db12f79c821 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -604,7 +604,7 @@ description 'Mutates an object. Does not mutate the object if ' \
def resolve(id: )
object = authorized_find!(id: id)
- raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`my_feature_flag` feature flag is disabled.' \
+ raise_resource_not_available_error! '`my_feature_flag` feature flag is disabled.' \
if Feature.disabled?(:my_feature_flag, object)
# ...
end
diff --git a/doc/development/navigation_sidebar.md b/doc/development/navigation_sidebar.md
index 90b87e9518d..cd543012ddd 100644
--- a/doc/development/navigation_sidebar.md
+++ b/doc/development/navigation_sidebar.md
@@ -35,8 +35,9 @@ Only one instance of this component on a given page is supported. This is to
avoid ordering issues and cluttering the sidebar.
NOTE:
-Arbitrary content is allowed, but nav items should be implemented by
-subclassing `::Sidebars::Panel`.
+You can use arbitrary content. You should implement nav items by subclassing `::Sidebars::Panel`.
+If you must use Vue to render nav items (for example, if you need to use Vue Router) you can make an exception.
+However, in the corresponding `panel.rb` file, you must add a comment that explains how the nav items are rendered.
NOTE:
Do not use the `SidebarPortalTarget` component. It is internal to the sidebar.
diff --git a/doc/update/removals.md b/doc/update/removals.md
index bdb68f8330c..17bcbd8d72d 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -120,6 +120,22 @@ In GitLab 13.9, we updated the Omnibus GitLab package and GitLab Helm chart 4.9
GitLab 16.0, we have removed support for Redis 5. If you are using your own Redis 5.0 instance, you must upgrade it to Redis 6.0 or later before upgrading to GitLab 16.0
or later.
+### Removed `external` field from GraphQL `ReleaseAssetLink` type
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+From GitLab 15.9, all Release links are external. The `external` field of the `ReleaseAssetLink` type was deprecated in 15.9, and removed in GitLab 16.0.
+
+### Removed `external` field from Releases and Release link APIs
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+From GitLab 15.9, all Release links are external. The `external` field in the Releases and Release link APIs was deprecated in 15.9, and removed in GitLab 16.0.
+
### Use of `id` field in vulnerabilityFindingDismiss mutation
WARNING:
diff --git a/doc/user/compliance/compliance_report/index.md b/doc/user/compliance/compliance_report/index.md
index fa342228b5b..62f5acc6e71 100644
--- a/doc/user/compliance/compliance_report/index.md
+++ b/doc/user/compliance/compliance_report/index.md
@@ -225,6 +225,28 @@ To remove a compliance framework from projects in a group:
1. From the **Choose one bulk action** dropdown list, select **Remove framework from selected projects**.
1. Select **Remove**.
+### Export a report of compliance frameworks on projects in a group
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387912) in GitLab 16.0.
+
+Export a report of compliance frameworks that are applied to projects in a group. Reports:
+
+- Do not use filters on the framework report.
+- Are truncated at 15 MB so the email attachment too large.
+
+Prerequisites:
+
+- You must be an administrator or have the Owner role for the group.
+
+To export a report of compliance frameworks on projects in a group:
+
+1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Security and Compliance > Compliance report**.
+1. On the page, select the **Frameworks** tab.
+1. On the Frameworks tab, select the **Export as CSV** action in the top right corner
+
+A report is compiled and delivered to your email inbox as an attachment.
+
#### Filter the compliance frameworks report
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387911) in GitLab 15.11.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2bfb2554b85..2b65ff3e8fb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22783,6 +22783,9 @@ msgstr ""
msgid "Importing..."
msgstr ""
+msgid "Import|An error occurred while fetching import details."
+msgstr ""
+
msgid "Import|GitHub import details"
msgstr ""
@@ -25217,6 +25220,9 @@ msgstr ""
msgid "JiraService|An error occurred while fetching issue list"
msgstr ""
+msgid "JiraService|Are you a GitLab administrator?"
+msgstr ""
+
msgid "JiraService|Automatically transitions Jira issues to the \"Done\" category. %{linkStart}Learn more%{linkEnd}"
msgstr ""
@@ -25331,6 +25337,9 @@ msgstr ""
msgid "JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}"
msgstr ""
+msgid "JiraService|Setting up this integration is only possible if you're a GitLab administrator."
+msgstr ""
+
msgid "JiraService|Sign in to GitLab to get started."
msgstr ""
@@ -48116,6 +48125,9 @@ msgstr ""
msgid "UsageQuota|Pending Members"
msgstr ""
+msgid "UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD."
+msgstr ""
+
msgid "UsageQuota|Pipeline artifacts created by CI/CD."
msgstr ""
@@ -48194,6 +48206,9 @@ msgstr ""
msgid "UsageQuota|Transfer data used by month"
msgstr ""
+msgid "UsageQuota|Transfer type"
+msgstr ""
+
msgid "UsageQuota|Transfer usage breakout"
msgstr ""
diff --git a/package.json b/package.json
index ff92e222567..302fe9272bb 100644
--- a/package.json
+++ b/package.json
@@ -244,7 +244,7 @@
"cheerio": "^1.0.0-rc.9",
"commander": "^2.20.3",
"custom-jquery-matchers": "^2.1.0",
- "eslint": "8.39.0",
+ "eslint": "8.40.0",
"eslint-import-resolver-jest": "3.0.2",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-import": "^2.27.5",
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 2deca64a9ac..bd2672ffadc 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -104,10 +104,14 @@ module QA
end
def go_to_groups
- return click_element(:nav_item_link, submenu_item: 'Groups') if Runtime::Env.super_sidebar_enabled?
-
- # Use new functionality to visit Groups where possible
- if has_element?(:sidebar_menu_link, menu_item: 'Groups')
+ # This needs to be fixed in the tests themselves. Fullfillment tests try to go to groups view from the
+ # group. Instead of having a global hack, explicit test should navigate to correct view first.
+ # see: https://gitlab.com/gitlab-org/gitlab/-/issues/403589#note_1383040061
+ if Runtime::Env.super_sidebar_enabled?
+ go_to_your_work unless has_element?(:nav_item_link, submenu_item: 'Groups', wait: 0)
+ click_element(:nav_item_link, submenu_item: 'Groups')
+ elsif has_element?(:sidebar_menu_link, menu_item: 'Groups')
+ # Use new functionality to visit Groups where possible
click_element(:sidebar_menu_link, menu_item: 'Groups')
else
# Otherwise fallback to previous functionality
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index a120166a2ac..9f228c75127 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -86,7 +86,7 @@ RSpec.describe 'Database schema', feature_category: :database do
oauth_access_grants: %w[resource_owner_id application_id],
oauth_access_tokens: %w[resource_owner_id application_id],
oauth_applications: %w[owner_id],
- p_ci_builds: %w[project_id runner_id user_id erased_by_id trigger_request_id partition_id],
+ p_ci_runner_machine_builds: %w[partition_id build_id],
product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
project_data_transfers: %w[project_id namespace_id],
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 9fe72b981f1..9739ea53f81 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -143,24 +143,61 @@ RSpec.describe "Admin::AbuseReports", :js, feature_category: :shared do
end
describe 'if a user has been reported for abuse' do
- let!(:abuse_report) { create(:abuse_report, user: user) }
+ let_it_be(:abuse_report) { create(:abuse_report, user: user) }
describe 'in the abuse report view' do
- it 'presents information about abuse report' do
+ before do
visit admin_abuse_reports_path
+ end
+ it 'presents information about abuse report' do
expect(page).to have_content('Abuse Reports')
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content(abuse_report.reporter.name)
expect(page).to have_content(abuse_report.message)
expect(page).to have_link(user.name, href: user_path(user))
+ end
+
+ it 'present actions items' do
+ expect(page).to have_link('Remove user & report')
+ expect(page).to have_link('Block user')
expect(page).to have_link('Remove user')
end
end
describe 'in the profile page of the user' do
- it 'shows a link to the admin view of the user' do
+ it 'shows a link to view user in the admin area' do
visit user_path(user)
- expect(page).to have_link '', href: admin_user_path(user)
+ expect(page).to have_link 'View user in admin area', href: admin_user_path(user)
+ end
+ end
+ end
+
+ describe 'if an admin has been reported for abuse' do
+ let_it_be(:admin_abuse_report) { create(:abuse_report, user: admin) }
+
+ describe 'in the abuse report view' do
+ before do
+ visit admin_abuse_reports_path
+ end
+
+ it 'presents information about abuse report' do
+ page.within(:table_row, { "User" => admin.name }) do
+ expect(page).to have_content(admin.name)
+ expect(page).to have_content(admin_abuse_report.reporter.name)
+ expect(page).to have_content(admin_abuse_report.message)
+ expect(page).to have_link(admin.name, href: user_path(admin))
+ end
+ end
+
+ it 'does not present actions items' do
+ page.within(:table_row, { "User" => admin.name }) do
+ expect(page).not_to have_link('Remove user & report')
+ expect(page).not_to have_link('Block user')
+ expect(page).not_to have_link('Remove user')
+ end
end
end
end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index 8d2813d26f7..68d63ac321e 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -37,8 +37,11 @@ RSpec.describe 'admin issues labels', feature_category: :team_planning do
end
it 'deletes all labels', :js do
- page.all('.labels .js-remove-label').each do |remove|
- accept_gl_confirm(button_text: 'Delete label') { remove.click }
+ page.all('.labels .label-actions-list').each do |label|
+ label.click
+ accept_gl_confirm(button_text: 'Delete label') do
+ click_link 'Delete'
+ end
wait_for_requests
end
diff --git a/spec/features/jira_oauth_provider_authorize_spec.rb b/spec/features/jira_oauth_provider_authorize_spec.rb
index a542aaa7619..e873d9c219f 100644
--- a/spec/features/jira_oauth_provider_authorize_spec.rb
+++ b/spec/features/jira_oauth_provider_authorize_spec.rb
@@ -4,18 +4,39 @@ require 'spec_helper'
RSpec.describe 'JIRA OAuth Provider', feature_category: :integrations do
describe 'JIRA DVCS OAuth Authorization' do
- let(:application) { create(:oauth_application, redirect_uri: oauth_jira_dvcs_callback_url, scopes: 'read_user') }
+ let_it_be(:application) do
+ create(:oauth_application, redirect_uri: oauth_jira_dvcs_callback_url, scopes: 'read_user')
+ end
+
+ let(:authorize_path) do
+ oauth_jira_dvcs_authorize_path(client_id: application.uid,
+ redirect_uri: oauth_jira_dvcs_callback_url,
+ response_type: 'code',
+ state: 'my_state',
+ scope: 'read_user')
+ end
before do
sign_in(user)
+ end
- visit oauth_jira_dvcs_authorize_path(client_id: application.uid,
- redirect_uri: oauth_jira_dvcs_callback_url,
- response_type: 'code',
- state: 'my_state',
- scope: 'read_user')
+ it_behaves_like 'Secure OAuth Authorizations' do
+ before do
+ visit authorize_path
+ end
end
- it_behaves_like 'Secure OAuth Authorizations'
+ context 'when the flag is disabled' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ stub_feature_flags(jira_dvcs_end_of_life_amnesty: false)
+ visit authorize_path
+ end
+
+ it 'presents as an endpoint that does not exist' do
+ expect(page).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
diff --git a/spec/frontend/import/details/components/import_details_app_spec.js b/spec/frontend/import/details/components/import_details_app_spec.js
index 178ce071de0..6e748a57a1d 100644
--- a/spec/frontend/import/details/components/import_details_app_spec.js
+++ b/spec/frontend/import/details/components/import_details_app_spec.js
@@ -1,16 +1,11 @@
import { shallowMount } from '@vue/test-utils';
import ImportDetailsApp from '~/import/details/components/import_details_app.vue';
-import { mockProject } from '../mock_data';
describe('Import details app', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMount(ImportDetailsApp, {
- propsData: {
- project: mockProject,
- },
- });
+ wrapper = shallowMount(ImportDetailsApp);
};
describe('template', () => {
diff --git a/spec/frontend/import/details/components/import_details_table_spec.js b/spec/frontend/import/details/components/import_details_table_spec.js
index 43c9a66c00a..aee8573eb02 100644
--- a/spec/frontend/import/details/components/import_details_table_spec.js
+++ b/spec/frontend/import/details/components/import_details_table_spec.js
@@ -1,17 +1,30 @@
import { mount, shallowMount } from '@vue/test-utils';
-import { GlEmptyState, GlTable } from '@gitlab/ui';
+import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
+import { createAlert } from '~/alert';
+import waitForPromises from 'helpers/wait_for_promises';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
import ImportDetailsTable from '~/import/details/components/import_details_table.vue';
+import { mockImportFailures, mockHeaders } from '../mock_data';
+
+jest.mock('~/alert');
describe('Import details table', () => {
let wrapper;
+ let mock;
- const createComponent = ({ mountFn = shallowMount } = {}) => {
- wrapper = mountFn(ImportDetailsTable);
+ const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => {
+ wrapper = mountFn(ImportDetailsTable, {
+ provide,
+ });
};
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findGlTable = () => wrapper.findComponent(GlTable);
+ const findGlTableRows = () => findGlTable().find('tbody').findAll('tr');
const findGlEmptyState = () => findGlTable().findComponent(GlEmptyState);
const findPaginationBar = () => wrapper.findComponent(PaginationBar);
@@ -20,7 +33,7 @@ describe('Import details table', () => {
it('renders table with empty state', () => {
createComponent({ mountFn: mount });
- expect(findGlEmptyState().exists()).toBe(true);
+ expect(findGlEmptyState().text()).toBe(ImportDetailsTable.i18n.emptyText);
});
it('does not render pagination', () => {
@@ -30,4 +43,71 @@ describe('Import details table', () => {
});
});
});
+
+ describe('fetching failures from API', () => {
+ const mockImportFailuresPath = '/failures';
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('when request is successful', () => {
+ beforeEach(() => {
+ mock.onGet(mockImportFailuresPath).reply(HTTP_STATUS_OK, mockImportFailures, mockHeaders);
+
+ createComponent({
+ mountFn: mount,
+ provide: {
+ failuresPath: mockImportFailuresPath,
+ },
+ });
+ });
+
+ it('renders loading icon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render loading icon after fetch', async () => {
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sets items and pagination info', async () => {
+ await waitForPromises();
+
+ expect(findGlTableRows().length).toBe(mockImportFailures.length);
+ expect(findPaginationBar().props('pageInfo')).toMatchObject({
+ page: mockHeaders['x-page'],
+ perPage: mockHeaders['x-per-page'],
+ total: mockHeaders['x-total'],
+ totalPages: mockHeaders['x-total-pages'],
+ });
+ });
+ });
+
+ describe('when request fails', () => {
+ beforeEach(() => {
+ mock.onGet(mockImportFailuresPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ createComponent({
+ provide: {
+ failuresPath: mockImportFailuresPath,
+ },
+ });
+ });
+
+ it('displays an error', async () => {
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: ImportDetailsTable.i18n.fetchErrorMessage,
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/import/details/mock_data.js b/spec/frontend/import/details/mock_data.js
index 514fb3a923d..a8e0e53ed2b 100644
--- a/spec/frontend/import/details/mock_data.js
+++ b/spec/frontend/import/details/mock_data.js
@@ -1,31 +1,53 @@
-export const mockProject = {
- id: 26,
- name: 'acl',
- fullPath: '/root/acl',
- fullName: 'Administrator / acl',
- refsUrl: '/root/acl/refs',
- importSource: 'namespace/acl',
- importStatus: 'finished',
- humanImportStatusName: 'finished',
- providerLink: 'https://github.com/namespace/acl',
- relationType: null,
- stats: {
- fetched: {
- note: 1,
- issue: 2,
- label: 5,
- collaborator: 2,
- pullRequest: 1,
- pullRequestMergedBy: 1,
+export const mockImportFailures = [
+ {
+ type: 'pull_request',
+ title: 'Add one cool feature',
+ url: 'https://github.com/USER/REPO/pull/2',
+ details: {
+ exception_class: 'ActiveRecord::RecordInvalid',
+ exception_message: 'Record invalid',
+ source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
+ github_identifiers: {
+ iid: 2,
+ issuable_type: 'MergeRequest',
+ object_type: 'pull_request',
+ },
},
- imported: {
- note: 1,
- issue: 2,
- label: 6,
- collaborator: 3,
- pullRequest: 1,
- pullRequestMergedBy: 1,
- pullRequestReviewRequest: 1,
+ },
+ {
+ type: 'pull_request',
+ title: 'Add another awesome feature',
+ url: 'https://github.com/USER/REPO/pull/3',
+ details: {
+ exception_class: 'ActiveRecord::RecordInvalid',
+ exception_message: 'Record invalid',
+ source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
+ github_identifiers: {
+ iid: 3,
+ issuable_type: 'MergeRequest',
+ object_type: 'pull_request',
+ },
+ },
+ },
+ {
+ type: 'lfs_object',
+ title: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
+ url: null,
+ details: {
+ exception_class: 'NameError',
+ exception_message: 'some message',
+ source: 'Gitlab::GithubImport::Importer::LfsObjectImporter',
+ github_identifiers: {
+ oid: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
+ size: 2473979,
+ },
},
},
+];
+
+export const mockHeaders = {
+ 'x-page': 1,
+ 'x-per-page': 20,
+ 'x-total': 3,
+ 'x-total-pages': 1,
};
diff --git a/spec/frontend/import_entities/components/import_status_spec.js b/spec/frontend/import_entities/components/import_status_spec.js
index 8e569e3ec85..4c6fee35389 100644
--- a/spec/frontend/import_entities/components/import_status_spec.js
+++ b/spec/frontend/import_entities/components/import_status_spec.js
@@ -155,6 +155,7 @@ describe('Import entities status component', () => {
describe('show details link', () => {
const mockDetailsPath = 'details_path';
+ const mockProjectId = 29;
const mockCompleteStats = {
fetched: { ...mockStatItems },
imported: { ...mockStatItems },
@@ -180,6 +181,7 @@ describe('Import entities status component', () => {
beforeEach(() => {
createComponent(
{
+ projectId: mockProjectId,
status: STATUSES.FINISHED,
stats: partialImport ? mockIncompleteStats : mockCompleteStats,
},
@@ -195,7 +197,9 @@ describe('Import entities status component', () => {
it(`${expectLink ? 'renders' : 'does not render'} import details link`, () => {
expect(findGlLink().exists()).toBe(expectLink);
if (expectLink) {
- expect(findGlLink().attributes('href')).toBe(mockDetailsPath);
+ expect(findGlLink().attributes('href')).toBe(
+ `${mockDetailsPath}?project_id=${mockProjectId}`,
+ );
}
});
},
diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index 8e73f76382a..57e232a4c46 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -40,6 +40,7 @@ describe('ProviderRepoTableRow', () => {
const findImportButton = () => findButton('Import');
const findReimportButton = () => findButton('Re-import');
const findGroupDropdown = () => wrapper.findComponent(ImportGroupDropdown);
+ const findImportStatus = () => wrapper.findComponent(ImportStatus);
const findCancelButton = () => {
const buttons = wrapper
@@ -81,7 +82,7 @@ describe('ProviderRepoTableRow', () => {
});
it('renders empty import status', () => {
- expect(wrapper.findComponent(ImportStatus).props().status).toBe(STATUSES.NONE);
+ expect(findImportStatus().props().status).toBe(STATUSES.NONE);
});
it('renders a group namespace select', () => {
@@ -198,9 +199,7 @@ describe('ProviderRepoTableRow', () => {
});
it('renders proper import status', () => {
- expect(wrapper.findComponent(ImportStatus).props().status).toBe(
- repo.importedProject.importStatus,
- );
+ expect(findImportStatus().props().status).toBe(repo.importedProject.importStatus);
});
it('does not render a namespace select', () => {
@@ -236,8 +235,11 @@ describe('ProviderRepoTableRow', () => {
});
});
- it('passes stats to import status component', () => {
- expect(wrapper.findComponent(ImportStatus).props().stats).toBe(FAKE_STATS);
+ it('passes props to import status component', () => {
+ expect(findImportStatus().props()).toMatchObject({
+ projectId: repo.importedProject.id,
+ stats: FAKE_STATS,
+ });
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index 15a7e2e4ea0..26a9d07321c 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -50,18 +50,6 @@ describe('JiraConnectApp', () => {
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
});
- it('renders UserLink component', () => {
- createComponent();
-
- const userLink = findUserLink();
- expect(userLink.exists()).toBe(true);
- expect(userLink.props()).toEqual({
- hasSubscriptions: true,
- user: null,
- userSignedIn: false,
- });
- });
-
it('renders only Jira Connect app', () => {
createComponent();
@@ -79,12 +67,12 @@ describe('JiraConnectApp', () => {
});
describe.each`
- scenario | currentUser | shouldRenderSignInPage | shouldRenderSubscriptionsPage
- ${'user is not signed in'} | ${undefined} | ${true} | ${false}
- ${'user is signed in'} | ${mockCurrentUser} | ${false} | ${true}
+ scenario | currentUser | expectUserLink | expectSignInPage | expectSubscriptionsPage
+ ${'user is not signed in'} | ${undefined} | ${false} | ${true} | ${false}
+ ${'user is signed in'} | ${mockCurrentUser} | ${true} | ${false} | ${true}
`(
'when $scenario',
- ({ currentUser, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
+ ({ currentUser, expectUserLink, expectSignInPage, expectSubscriptionsPage }) => {
beforeEach(() => {
createComponent({
initialState: {
@@ -93,18 +81,23 @@ describe('JiraConnectApp', () => {
});
});
- it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
- expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
- if (shouldRenderSignInPage) {
+ it(`${expectUserLink ? 'renders' : 'does not render'} user link`, () => {
+ expect(findUserLink().exists()).toBe(expectUserLink);
+ if (expectUserLink) {
+ expect(findUserLink().props('user')).toBe(mockCurrentUser);
+ }
+ });
+
+ it(`${expectSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
+ expect(findSignInPage().isVisible()).toBe(expectSignInPage);
+ if (expectSignInPage) {
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}
});
- it(`${
- shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
- } subscriptions page`, () => {
- expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
- if (shouldRenderSubscriptionsPage) {
+ it(`${expectSubscriptionsPage ? 'renders' : 'does not render'} subscriptions page`, () => {
+ expect(findSubscriptionsPage().exists()).toBe(expectSubscriptionsPage);
+ if (expectSubscriptionsPage) {
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
}
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
index a10e352ed85..77bc1d2004c 100644
--- a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
@@ -1,6 +1,5 @@
import { GlSprintf } from '@gitlab/ui';
import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
-import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -13,42 +12,18 @@ describe('UserLink', () => {
provide,
stubs: {
GlSprintf,
- SignInOauthButton,
},
});
};
const findGitlabUserLink = () => wrapper.findByTestId('gitlab-user-link');
const findSprintf = () => wrapper.findComponent(GlSprintf);
- const findOauthButton = () => wrapper.findComponent(SignInOauthButton);
- describe.each`
- userSignedIn | hasSubscriptions | expectGlSprintf | expectOauthButton
- ${false} | ${false} | ${false} | ${false}
- ${false} | ${true} | ${false} | ${true}
- ${true} | ${false} | ${true} | ${false}
- ${true} | ${true} | ${true} | ${false}
- `(
- 'when `userSignedIn` is $userSignedIn, `hasSubscriptions` is $hasSubscriptions',
- ({ userSignedIn, hasSubscriptions, expectGlSprintf, expectOauthButton }) => {
- it('renders template correctly', () => {
- createComponent(
- {
- userSignedIn,
- hasSubscriptions,
- },
- {
- provide: {
- oauthMetadata: {},
- },
- },
- );
+ it('renders template correctly', () => {
+ createComponent();
- expect(findSprintf().exists()).toBe(expectGlSprintf);
- expect(findOauthButton().exists()).toBe(expectOauthButton);
- });
- },
- );
+ expect(findSprintf().exists()).toBe(true);
+ });
describe('gitlab user link', () => {
describe.each`
@@ -62,14 +37,7 @@ describe('UserLink', () => {
beforeEach(() => {
window.gon = { current_username, relative_root_url: '' };
- createComponent(
- {
- userSignedIn: true,
- hasSubscriptions: true,
- user,
- },
- { provide: { gitlabUserPath } },
- );
+ createComponent({ user }, { provide: { gitlabUserPath } });
});
it(`sets href to ${expectedUserLink}`, () => {
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
index ce6861ff460..93663319e6d 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
@@ -1,7 +1,6 @@
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue';
import SignInGitlabMultiversion from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue';
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import VersionSelectForm from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue';
@@ -23,7 +22,6 @@ describe('SignInGitlabMultiversion', () => {
const mockBasePath = 'gitlab.mycompany.com';
- const findSetupInstructions = () => wrapper.findComponent(SetupInstructions);
const findSignInOauthButton = () => wrapper.findComponent(SignInOauthButton);
const findVersionSelectForm = () => wrapper.findComponent(VersionSelectForm);
const findSubtitle = () => wrapper.findByTestId('subtitle');
@@ -68,28 +66,13 @@ describe('SignInGitlabMultiversion', () => {
expect(findSubtitle().text()).toBe(SignInGitlabMultiversion.i18n.signInSubtitle);
});
- it('renders setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(true);
+ it('renders sign in button', () => {
+ expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
});
it('calls setApiBaseURL with correct params', () => {
expect(setApiBaseURL).toHaveBeenCalledWith(mockBasePath);
});
-
- describe('when SetupInstructions emits `next` event', () => {
- beforeEach(async () => {
- findSetupInstructions().vm.$emit('next');
- await nextTick();
- });
-
- it('renders sign in button', () => {
- expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
- });
-
- it('hides setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(false);
- });
- });
});
describe('when on GitLab.com', () => {
@@ -98,10 +81,6 @@ describe('SignInGitlabMultiversion', () => {
createComponent();
});
- it('does not render setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(false);
- });
-
it('renders sign in button', () => {
expect(findSignInOauthButton().props('gitlabBasePath')).toBe(GITLAB_COM_BASE_PATH);
});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
index 5496cf008c5..40ea6058c70 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
@@ -7,8 +7,9 @@ import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_i
describe('SetupInstructions', () => {
let wrapper;
- const findGlButton = () => wrapper.findComponent(GlButton);
const findGlLink = () => wrapper.findComponent(GlLink);
+ const findBackButton = () => wrapper.findAllComponents(GlButton).at(0);
+ const findNextButton = () => wrapper.findAllComponents(GlButton).at(1);
const createComponent = () => {
wrapper = shallowMount(SetupInstructions);
@@ -23,12 +24,23 @@ describe('SetupInstructions', () => {
expect(findGlLink().attributes('href')).toBe(OAUTH_SELF_MANAGED_DOC_LINK);
});
- describe('when button is clicked', () => {
+ describe('when "Next" button is clicked', () => {
it('emits "next" event', () => {
expect(wrapper.emitted('next')).toBeUndefined();
- findGlButton().vm.$emit('click');
+ findNextButton().vm.$emit('click');
expect(wrapper.emitted('next')).toHaveLength(1);
+ expect(wrapper.emitted('back')).toBeUndefined();
+ });
+ });
+
+ describe('when "Back" button is clicked', () => {
+ it('emits "back" event', () => {
+ expect(wrapper.emitted('back')).toBeUndefined();
+ findBackButton().vm.$emit('click');
+
+ expect(wrapper.emitted('back')).toHaveLength(1);
+ expect(wrapper.emitted('next')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
index 428aa1d734b..2a08547b048 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
@@ -1,8 +1,9 @@
import { GlFormInput, GlFormRadioGroup, GlForm } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import VersionSelectForm from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue';
+import SelfManagedAlert from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue';
+import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue';
describe('VersionSelectForm', () => {
let wrapper;
@@ -10,26 +11,53 @@ describe('VersionSelectForm', () => {
const findFormRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findForm = () => wrapper.findComponent(GlForm);
const findInput = () => wrapper.findComponent(GlFormInput);
+ const findSelfManagedAlert = () => wrapper.findComponent(SelfManagedAlert);
+ const findSetupInstructions = () => wrapper.findComponent(SetupInstructions);
+ const findBackButton = () => wrapper.findByTestId('back-button');
+ const findSubmitButton = () => wrapper.findByTestId('submit-button');
const submitForm = () => findForm().vm.$emit('submit', new Event('submit'));
+ const expectSelfManagedFlowAtStep = (step) => {
+ // step 0 is for SaaS which doesn't have any of the self-managed elements
+ const expectSelfManagedAlert = step === 1;
+ const expectSetupInstructions = step === 2;
+ const expectSelfManagedInput = step === 3;
+
+ it(`${expectSelfManagedAlert ? 'renders' : 'does not render'} self-managed alert`, () => {
+ expect(findSelfManagedAlert().exists()).toBe(expectSelfManagedAlert);
+ });
+
+ it(`${expectSetupInstructions ? 'renders' : 'does not render'} setup instructions`, () => {
+ expect(findSetupInstructions().exists()).toBe(expectSetupInstructions);
+ });
+
+ it(`${
+ expectSelfManagedInput ? 'renders' : 'does not render'
+ } self-managed instance URL input`, () => {
+ expect(findInput().exists()).toBe(expectSelfManagedInput);
+ });
+ };
+
const createComponent = () => {
wrapper = shallowMountExtended(VersionSelectForm);
};
- describe('default state', () => {
+ describe('when "SaaS" radio option is selected (default state)', () => {
beforeEach(() => {
createComponent();
});
- it('selects saas radio option by default', () => {
+ it('selects "saas" radio option by default', () => {
expect(findFormRadioGroup().vm.$attrs.checked).toBe(VersionSelectForm.radioOptions.saas);
});
- it('does not render instance input', () => {
- expect(findInput().exists()).toBe(false);
+ it('renders submit button as "Save"', () => {
+ expect(findSubmitButton().text()).toBe(VersionSelectForm.i18n.buttonSave);
});
+ expectSelfManagedFlowAtStep(0);
+
describe('when form is submitted', () => {
it('emits "submit" event with gitlab.com as the payload', () => {
submitForm();
@@ -39,26 +67,61 @@ describe('VersionSelectForm', () => {
});
});
- describe('when "self-managed" radio option is selected', () => {
- beforeEach(async () => {
+ describe('when "self-managed" radio option is selected (step 1 of 3)', () => {
+ beforeEach(() => {
createComponent();
findFormRadioGroup().vm.$emit('input', VersionSelectForm.radioOptions.selfManaged);
- await nextTick();
});
- it('reveals the self-managed input field', () => {
- expect(findInput().exists()).toBe(true);
+ it('renders submit button as "Next"', () => {
+ expect(findSubmitButton().text()).toBe(VersionSelectForm.i18n.buttonNext);
});
- describe('when form is submitted', () => {
- it('emits "submit" event with the input field value as the payload', () => {
- const mockInstanceUrl = 'https://gitlab.example.com';
+ expectSelfManagedFlowAtStep(1);
- findInput().vm.$emit('input', mockInstanceUrl);
+ describe('when user clicks "Next" button (next to step 2 of 3)', () => {
+ beforeEach(() => {
submitForm();
+ });
+
+ expectSelfManagedFlowAtStep(2);
+
+ describe('when SetupInstructions emits `next` event (next to step 3 of 3)', () => {
+ beforeEach(() => {
+ findSetupInstructions().vm.$emit('next');
+ });
+
+ expectSelfManagedFlowAtStep(3);
+
+ describe('when form is submitted', () => {
+ it('emits "submit" event with the input field value as the payload', () => {
+ const mockInstanceUrl = 'https://gitlab.example.com';
+
+ findInput().vm.$emit('input', mockInstanceUrl);
+ submitForm();
+
+ expect(wrapper.emitted('submit')[0][0]).toBe(mockInstanceUrl);
+ });
+ });
+
+ describe('when back button is clicked', () => {
+ beforeEach(() => {
+ findBackButton().vm.$emit('click', {
+ preventDefault: jest.fn(), // preventDefault is needed to prevent form submission
+ });
+ });
+
+ expectSelfManagedFlowAtStep(1);
+ });
+ });
+
+ describe('when SetupInstructions emits `back` event (back to step 1 of 3)', () => {
+ beforeEach(() => {
+ findSetupInstructions().vm.$emit('back');
+ });
- expect(wrapper.emitted('submit')[0][0]).toBe(mockInstanceUrl);
+ expectSelfManagedFlowAtStep(1);
});
});
});
diff --git a/spec/frontend/projects/commit_box/info/init_details_button_spec.js b/spec/frontend/projects/commit_box/info/init_details_button_spec.js
new file mode 100644
index 00000000000..8aaba31e23e
--- /dev/null
+++ b/spec/frontend/projects/commit_box/info/init_details_button_spec.js
@@ -0,0 +1,32 @@
+import { setHTMLFixture } from 'helpers/fixtures';
+import { initDetailsButton } from '~/projects/commit_box/info/init_details_button';
+
+const htmlFixture = `
+ <span>
+ <a href="#" class="js-details-expand">Expand</a>
+ <span class="js-details-content hide">Some branch</span>
+ </span>`;
+
+describe('~/projects/commit_box/info/init_details_button', () => {
+ const findExpandButton = () => document.querySelector('.js-details-expand');
+ const findContent = () => document.querySelector('.js-details-content');
+
+ beforeEach(() => {
+ setHTMLFixture(htmlFixture);
+ initDetailsButton();
+ });
+
+ describe('when clicking the expand button', () => {
+ it('renders the content by removing the `hide` class', () => {
+ expect(findContent().classList).toContain('hide');
+ findExpandButton().click();
+ expect(findContent().classList).not.toContain('hide');
+ });
+
+ it('hides the expand button by adding the `gl-display-none` class', () => {
+ expect(findExpandButton().classList).not.toContain('gl-display-none');
+ findExpandButton().click();
+ expect(findExpandButton().classList).toContain('gl-display-none');
+ });
+ });
+});
diff --git a/spec/frontend/projects/commit_box/info/load_branches_spec.js b/spec/frontend/projects/commit_box/info/load_branches_spec.js
index e49d92188ed..b00a6378e07 100644
--- a/spec/frontend/projects/commit_box/info/load_branches_spec.js
+++ b/spec/frontend/projects/commit_box/info/load_branches_spec.js
@@ -4,6 +4,9 @@ import { setHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { loadBranches } from '~/projects/commit_box/info/load_branches';
+import { initDetailsButton } from '~/projects/commit_box/info/init_details_button';
+
+jest.mock('~/projects/commit_box/info/init_details_button');
const mockCommitPath = '/commit/abcd/branches';
const mockBranchesRes =
@@ -26,6 +29,13 @@ describe('~/projects/commit_box/info/load_branches', () => {
mock.onGet(mockCommitPath).reply(HTTP_STATUS_OK, mockBranchesRes);
});
+ it('initializes the details button', async () => {
+ loadBranches();
+ await waitForPromises();
+
+ expect(initDetailsButton).toHaveBeenCalled();
+ });
+
it('loads and renders branches info', async () => {
loadBranches();
await waitForPromises();
diff --git a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
index c7dd6e931b1..5207665f883 100644
--- a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue';
@@ -30,7 +30,7 @@ describe('ScopeNewNavigation', () => {
getters: getterSpies,
});
- wrapper = shallowMount(ScopeNewNavigation, {
+ wrapper = mount(ScopeNewNavigation, {
store,
stubs: {
NavItem,
diff --git a/spec/frontend/super_sidebar/components/nav_item_link_spec.js b/spec/frontend/super_sidebar/components/nav_item_link_spec.js
new file mode 100644
index 00000000000..5cc1bd01d0f
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/nav_item_link_spec.js
@@ -0,0 +1,37 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import NavItemLink from '~/super_sidebar/components/nav_item_link.vue';
+
+describe('NavItemLink component', () => {
+ let wrapper;
+
+ const createWrapper = (item) => {
+ wrapper = shallowMountExtended(NavItemLink, {
+ propsData: {
+ item,
+ },
+ });
+ };
+
+ describe('when `item` has `is_active` set to `false`', () => {
+ it('renders an anchor tag without active CSS class and `aria-current` attribute', () => {
+ createWrapper({ title: 'foo', link: '/foo', is_active: false });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ class: '',
+ });
+ });
+ });
+
+ describe('when `item` has `is_active` set to `true`', () => {
+ it('renders an anchor tag with active CSS class and `aria-current="page"`', () => {
+ createWrapper({ title: 'foo', link: '/foo', is_active: true });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ class: 'gl-bg-t-gray-a-08',
+ 'aria-current': 'page',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js b/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js
new file mode 100644
index 00000000000..a7ca56325fe
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js
@@ -0,0 +1,56 @@
+import { RouterLinkStub } from '@vue/test-utils';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import NavItemRouterLink from '~/super_sidebar/components/nav_item_router_link.vue';
+
+describe('NavItemRouterLink component', () => {
+ let wrapper;
+
+ const createWrapper = ({ item, routerLinkSlotProps = {} }) => {
+ wrapper = mountExtended(NavItemRouterLink, {
+ propsData: {
+ item,
+ },
+ stubs: {
+ RouterLink: {
+ ...RouterLinkStub,
+ render() {
+ const children = this.$scopedSlots.default({
+ href: '/foo',
+ isActive: false,
+ navigate: jest.fn(),
+ ...routerLinkSlotProps,
+ });
+ return children;
+ },
+ },
+ },
+ });
+ };
+
+ describe('when `RouterLink` is not active', () => {
+ it('renders an anchor tag without active CSS class and `aria-current` attribute', () => {
+ createWrapper({ item: { title: 'foo', to: { name: 'foo' } } });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ custom: '',
+ });
+ });
+ });
+
+ describe('when `RouterLink` is active', () => {
+ it('renders an anchor tag with active CSS class and `aria-current="page"`', () => {
+ createWrapper({
+ item: { title: 'foo', to: { name: 'foo' } },
+ routerLinkSlotProps: { isActive: true },
+ });
+
+ expect(wrapper.findComponent(RouterLinkStub).props('activeClass')).toBe('gl-bg-t-gray-a-08');
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ 'aria-current': 'page',
+ custom: '',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js
index 1714a4c3a4e..43b3f14f2f5 100644
--- a/spec/frontend/super_sidebar/components/nav_item_spec.js
+++ b/spec/frontend/super_sidebar/components/nav_item_spec.js
@@ -1,6 +1,9 @@
import { GlBadge } from '@gitlab/ui';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { RouterLinkStub } from '@vue/test-utils';
+import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import NavItem from '~/super_sidebar/components/nav_item.vue';
+import NavItemRouterLink from '~/super_sidebar/components/nav_item_router_link.vue';
+import NavItemLink from '~/super_sidebar/components/nav_item_link.vue';
import {
CLICK_MENU_ITEM_ACTION,
TRACKING_UNKNOWN_ID,
@@ -12,19 +15,36 @@ describe('NavItem component', () => {
const findLink = () => wrapper.findByTestId('nav-item-link');
const findPill = () => wrapper.findComponent(GlBadge);
- const createWrapper = (item, props = {}, provide = {}) => {
- wrapper = shallowMountExtended(NavItem, {
+ const findNavItemRouterLink = () => extendedWrapper(wrapper.findComponent(NavItemRouterLink));
+ const findNavItemLink = () => extendedWrapper(wrapper.findComponent(NavItemLink));
+
+ const createWrapper = ({ item, props = {}, provide = {}, routerLinkSlotProps = {} }) => {
+ wrapper = mountExtended(NavItem, {
propsData: {
item,
...props,
},
provide,
+ stubs: {
+ RouterLink: {
+ ...RouterLinkStub,
+ render() {
+ const children = this.$scopedSlots.default({
+ href: '/foo',
+ isActive: false,
+ navigate: jest.fn(),
+ ...routerLinkSlotProps,
+ });
+ return children;
+ },
+ },
+ },
});
};
describe('pills', () => {
it.each([0, 5, 3.4, 'foo', '10%'])('item with pill_data `%p` renders a pill', (pillCount) => {
- createWrapper({ title: 'Foo', pill_count: pillCount });
+ createWrapper({ item: { title: 'Foo', pill_count: pillCount } });
expect(findPill().text()).toEqual(pillCount.toString());
});
@@ -32,7 +52,7 @@ describe('NavItem component', () => {
it.each([null, undefined, false, true, '', NaN, Number.POSITIVE_INFINITY])(
'item with pill_data `%p` renders no pill',
(pillCount) => {
- createWrapper({ title: 'Foo', pill_count: pillCount });
+ createWrapper({ item: { title: 'Foo', pill_count: pillCount } });
expect(findPill().exists()).toEqual(false);
},
@@ -41,21 +61,21 @@ describe('NavItem component', () => {
it('applies custom link classes', () => {
const customClass = 'customClass';
- createWrapper(
- { title: 'Foo' },
- {
+ createWrapper({
+ item: { title: 'Foo' },
+ props: {
linkClasses: {
[customClass]: true,
},
},
- );
+ });
expect(findLink().attributes('class')).toContain(customClass);
});
it('applies custom classes set in the backend', () => {
const customClass = 'customBackendClass';
- createWrapper({ title: 'Foo', link_classes: customClass });
+ createWrapper({ item: { title: 'Foo', link_classes: customClass } });
expect(findLink().attributes('class')).toContain(customClass);
});
@@ -70,7 +90,7 @@ describe('NavItem component', () => {
`(
'adds appropriate data tracking labels for id=$id and panelType=$panelType',
({ id, eventLabel, panelType, eventProperty, eventExtra }) => {
- createWrapper({ title: 'Foo', id }, {}, { panelType });
+ createWrapper({ item: { title: 'Foo', id }, props: {}, provide: { panelType } });
expect(findLink().attributes('data-track-action')).toBe(CLICK_MENU_ITEM_ACTION);
expect(findLink().attributes('data-track-label')).toBe(eventLabel);
@@ -79,4 +99,51 @@ describe('NavItem component', () => {
},
);
});
+
+ describe('when `item` prop has `to` attribute', () => {
+ describe('when `RouterLink` is not active', () => {
+ it('renders `NavItemRouterLink` with active indicator hidden', () => {
+ createWrapper({ item: { title: 'Foo', to: { name: 'foo' } } });
+
+ expect(findNavItemRouterLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-transparent',
+ );
+ });
+ });
+
+ describe('when `RouterLink` is active', () => {
+ it('renders `NavItemRouterLink` with active indicator shown', () => {
+ createWrapper({
+ item: { title: 'Foo', to: { name: 'foo' } },
+ routerLinkSlotProps: { isActive: true },
+ });
+
+ expect(findNavItemRouterLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-blue-500',
+ );
+ });
+ });
+ });
+
+ describe('when `item` prop has `link` attribute', () => {
+ describe('when `item` has `is_active` set to `false`', () => {
+ it('renders `NavItemLink` with active indicator hidden', () => {
+ createWrapper({ item: { title: 'Foo', link: '/foo', is_active: false } });
+
+ expect(findNavItemLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-transparent',
+ );
+ });
+ });
+
+ describe('when `item` has `is_active` set to `true`', () => {
+ it('renders `NavItemLink` with active indicator shown', () => {
+ createWrapper({ item: { title: 'Foo', link: '/foo', is_active: true } });
+
+ expect(findNavItemLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-blue-500',
+ );
+ });
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js
index d2984254dee..8c8673ddbc4 100644
--- a/spec/frontend/super_sidebar/utils_spec.js
+++ b/spec/frontend/super_sidebar/utils_spec.js
@@ -2,6 +2,7 @@ import {
getTopFrequentItems,
trackContextAccess,
formatContextSwitcherItems,
+ ariaCurrent,
} from '~/super_sidebar/utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import AccessorUtilities from '~/lib/utils/accessor';
@@ -157,4 +158,14 @@ describe('Super sidebar utils spec', () => {
]);
});
});
+
+ describe('ariaCurrent', () => {
+ it.each`
+ isActive | expected
+ ${true} | ${'page'}
+ ${false} | ${null}
+ `('returns `$expected` when `isActive` is `$isActive`', ({ isActive, expected }) => {
+ expect(ariaCurrent(isActive)).toBe(expected);
+ });
+ });
});
diff --git a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
index 37075c4d2df..faf0447c054 100644
--- a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
@@ -78,13 +78,13 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
}
}
},
- "does add column to p_ci_builds in gitlab_main and gitlab_ci" => {
+ "does add column to ci_builds in gitlab_main and gitlab_ci" => {
migration: ->(klass) do
def change
- add_column :p_ci_builds, :__test_column, :integer
+ add_column :ci_builds, :__test_column, :integer
end
end,
- query_matcher: /ALTER TABLE "p_ci_builds" ADD "__test_column" integer/,
+ query_matcher: /ALTER TABLE "ci_builds" ADD "__test_column" integer/,
expected: {
no_gitlab_schema: {
main: :success,
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index f9a391e9054..87beba680d8 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -126,7 +126,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching, feature_categ
expect(environment.errors[:external_url].first).to eq(expected_error_message)
else
expect(environment.errors[:external_url]).to be_empty,
- "There were unexpected errors: #{environment.errors.full_messages}"
+ "There were unexpected errors: #{environment.errors.full_messages}"
expect(environment.external_url).to eq(source_external_url)
end
end
@@ -660,15 +660,17 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching, feature_categ
let(:build) { create(:ci_build, :success) }
let!(:deployment) do
- create(:deployment, :success,
- environment: environment,
- deployable: build,
- on_stop: 'close_app')
+ create(
+ :deployment,
+ :success,
+ environment: environment,
+ deployable: build,
+ on_stop: 'close_app'
+ )
end
let!(:close_action) do
- create(:ci_build, :manual, pipeline: build.pipeline,
- name: 'close_app')
+ create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app')
end
context 'when environment is available' do
@@ -750,8 +752,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching, feature_categ
before do
project.add_developer(user)
- create(:protected_branch, :developers_can_merge,
- name: 'master', project: project)
+ create(:protected_branch, :developers_can_merge, name: 'master', project: project)
end
context 'when action did not yet finish' do
@@ -774,8 +775,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching, feature_categ
context 'if action did finish' do
let!(:close_action) do
- create(:ci_build, :manual, :success,
- pipeline: pipeline, name: 'close_app_a')
+ create(:ci_build, :manual, :success, pipeline: pipeline, name: 'close_app_a')
end
it 'returns a new action of the same type' do
@@ -1258,8 +1258,7 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching, feature_categ
describe '#deployment_platform' do
context 'when there is a deployment platform for environment' do
let!(:cluster) do
- create(:cluster, :provided_by_gcp,
- environment_scope: '*', projects: [project])
+ create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
end
it 'finds a deployment platform' do
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index dbd769d7fdc..9814eed8b45 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -178,11 +178,13 @@ RSpec.describe EnvironmentStatus do
let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) }
let(:merge_request) do
- create(:merge_request,
- source_project: forked,
- target_project: project,
- target_branch: 'master',
- head_pipeline: pipeline)
+ create(
+ :merge_request,
+ source_project: forked,
+ target_project: project,
+ target_branch: 'master',
+ head_pipeline: pipeline
+ )
end
it 'returns environment status' do
@@ -199,12 +201,14 @@ RSpec.describe EnvironmentStatus do
let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) }
let(:merge_request) do
- create(:merge_request,
- source_project: project,
- source_branch: 'feature',
- target_project: project,
- target_branch: 'master',
- head_pipeline: pipeline)
+ create(
+ :merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ head_pipeline: pipeline
+ )
end
it 'returns environment status' do
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 13983dcfde3..cab2444c174 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -19,8 +19,7 @@ RSpec.describe EventCollection do
context 'with project events' do
let_it_be(:push_event_payloads) do
Array.new(9) do
- create(:push_event_payload,
- event: create(:push_event, project: project, author: user))
+ create(:push_event_payload, event: create(:push_event, project: project, author: user))
end
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 931d12b7109..3e4fe57c59b 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -203,9 +203,7 @@ RSpec.describe Event, feature_category: :user_profile do
let(:target) { nil }
let(:event) do
- described_class.new(project: project,
- target: target,
- author_id: author.id)
+ described_class.new(project: project, target: target, author_id: author.id)
end
context 'for an issue' do
@@ -304,9 +302,7 @@ RSpec.describe Event, feature_category: :user_profile do
let(:note_on_design) { create(:note_on_design, author: author, noteable: design, project: project) }
let(:milestone_on_project) { create(:milestone, project: project) }
let(:event) do
- described_class.new(project: project,
- target: target,
- author_id: author.id)
+ described_class.new(project: project, target: target, author_id: author.id)
end
before do
@@ -902,8 +898,10 @@ RSpec.describe Event, feature_category: :user_profile do
it "deletes the redis key for if the project was inactive" do
Gitlab::Redis::SharedState.with do |redis|
- expect(redis).to receive(:hdel).with('inactive_projects_deletion_warning_email_notified',
- "project:#{project.id}")
+ expect(redis).to receive(:hdel).with(
+ 'inactive_projects_deletion_warning_email_notified',
+ "project:#{project.id}"
+ )
end
project.touch(:last_activity_at, time: 1.year.ago)
@@ -1138,11 +1136,13 @@ RSpec.describe Event, feature_category: :user_profile do
def create_push_event(project, user)
event = create(:push_event, project: project, author: user)
- create(:push_event_payload,
- event: event,
- commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
- commit_count: 0,
- ref: 'master')
+ create(
+ :push_event_payload,
+ event: event,
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 0,
+ ref: 'master'
+ )
event
end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index fe22b20ecf9..c477dec237f 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -8,8 +8,7 @@ RSpec.describe GenericCommitStatus do
let(:external_url) { 'http://example.gitlab.com/status' }
let(:generic_commit_status) do
- create(:generic_commit_status, pipeline: pipeline,
- target_url: external_url)
+ create(:generic_commit_status, pipeline: pipeline, target_url: external_url)
end
describe 'validations' do
diff --git a/spec/models/group_group_link_spec.rb b/spec/models/group_group_link_spec.rb
index 59370cf12d2..780196e6c8c 100644
--- a/spec/models/group_group_link_spec.rb
+++ b/spec/models/group_group_link_spec.rb
@@ -8,8 +8,7 @@ RSpec.describe GroupGroupLink do
describe 'validation' do
let_it_be(:group_group_link) do
- create(:group_group_link, shared_group: shared_group,
- shared_with_group: group)
+ create(:group_group_link, shared_group: shared_group, shared_with_group: group)
end
it { is_expected.to validate_presence_of(:shared_group) }
@@ -46,18 +45,18 @@ RSpec.describe GroupGroupLink do
describe '.non_guests' do
it 'returns all records which are greater than Guests access' do
expect(described_class.non_guests).to match_array([
- group_group_link_reporter, group_group_link_developer,
- group_group_link_maintainer, group_group_link_owner
- ])
+ group_group_link_reporter, group_group_link_developer,
+ group_group_link_maintainer, group_group_link_owner
+ ])
end
end
describe '.with_owner_or_maintainer_access' do
it 'returns all records which have OWNER or MAINTAINER access' do
expect(described_class.with_owner_or_maintainer_access).to match_array([
- group_group_link_maintainer,
- group_group_link_owner
- ])
+ group_group_link_maintainer,
+ group_group_link_owner
+ ])
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index f7f9ec8e7a7..9e8b4032ee3 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -2456,8 +2456,7 @@ RSpec.describe Group, feature_category: :subgroups do
let(:shared_with_group) { create(:group, parent: group) }
before do
- create(:group_group_link, shared_group: nested_group,
- shared_with_group: shared_with_group)
+ create(:group_group_link, shared_group: nested_group, shared_with_group: shared_with_group)
end
subject(:related_group_ids) { nested_group.related_group_ids }
diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb
index 7487793cf4f..70b32a15148 100644
--- a/spec/models/integrations/apple_app_store_spec.rb
+++ b/spec/models/integrations/apple_app_store_spec.rb
@@ -41,8 +41,8 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do
end
it 'returns false for an invalid request' do
- allow(AppStoreConnect::Client).to receive_message_chain(:new,
-:apps).and_return({ errors: [title: "error title"] })
+ allow(AppStoreConnect::Client).to receive_message_chain(:new, :apps)
+ .and_return({ errors: [title: "error title"] })
expect(apple_app_store_integration.test[:success]).to be false
end
end
diff --git a/spec/models/integrations/buildkite_spec.rb b/spec/models/integrations/buildkite_spec.rb
index 5f62c68bd2b..29c649af6c6 100644
--- a/spec/models/integrations/buildkite_spec.rb
+++ b/spec/models/integrations/buildkite_spec.rb
@@ -146,9 +146,10 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
def stub_request(status: 200, body: nil)
body ||= %q({"status":"success"})
- stub_full_request(buildkite_full_url)
- .to_return(status: status,
- headers: { 'Content-Type' => 'application/json' },
- body: body)
+ stub_full_request(buildkite_full_url).to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
end
end
diff --git a/spec/models/integrations/hangouts_chat_spec.rb b/spec/models/integrations/hangouts_chat_spec.rb
index 288478b494e..1ebf2ec3005 100644
--- a/spec/models/integrations/hangouts_chat_spec.rb
+++ b/spec/models/integrations/hangouts_chat_spec.rb
@@ -116,10 +116,13 @@ RSpec.describe Integrations::HangoutsChat do
context 'when commit comment event executed' do
let(:commit_note) do
- create(:note_on_commit, author: user,
- project: project,
- commit_id: project.repository.commit.id,
- note: 'a comment on a commit')
+ create(
+ :note_on_commit,
+ author: user,
+ project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit'
+ )
end
it "adds thread key" do
@@ -135,8 +138,7 @@ RSpec.describe Integrations::HangoutsChat do
context 'when merge request comment event executed' do
let(:merge_request_note) do
- create(:note_on_merge_request, project: project,
- note: "merge request note")
+ create(:note_on_merge_request, project: project, note: "merge request note")
end
it "adds thread key" do
@@ -168,8 +170,7 @@ RSpec.describe Integrations::HangoutsChat do
context 'when snippet comment event executed' do
let(:snippet_note) do
- create(:note_on_project_snippet, project: project,
- note: "snippet note")
+ create(:note_on_project_snippet, project: project, note: "snippet note")
end
it "adds thread key" do
diff --git a/spec/requests/jira_authorizations_spec.rb b/spec/requests/jira_authorizations_spec.rb
index 8c27b61712c..704db7fba08 100644
--- a/spec/requests/jira_authorizations_spec.rb
+++ b/spec/requests/jira_authorizations_spec.rb
@@ -39,6 +39,16 @@ RSpec.describe 'Jira authorization requests', feature_category: :integrations do
expect(oauth_response_access_token).not_to eql(jira_response_access_token)
end
+ it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
+ subject do
+ post '/login/oauth/access_token', params: {
+ client_id: client_id,
+ client_secret: client_secret,
+ code: generate_access_grant.token
+ }
+ end
+ end
+
context 'when authorization fails' do
before do
post '/login/oauth/access_token', params: {
diff --git a/spec/services/ci/pipelines/add_job_service_spec.rb b/spec/services/ci/pipelines/add_job_service_spec.rb
index 6380a6a5ec3..9fb1d6933c6 100644
--- a/spec/services/ci/pipelines/add_job_service_spec.rb
+++ b/spec/services/ci/pipelines/add_job_service_spec.rb
@@ -86,15 +86,5 @@ RSpec.describe Ci::Pipelines::AddJobService, feature_category: :continuous_integ
expect(execute.payload[:job]).to eq(job)
end
end
-
- it 'locks pipelines and stages before persisting builds', :aggregate_failures do
- expect(job).not_to be_persisted
-
- recorder = ActiveRecord::QueryRecorder.new(skip_cached: false) { execute }
- entries = recorder.log.select { |query| query.match(/LOCK|INSERT INTO ".{0,2}ci_builds"/) }
-
- expect(entries.size).to eq(2)
- expect(entries.first).to match(/LOCK "ci_pipelines", "ci_stages" IN ROW SHARE MODE;/)
- end
end
end
diff --git a/yarn.lock b/yarn.lock
index e26786a8895..f7a75b6eb39 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1044,14 +1044,14 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
-"@eslint/eslintrc@^2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02"
- integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==
+"@eslint/eslintrc@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331"
+ integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
- espree "^9.5.1"
+ espree "^9.5.2"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@@ -1059,10 +1059,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
-"@eslint/js@8.39.0":
- version "8.39.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b"
- integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==
+"@eslint/js@8.40.0":
+ version "8.40.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec"
+ integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==
"@gitlab/at.js@1.5.7":
version "1.5.7"
@@ -5809,20 +5809,20 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
-eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
- integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
+ integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
-eslint@8.39.0:
- version "8.39.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1"
- integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==
+eslint@8.40.0:
+ version "8.40.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4"
+ integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
- "@eslint/eslintrc" "^2.0.2"
- "@eslint/js" "8.39.0"
+ "@eslint/eslintrc" "^2.0.3"
+ "@eslint/js" "8.40.0"
"@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@@ -5833,8 +5833,8 @@ eslint@8.39.0:
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.0"
- eslint-visitor-keys "^3.4.0"
- espree "^9.5.1"
+ eslint-visitor-keys "^3.4.1"
+ espree "^9.5.2"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@@ -5860,14 +5860,14 @@ eslint@8.39.0:
strip-json-comments "^3.1.0"
text-table "^0.2.0"
-espree@^9.3.1, espree@^9.5.1:
- version "9.5.1"
- resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
- integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
+espree@^9.3.1, espree@^9.5.2:
+ version "9.5.2"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b"
+ integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
- eslint-visitor-keys "^3.4.0"
+ eslint-visitor-keys "^3.4.1"
esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"