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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/attention_requests/components/navigation_popover.vue122
-rw-r--r--app/assets/javascripts/attention_requests/index.js73
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js21
-rw-r--r--app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js15
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js19
-rw-r--r--app/assets/javascripts/main.js6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.vue10
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue22
-rw-r--r--app/assets/javascripts/sidebar/components/attention_requested_toggle.vue105
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/reviewers.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue20
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js9
-rw-r--r--app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql7
-rw-r--r--app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql5
-rw-r--r--app/assets/javascripts/sidebar/services/sidebar_service.js23
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js12
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js87
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js34
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue11
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/helpers/dashboard_helper.rb4
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/environment.rb6
-rw-r--r--app/models/projects/import_export/relation_export.rb41
-rw-r--r--app/serializers/environment_serializer.rb13
-rw-r--r--app/services/projects/import_export/relation_export_service.rb95
-rw-r--r--app/services/work_items/create_and_link_service.rb2
-rw-r--r--app/views/layouts/header/_default.html.haml19
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/shared/issuable/_assignees.html.haml7
-rw-r--r--app/views/shared/issuable/_merge_request_assignees.html.haml8
-rw-r--r--app/views/shared/issuable/_merge_request_reviewers.html.haml8
-rw-r--r--app/views/shared/issuable/_reviewers.html.haml7
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml10
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/projects/import_export/relation_export_worker.rb26
-rw-r--r--config/feature_flags/development/batch_load_environment_last_deployment_group.yml8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/post_migrate/20220719081542_drop_queued_at_index_from_ci_builds.rb17
-rw-r--r--db/post_migrate/20220719165537_remove_ci_runners_semver_column.rb21
-rw-r--r--db/schema_migrations/202207190815421
-rw-r--r--db/schema_migrations/202207191655371
-rw-r--r--db/structure.sql6
-rw-r--r--doc/api/jobs.md8
-rw-r--r--doc/api/users.md6
-rw-r--r--doc/development/database/ci_mirrored_tables.md156
-rw-r--r--doc/development/documentation/testing.md3
-rw-r--r--doc/user/application_security/container_scanning/index.md3
-rw-r--r--doc/user/project/repository/mirror/index.md5
-rw-r--r--lib/gitlab/background_migration/backfill_ci_runner_semver.rb31
-rw-r--r--lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-On-Demand-API-Scan.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml3
-rw-r--r--lib/gitlab/github_import/importer/events/base_importer.rb32
-rw-r--r--lib/gitlab/github_import/importer/events/changed_assignee.rb52
-rw-r--r--lib/gitlab/github_import/importer/events/changed_label.rb12
-rw-r--r--lib/gitlab/github_import/importer/events/changed_milestone.rb12
-rw-r--r--lib/gitlab/github_import/importer/events/closed.rb14
-rw-r--r--lib/gitlab/github_import/importer/events/cross_referenced.rb19
-rw-r--r--lib/gitlab/github_import/importer/events/renamed.rb12
-rw-r--r--lib/gitlab/github_import/importer/events/reopened.rb14
-rw-r--r--lib/gitlab/github_import/importer/issue_event_importer.rb45
-rw-r--r--lib/gitlab/github_import/representation/issue_event.rb75
-rw-r--r--lib/gitlab/github_import/user_finder.rb12
-rw-r--r--lib/gitlab/import_export/project/relation_saver.rb57
-rw-r--r--locale/gitlab.pot51
-rw-r--r--spec/commands/sidekiq_cluster/cli_spec.rb4
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb14
-rw-r--r--spec/frontend/attention_requests/components/navigation_popover_spec.js88
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js6
-rw-r--r--spec/frontend/runner/components/registration/registration_token_spec.js27
-rw-r--r--spec/frontend/sidebar/components/attention_requested_toggle_spec.js121
-rw-r--r--spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js15
-rw-r--r--spec/frontend/sidebar/sidebar_mediator_spec.js92
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js9
-rw-r--r--spec/frontend/work_items_hierarchy/components/app_spec.js8
-rw-r--r--spec/frontend/work_items_hierarchy/components/hierarchy_spec.js7
-rw-r--r--spec/graphql/types/project_type_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb54
-rw-r--r--spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb15
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb91
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb7
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb7
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/events/renamed_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/events/reopened_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb22
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb33
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb66
-rw-r--r--spec/lib/gitlab/import_export/project/relation_saver_spec.rb125
-rw-r--r--spec/models/environment_spec.rb10
-rw-r--r--spec/models/projects/import_export/relation_export_spec.rb32
-rw-r--r--spec/services/projects/import_export/relation_export_service_spec.rb121
-rw-r--r--spec/services/security/ci_configuration/sast_parser_service_spec.rb1
-rw-r--r--spec/services/work_items/create_and_link_service_spec.rb2
-rw-r--r--spec/services/work_items/create_from_task_service_spec.rb2
-rw-r--r--spec/support/helpers/ci/template_helpers.rb4
-rw-r--r--spec/support/shared_examples/services/work_items/create_task_shared_examples.rb16
-rw-r--r--spec/workers/projects/import_export/relation_export_worker_spec.rb36
117 files changed, 1276 insertions, 1409 deletions
diff --git a/app/assets/javascripts/attention_requests/components/navigation_popover.vue b/app/assets/javascripts/attention_requests/components/navigation_popover.vue
deleted file mode 100644
index 804eda8f321..00000000000
--- a/app/assets/javascripts/attention_requests/components/navigation_popover.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-<script>
-import { GlPopover, GlSprintf, GlButton, GlLink, GlIcon } from '@gitlab/ui';
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import { helpPagePath } from '~/helpers/help_page_helper';
-import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
-
-export default {
- components: {
- GlPopover,
- GlSprintf,
- GlButton,
- GlLink,
- GlIcon,
- UserCalloutDismisser,
- },
- inject: {
- message: {
- default: '',
- },
- observerElSelector: {
- default: '',
- },
- observerElToggledClass: {
- default: '',
- },
- featureName: {
- default: '',
- },
- popoverTarget: {
- default: '',
- },
- showAttentionIcon: {
- default: false,
- },
- delay: {
- default: 0,
- },
- popoverCssClass: {
- default: '',
- },
- },
- data() {
- return {
- showPopover: false,
- popoverPlacement: this.popoverPosition(),
- };
- },
- mounted() {
- this.observeEl = document.querySelector(this.observerElSelector);
- this.observer = new MutationObserver(this.callback);
- this.observer.observe(this.observeEl, {
- attributes: true,
- });
- this.callback();
-
- window.addEventListener('resize', () => {
- this.popoverPlacement = this.popoverPosition();
- });
- },
- beforeDestroy() {
- this.observer.disconnect();
- },
- methods: {
- callback() {
- if (this.showPopover) {
- this.$root.$emit('bv::hide::popover');
- }
-
- setTimeout(() => this.toggleShowPopover(), this.delay);
- },
- toggleShowPopover() {
- this.showPopover = this.observeEl.classList.contains(this.observerElToggledClass);
- },
- getPopoverTarget() {
- return document.querySelector(this.popoverTarget);
- },
- popoverPosition() {
- if (bp.isDesktop()) {
- return 'left';
- }
-
- return 'bottom';
- },
- },
- docsPage: helpPagePath('user/project/merge_requests/index.md', {
- anchor: 'request-attention-to-a-merge-request',
- }),
-};
-</script>
-
-<template>
- <user-callout-dismisser :feature-name="featureName">
- <template #default="{ shouldShowCallout, dismiss }">
- <gl-popover
- v-if="shouldShowCallout"
- :show-close-button="false"
- :target="() => getPopoverTarget()"
- :show="showPopover"
- :delay="0"
- triggers="manual"
- :placement="popoverPlacement"
- boundary="window"
- no-fade
- :css-classes="[popoverCssClass]"
- >
- <p v-for="(m, index) in message" :key="index" class="gl-mb-5">
- <gl-sprintf :message="m">
- <template #strong="{ content }">
- <strong><gl-icon v-if="showAttentionIcon" name="attention" /> {{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <div class="gl-display-flex gl-align-items-center">
- <gl-button size="small" variant="confirm" class="gl-mr-5" @click.prevent.stop="dismiss">
- {{ __('Got it!') }}
- </gl-button>
- <gl-link :href="$options.docsPage" target="_blank">{{ __('Learn more') }}</gl-link>
- </div>
- </gl-popover>
- </template>
- </user-callout-dismisser>
-</template>
diff --git a/app/assets/javascripts/attention_requests/index.js b/app/assets/javascripts/attention_requests/index.js
deleted file mode 100644
index 2a142ab46e5..00000000000
--- a/app/assets/javascripts/attention_requests/index.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import { __ } from '~/locale';
-import createDefaultClient from '~/lib/graphql';
-import NavigationPopover from './components/navigation_popover.vue';
-
-Vue.use(VueApollo);
-
-const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
-});
-
-export const initTopNavPopover = () => {
- const el = document.getElementById('js-need-attention-nav-onboarding');
-
- if (!el) return;
-
- // eslint-disable-next-line no-new
- new Vue({
- el,
- apolloProvider,
- provide: {
- observerElSelector: '.user-counter.dropdown',
- observerElToggledClass: 'show',
- message: [
- __(
- '%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer.',
- ),
- ],
- featureName: 'attention_requests_top_nav',
- popoverTarget: '#js-need-attention-nav',
- },
- render(h) {
- return h(NavigationPopover);
- },
- });
-};
-
-export const initSideNavPopover = () => {
- const el = document.getElementById('js-need-attention-sidebar-onboarding');
-
- if (!el) return;
-
- // eslint-disable-next-line no-new
- new Vue({
- el,
- apolloProvider,
- provide: {
- observerElSelector: '.js-right-sidebar',
- observerElToggledClass: 'right-sidebar-expanded',
- message: [
- __(
- 'To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request.',
- ),
- __(
- 'Some actions remove attention requests, like a reviewer approving or anyone merging the merge request.',
- ),
- ],
- featureName: 'attention_requests_side_nav',
- popoverTarget: '.js-attention-request-toggle',
- showAttentionIcon: true,
- delay: 500,
- popoverCssClass: 'attention-request-sidebar-popover',
- },
- render(h) {
- return h(NavigationPopover);
- },
- });
-};
-
-export default () => {
- initTopNavPopover();
-};
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
index 784e9cb2faa..b105273ece7 100644
--- a/app/assets/javascripts/commons/nav/user_merge_requests.js
+++ b/app/assets/javascripts/commons/nav/user_merge_requests.js
@@ -26,39 +26,20 @@ function updateMergeRequestCounts(newCount) {
mergeRequestsCountEl.classList.toggle('gl-display-none', Number(newCount) === 0);
}
-function updateAttentionRequestsCount(count) {
- const attentionCountEl = document.querySelector('.js-attention-count');
- attentionCountEl.textContent = count.toLocaleString();
-
- if (Number(count) === 0) {
- attentionCountEl.classList.replace('badge-warning', 'badge-neutral');
- } else {
- attentionCountEl.classList.replace('badge-neutral', 'badge-warning');
- }
-}
-
/**
* Refresh user counts (and broadcast if open)
*/
export function refreshUserMergeRequestCounts() {
return getUserCounts()
.then(({ data }) => {
- const attentionRequestsEnabled = window.gon?.features?.mrAttentionRequests;
const assignedMergeRequests = data.assigned_merge_requests;
const reviewerMergeRequests = data.review_requested_merge_requests;
- const attentionRequests = data.attention_requests;
- const fullCount = attentionRequestsEnabled
- ? attentionRequests
- : assignedMergeRequests + reviewerMergeRequests;
+ const fullCount = assignedMergeRequests + reviewerMergeRequests;
updateUserMergeRequestCounts(assignedMergeRequests);
updateReviewerMergeRequestCounts(reviewerMergeRequests);
updateMergeRequestCounts(fullCount);
broadcastCount(fullCount);
-
- if (attentionRequestsEnabled) {
- updateAttentionRequestsCount(attentionRequests);
- }
})
.catch((ex) => {
console.error(ex); // eslint-disable-line no-console
diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
index 28a3c54cc8f..d00e6e59cf5 100644
--- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
+++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
@@ -13,21 +13,6 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => {
IssuableTokenKeys.tokenKeys.splice(2, 0, reviewerToken);
IssuableTokenKeys.tokenKeysWithAlternative.splice(2, 0, reviewerToken);
- if (window.gon?.features?.mrAttentionRequests) {
- const attentionRequestedToken = {
- formattedKey: __('Attention'),
- key: 'attention',
- type: 'string',
- param: '',
- symbol: '@',
- icon: 'user',
- tag: '@attention',
- hideNotEqual: true,
- };
- IssuableTokenKeys.tokenKeys.splice(2, 0, attentionRequestedToken);
- IssuableTokenKeys.tokenKeysWithAlternative.splice(2, 0, attentionRequestedToken);
- }
-
const draftToken = {
token: {
formattedKey: __('Draft'),
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index d4dafbdc94f..7aea3715971 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -276,8 +276,6 @@ class GfmAutoComplete {
UNASSIGN_REVIEWER: '/unassign_reviewer',
REASSIGN: '/reassign',
CC: '/cc',
- ATTENTION: '/attention',
- REMOVE_ATTENTION: '/remove_attention',
};
let assignees = [];
let reviewers = [];
@@ -356,23 +354,6 @@ class GfmAutoComplete {
} else if (command === MEMBER_COMMAND.UNASSIGN_REVIEWER) {
// Only include members which are not assigned as a reviewer to Issuable currently
return data.filter((member) => reviewers.includes(member.search));
- } else if (
- command === MEMBER_COMMAND.ATTENTION ||
- command === MEMBER_COMMAND.REMOVE_ATTENTION
- ) {
- const attentionUsers = [
- ...(SidebarMediator.singleton?.store?.assignees || []),
- ...(SidebarMediator.singleton?.store?.reviewers || []),
- ];
- const attentionRequested = command === MEMBER_COMMAND.REMOVE_ATTENTION;
-
- return data.filter((member) =>
- attentionUsers.find(
- (u) =>
- createMemberSearchString(u).includes(member.search) &&
- u.attention_requested === attentionRequested,
- ),
- );
}
return data;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 349a28ace52..c16ed68096d 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -134,12 +134,6 @@ function deferredInitialisation() {
// Adding a helper class to activate animations only after all is rendered
setTimeout(() => $body.addClass('page-initialised'), 1000);
-
- if (window.gon?.features?.mrAttentionRequests) {
- import('~/attention_requests')
- .then((module) => module.default())
- .catch(() => {});
- }
}
// header search vue component bootstrap
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index 3602b5ec4f6..29ea390a81d 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -39,9 +39,6 @@ export default {
assignSelf() {
this.$emit('assign-self');
},
- toggleAttentionRequested(data) {
- this.$emit('toggle-attention-requested', data);
- },
},
};
</script>
@@ -66,12 +63,7 @@ export default {
</template>
</span>
- <uncollapsed-assignee-list
- v-else
- :users="sortedAssigness"
- :issuable-type="issuableType"
- @toggle-attention-requested="toggleAttentionRequested"
- />
+ <uncollapsed-assignee-list v-else :users="sortedAssigness" :issuable-type="issuableType" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
index 59a4eb54bbe..a94dd128a1a 100644
--- a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
@@ -32,11 +32,6 @@ export default {
return this.users.length === 0;
},
},
- methods: {
- toggleAttentionRequested(data) {
- this.$emit('toggle-attention-requested', data);
- },
- },
};
</script>
@@ -66,7 +61,6 @@ export default {
:users="users"
:issuable-type="issuableType"
class="gl-text-gray-800 hide-collapsed"
- @toggle-attention-requested="toggleAttentionRequested"
/>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index e596d6292bf..18b26c7d8bd 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -125,9 +125,6 @@ export default {
availability: this.assigneeAvailabilityStatus[username] || '',
}));
},
- toggleAttentionRequested(data) {
- this.mediator.toggleAttentionRequested('assignee', data);
- },
},
};
</script>
@@ -155,7 +152,6 @@ export default {
:editable="store.editable"
:issuable-type="issuableType"
@assign-self="assignSelf"
- @toggle-attention-requested="toggleAttentionRequested"
/>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index b6260418837..0e4d4c74160 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -2,7 +2,6 @@
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { IssuableType } from '~/issues/constants';
import { __, sprintf } from '~/locale';
-import AttentionRequestedToggle from '../attention_requested_toggle.vue';
import AssigneeAvatarLink from './assignee_avatar_link.vue';
import UserNameWithStatus from './user_name_with_status.vue';
@@ -10,7 +9,6 @@ const DEFAULT_RENDER_COUNT = 5;
export default {
components: {
- AttentionRequestedToggle,
AssigneeAvatarLink,
UserNameWithStatus,
},
@@ -46,10 +44,6 @@ export default {
return this.users.length - DEFAULT_RENDER_COUNT;
},
uncollapsedUsers() {
- if (this.showVerticalList) {
- return this.users;
- }
-
const uncollapsedLength = this.showLess
? Math.min(this.users.length, DEFAULT_RENDER_COUNT)
: this.users.length;
@@ -58,9 +52,6 @@ export default {
username() {
return `@${this.firstUser.username}`;
},
- showVerticalList() {
- return this.glFeatures.mrAttentionRequests && this.isMergeRequest;
- },
isMergeRequest() {
return this.issuableType === IssuableType.MergeRequest;
},
@@ -75,9 +66,6 @@ export default {
}
return u?.status?.availability || '';
},
- toggleAttentionRequested(data) {
- this.$emit('toggle-attention-requested', data);
- },
},
};
</script>
@@ -96,7 +84,7 @@ export default {
<assignee-avatar-link
:user="user"
:issuable-type="issuableType"
- :tooltip-has-name="!showVerticalList"
+ :tooltip-has-name="!isMergeRequest"
class="gl-word-break-word"
data-css-area="user"
>
@@ -107,14 +95,6 @@ export default {
<user-name-with-status :name="user.name" :availability="userAvailability(user)" />
</div>
</assignee-avatar-link>
- <attention-requested-toggle
- v-if="showVerticalList"
- :user="user"
- type="assignee"
- class="gl-mr-2"
- data-css-area="attention"
- @toggle-attention-requested="toggleAttentionRequested"
- />
</div>
</div>
<div v-if="renderShowMoreSection" class="user-list-more gl-hover-text-blue-800">
diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue
deleted file mode 100644
index 974ad189f32..00000000000
--- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<script>
-import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import { __ } from '~/locale';
-import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-
-export default {
- i18n: {
- addAttentionRequest: __('Add attention request'),
- removeAttentionRequest: __('Remove attention request'),
- attentionRequestedNoPermission: __('Attention requested'),
- noAttentionRequestedNoPermission: __('No attention request'),
- },
- components: {
- GlButton,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- type: {
- type: String,
- required: true,
- },
- user: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- loading: false,
- };
- },
- computed: {
- tooltipTitle() {
- if (this.user.attention_requested) {
- if (this.user.can_update_merge_request) {
- return this.$options.i18n.removeAttentionRequest;
- }
-
- return this.$options.i18n.attentionRequestedNoPermission;
- }
-
- if (this.user.can_update_merge_request) {
- return this.$options.i18n.addAttentionRequest;
- }
-
- return this.$options.i18n.noAttentionRequestedNoPermission;
- },
- request() {
- const state = {
- selected: false,
- icon: 'attention',
- direction: 'add',
- };
-
- if (this.user.attention_requested) {
- Object.assign(state, {
- selected: true,
- icon: 'attention-solid',
- direction: 'remove',
- });
- }
-
- return state;
- },
- },
- methods: {
- toggleAttentionRequired() {
- if (this.loading || !this.user.can_update_merge_request) return;
-
- this.$root.$emit(BV_HIDE_TOOLTIP);
- this.loading = true;
- this.$emit('toggle-attention-requested', {
- user: this.user,
- callback: this.toggleAttentionRequiredComplete,
- direction: this.request.direction,
- });
- },
- toggleAttentionRequiredComplete() {
- this.loading = false;
- },
- },
-};
-</script>
-
-<template>
- <div>
- <span
- v-gl-tooltip.left.viewport="tooltipTitle"
- class="gl-display-inline-block js-attention-request-toggle"
- >
- <gl-button
- :loading="loading"
- :selected="request.selected"
- :icon="request.icon"
- :aria-label="tooltipTitle"
- :class="{ 'gl-pointer-events-none': !user.can_update_merge_request }"
- size="small"
- category="tertiary"
- @click="toggleAttentionRequired"
- />
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
index b07fd944ff9..5729b958b5d 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/reviewers.vue
@@ -49,9 +49,6 @@ export default {
requestReview(data) {
this.$emit('request-review', data);
},
- toggleAttentionRequested(data) {
- this.$emit('toggle-attention-requested', data);
- },
},
};
</script>
@@ -73,7 +70,6 @@ export default {
:root-path="rootPath"
:issuable-type="issuableType"
@request-review="requestReview"
- @toggle-attention-requested="toggleAttentionRequested"
/>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index 2ea63219e92..e414aaf719b 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -88,9 +88,6 @@ export default {
requestReview(data) {
this.mediator.requestReview(data);
},
- toggleAttentionRequested(data) {
- this.mediator.toggleAttentionRequested('reviewer', data);
- },
},
};
</script>
@@ -109,7 +106,6 @@ export default {
:editable="store.editable"
:issuable-type="issuableType"
@request-review="requestReview"
- @toggle-attention-requested="toggleAttentionRequested"
/>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index 2f58e11c00f..3aeb49eb422 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -1,8 +1,6 @@
<script>
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, sprintf, s__ } from '~/locale';
-import AttentionRequestedToggle from '../attention_requested_toggle.vue';
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
const LOADING_STATE = 'loading';
@@ -16,12 +14,10 @@ export default {
GlButton,
GlIcon,
ReviewerAvatarLink,
- AttentionRequestedToggle,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagsMixin()],
props: {
users: {
type: Array,
@@ -80,9 +76,6 @@ export default {
this.loadingStates[userId] = null;
}
},
- toggleAttentionRequested(data) {
- this.$emit('toggle-attention-requested', data);
- },
},
LOADING_STATE,
SUCCESS_STATE,
@@ -96,7 +89,6 @@ export default {
:key="user.id"
:class="{
'gl-mb-3': index !== users.length - 1,
- 'attention-requests': glFeatures.mrAttentionRequests,
}"
class="gl-display-grid gl-align-items-center reviewer-grid gl-mr-2"
data-testid="reviewer"
@@ -112,14 +104,6 @@ export default {
{{ user.name }}
</div>
</reviewer-avatar-link>
- <attention-requested-toggle
- v-if="glFeatures.mrAttentionRequests"
- :user="user"
- type="reviewer"
- class="gl-mr-2"
- data-css-area="attention"
- @toggle-attention-requested="toggleAttentionRequested"
- />
<gl-icon
v-if="user.approved"
v-gl-tooltip.left
@@ -137,9 +121,7 @@ export default {
data-testid="re-request-success"
/>
<gl-button
- v-else-if="
- user.can_update_merge_request && user.reviewed && !glFeatures.mrAttentionRequests
- "
+ v-else-if="user.can_update_merge_request && user.reviewed"
v-gl-tooltip.left
:title="$options.i18n.reRequestReview"
:aria-label="$options.i18n.reRequestReview"
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 3f82fe5ce87..84e7c0f876a 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -27,8 +27,6 @@ import trackShowInviteMemberLink from '~/sidebar/track_invite_members';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
-import eventHub from '~/sidebar/event_hub';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue';
@@ -652,13 +650,6 @@ export function mountSidebar(mediator, store) {
mountSeverityComponent();
mountEscalationStatusComponent();
-
- if (window.gon?.features?.mrAttentionRequests) {
- eventHub.$on('removeCurrentUserAttentionRequested', () => {
- mediator.removeCurrentUserAttentionRequested();
- refreshUserMergeRequestCounts();
- });
- }
}
export { getSidebarOptions };
diff --git a/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql b/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
deleted file mode 100644
index d9b9c04fd63..00000000000
--- a/app/assets/javascripts/sidebar/queries/remove_attention_request.mutation.graphql
+++ /dev/null
@@ -1,7 +0,0 @@
-mutation mergeRequestRemoveAttentionRequest($projectPath: ID!, $iid: String!, $userId: UserID!) {
- mergeRequestRemoveAttentionRequest(
- input: { projectPath: $projectPath, iid: $iid, userId: $userId }
- ) {
- errors
- }
-}
diff --git a/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql b/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
deleted file mode 100644
index 99a86e4fe5c..00000000000
--- a/app/assets/javascripts/sidebar/queries/request_attention.mutation.graphql
+++ /dev/null
@@ -1,5 +0,0 @@
-mutation mergeRequestRequestAttention($projectPath: ID!, $iid: String!, $userId: UserID!) {
- mergeRequestRequestAttention(input: { projectPath: $projectPath, iid: $iid, userId: $userId }) {
- errors
- }
-}
diff --git a/app/assets/javascripts/sidebar/services/sidebar_service.js b/app/assets/javascripts/sidebar/services/sidebar_service.js
index 05268a5c89c..beacdeb559c 100644
--- a/app/assets/javascripts/sidebar/services/sidebar_service.js
+++ b/app/assets/javascripts/sidebar/services/sidebar_service.js
@@ -5,8 +5,6 @@ import createGqClient, { fetchPolicies } from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import reviewerRereviewMutation from '../queries/reviewer_rereview.mutation.graphql';
import sidebarDetailsMRQuery from '../queries/sidebar_details_mr.query.graphql';
-import requestAttentionMutation from '../queries/request_attention.mutation.graphql';
-import removeAttentionRequestMutation from '../queries/remove_attention_request.mutation.graphql';
const queries = {
merge_request: sidebarDetailsMRQuery,
@@ -93,25 +91,4 @@ export default class SidebarService {
},
});
}
-
- requestAttention(userId) {
- return gqClient.mutate({
- mutation: requestAttentionMutation,
- variables: {
- userId: convertToGraphQLId(TYPE_USER, `${userId}`),
- projectPath: this.fullPath,
- iid: this.iid.toString(),
- },
- });
- }
- removeAttentionRequest(userId) {
- return gqClient.mutate({
- mutation: removeAttentionRequestMutation,
- variables: {
- userId: convertToGraphQLId(TYPE_USER, `${userId}`),
- projectPath: this.fullPath,
- iid: this.iid.toString(),
- },
- });
- }
}
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index 74ab65e4e04..1be670f7590 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -3,17 +3,7 @@ import Mediator from './sidebar_mediator';
export default (store) => {
const mediator = new Mediator(getSidebarOptions());
- mediator
- .fetch()
- .then(() => {
- if (window.gon?.features?.mrAttentionRequests) {
- return import('~/attention_requests');
- }
-
- return null;
- })
- .then((module) => module?.initSideNavPopover())
- .catch(() => {});
+ mediator.fetch();
mountSidebar(mediator, store);
};
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 4df00903ab6..f7c93b6903c 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -1,8 +1,7 @@
import Store from '~/sidebar/stores/sidebar_store';
import createFlash from '~/flash';
-import { __, sprintf } from '~/locale';
+import { __ } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import { visitUrl } from '../lib/utils/url_utility';
import Service from './services/sidebar_service';
@@ -42,7 +41,6 @@ export default class SidebarMediator {
const data = { assignee_ids: assignees };
try {
- const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('assignees', res.data.assignees);
@@ -51,10 +49,6 @@ export default class SidebarMediator {
this.store.overwrite('reviewers', res.data.reviewers);
}
- if (currentUserHasAttention && this.store.isAddingAssignee) {
- toast(__('Assigned user(s). Your attention request was removed.'));
- }
-
return Promise.resolve(res);
} catch (e) {
return Promise.reject(e);
@@ -70,16 +64,11 @@ export default class SidebarMediator {
const data = { reviewer_ids: reviewers };
try {
- const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('reviewers', res.data.reviewers);
this.store.overwrite('assignees', res.data.assignees);
- if (currentUserHasAttention && this.store.isAddingAssignee) {
- toast(__('Requested review. Your attention request was removed.'));
- }
-
return Promise.resolve(res);
} catch (e) {
return Promise.reject();
@@ -97,80 +86,6 @@ export default class SidebarMediator {
.catch(() => callback(userId, false));
}
- removeCurrentUserAttentionRequested() {
- const currentUserId = gon.current_user_id;
-
- const currentUserReviewer = this.store.findReviewer({ id: currentUserId });
- const currentUserAssignee = this.store.findAssignee({ id: currentUserId });
-
- if (currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested) {
- // Update current users attention_requested state
- this.store.updateReviewer(currentUserId, 'attention_requested');
- this.store.updateAssignee(currentUserId, 'attention_requested');
- }
- }
-
- async toggleAttentionRequested(type, { user, callback, direction }) {
- const mutations = {
- add: (id) => this.service.requestAttention(id),
- remove: (id) => this.service.removeAttentionRequest(id),
- };
-
- try {
- const isReviewer = type === 'reviewer';
- const reviewerOrAssignee = isReviewer
- ? this.store.findReviewer(user)
- : this.store.findAssignee(user);
-
- await mutations[direction]?.(user.id);
-
- if (reviewerOrAssignee.attention_requested) {
- toast(
- sprintf(__('Removed attention request from @%{username}'), {
- username: user.username,
- }),
- );
- } else {
- const currentUserId = gon.current_user_id;
- const { currentUserHasAttention } = this.store;
-
- if (currentUserId !== user.id) {
- this.removeCurrentUserAttentionRequested();
- }
-
- toast(
- currentUserHasAttention && currentUserId !== user.id
- ? sprintf(
- __(
- 'Requested attention from @%{username}. Your own attention request was removed.',
- ),
- { username: user.username },
- )
- : sprintf(__('Requested attention from @%{username}'), { username: user.username }),
- );
- }
-
- this.store.updateReviewer(user.id, 'attention_requested');
- this.store.updateAssignee(user.id, 'attention_requested');
-
- refreshUserMergeRequestCounts();
- callback();
- } catch (error) {
- callback();
- createFlash({
- message: sprintf(__('Updating the attention request for %{username} failed.'), {
- username: user.username,
- }),
- error,
- captureError: true,
- actionConfig: {
- title: __('Try again'),
- clickHandler: () => this.toggleAttentionRequired(type, { user, callback, direction }),
- },
- });
- }
- }
-
setMoveToProjectId(projectId) {
this.store.setMoveToProjectId(projectId);
}
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 971e2a15c68..e2581a8f30e 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -19,9 +19,7 @@ export default class SidebarStore {
this.humanTimeSpent = '';
this.timeTrackingLimitToHours = timeTrackingLimitToHours;
this.assignees = [];
- this.addingAssignees = [];
this.reviewers = [];
- this.addingReviewers = [];
this.isFetching = {
assignees: true,
reviewers: true,
@@ -77,20 +75,12 @@ export default class SidebarStore {
if (!this.findAssignee(assignee)) {
this.changing = true;
this.assignees.push(assignee);
-
- if (assignee.id !== this.currentUser.id) {
- this.addingAssignees.push(assignee.id);
- }
}
}
addReviewer(reviewer) {
if (!this.findReviewer(reviewer)) {
this.reviewers.push(reviewer);
-
- if (reviewer.id !== this.currentUser.id) {
- this.addingReviewers.push(reviewer.id);
- }
}
}
@@ -126,14 +116,12 @@ export default class SidebarStore {
if (assignee) {
this.changing = true;
this.assignees = this.assignees.filter(({ id }) => id !== assignee.id);
- this.addingAssignees = this.addingAssignees.filter(({ id }) => id !== assignee.id);
}
}
removeReviewer(reviewer) {
if (reviewer) {
this.reviewers = this.reviewers.filter(({ id }) => id !== reviewer.id);
- this.addingReviewers = this.addingReviewers.filter(({ id }) => id !== reviewer.id);
}
}
@@ -161,26 +149,4 @@ export default class SidebarStore {
setMoveToProjectId(moveToProjectId) {
this.moveToProjectId = moveToProjectId;
}
-
- get currentUserHasAttention() {
- if (!window.gon?.features?.mrAttentionRequests || !this.isMergeRequest) return false;
-
- const currentUserId = this.currentUser.id;
- const currentUserReviewer = this.findReviewer({ id: currentUserId });
- const currentUserAssignee = this.findAssignee({ id: currentUserId });
-
- return currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested;
- }
-
- get isAddingAssignee() {
- return this.addingAssignees.length > 0;
- }
-
- get isAddingReviewer() {
- return this.addingReviewers.length > 0;
- }
-
- get isMergeRequest() {
- return this.issuableType === 'merge_request';
- }
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 4163d195e0f..f782c28ea19 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -4,9 +4,6 @@ import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__, __ } from '~/locale';
-import sidebarEventHub from '~/sidebar/event_hub';
-import showToast from '~/vue_shared/plugins/global_toast';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue';
@@ -192,16 +189,8 @@ export default {
.then((data) => {
this.mr.setApprovals(data);
- if (
- this.glFeatures.mrAttentionRequests &&
- SidebarMediator.singleton?.store.currentUserHasAttention
- ) {
- showToast(__('Approved. Your attention request was removed.'));
- }
-
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated');
- sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
this.$emit('updated');
})
.catch(errFn)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index a2f018c013b..6f1f37b86cf 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -43,7 +43,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:refactor_security_extension, @project)
push_frontend_feature_flag(:refactor_code_quality_inline_findings, project)
- push_frontend_feature_flag(:mr_attention_requests, current_user)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:paginated_mr_discussions, project)
push_frontend_feature_flag(:mr_review_submit_comment, project)
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index bcb1f63840d..f0e1f252917 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -15,10 +15,6 @@ module DashboardHelper
merge_requests_dashboard_path(reviewer_username: current_user.username)
end
- def attention_requested_mrs_dashboard_path
- merge_requests_dashboard_path(attention: current_user.username)
- end
-
def dashboard_nav_links
@dashboard_nav_links ||= get_dashboard_nav_links
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index b6b94dbb1fa..47d9cb9393e 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -15,7 +15,7 @@ module Ci
include Presentable
include EachBatch
- ignore_column :semver, remove_with: '15.3', remove_after: '2022-07-22'
+ ignore_column :semver, remove_with: '15.4', remove_after: '2022-08-22'
add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
diff --git a/app/models/environment.rb b/app/models/environment.rb
index f7becf1f700..b93f1158a90 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -331,11 +331,7 @@ class Environment < ApplicationRecord
end
def last_deployment_group
- if ::Feature.enabled?(:batch_load_environment_last_deployment_group, project)
- Deployment.last_deployment_group_for_environment(self)
- else
- legacy_last_deployment_group
- end
+ Deployment.last_deployment_group_for_environment(self)
end
def reset_auto_stop
diff --git a/app/models/projects/import_export/relation_export.rb b/app/models/projects/import_export/relation_export.rb
index 0a31e525ac2..15198049f87 100644
--- a/app/models/projects/import_export/relation_export.rb
+++ b/app/models/projects/import_export/relation_export.rb
@@ -3,6 +3,20 @@
module Projects
module ImportExport
class RelationExport < ApplicationRecord
+ DESIGN_REPOSITORY_RELATION = 'design_repository'
+ LFS_OBJECTS_RELATION = 'lfs_objects'
+ REPOSITORY_RELATION = 'repository'
+ ROOT_RELATION = 'project'
+ SNIPPETS_REPOSITORY_RELATION = 'snippets_repository'
+ UPLOADS_RELATION = 'uploads'
+ WIKI_REPOSITORY_RELATION = 'wiki_repository'
+
+ EXTRA_RELATION_LIST = [
+ DESIGN_REPOSITORY_RELATION, LFS_OBJECTS_RELATION, REPOSITORY_RELATION, ROOT_RELATION,
+ SNIPPETS_REPOSITORY_RELATION, UPLOADS_RELATION, WIKI_REPOSITORY_RELATION
+ ].freeze
+ private_constant :EXTRA_RELATION_LIST
+
self.table_name = 'project_relation_exports'
belongs_to :project_export_job
@@ -17,6 +31,33 @@ module Projects
validates :project_export_job, presence: true
validates :relation, presence: true, length: { maximum: 255 }, uniqueness: { scope: :project_export_job_id }
validates :status, numericality: { only_integer: true }, presence: true
+
+ scope :by_relation, -> (relation) { where(relation: relation) }
+
+ state_machine :status, initial: :queued do
+ state :queued, value: 0
+ state :started, value: 1
+ state :finished, value: 2
+ state :failed, value: 3
+
+ event :start do
+ transition queued: :started
+ end
+
+ event :finish do
+ transition started: :finished
+ end
+
+ event :fail_op do
+ transition [:queued, :started] => :failed
+ end
+ end
+
+ def self.relation_names_list
+ project_tree_relation_names = ::Gitlab::ImportExport::Reader.new(shared: nil).project_relation_names.map(&:to_s)
+
+ project_tree_relation_names + EXTRA_RELATION_LIST
+ end
end
end
end
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index 3f236fa55df..beee5522a06 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -56,10 +56,6 @@ class EnvironmentSerializer < BaseSerializer
resource = resource.preload(environment_associations.except(:last_deployment, :upcoming_deployment))
- if ::Feature.enabled?(:batch_load_environment_last_deployment_group, resource.first&.project)
- temp_deployment_associations[:deployable][:pipeline][:latest_successful_builds] = []
- end
-
Preloaders::Environments::DeploymentPreloader.new(resource)
.execute_with_union(:last_deployment, temp_deployment_associations)
@@ -72,10 +68,8 @@ class EnvironmentSerializer < BaseSerializer
environment.last_deployment&.commit&.try(:lazy_author)
environment.upcoming_deployment&.commit&.try(:lazy_author)
- if ::Feature.enabled?(:batch_load_environment_last_deployment_group, environment.project)
- # Batch loading last_deployment_group which is called later by environment.stop_actions
- environment.last_deployment_group
- end
+ # Batch loading last_deployment_group which is called later by environment.stop_actions
+ environment.last_deployment_group
end
end
end
@@ -101,7 +95,8 @@ class EnvironmentSerializer < BaseSerializer
metadata: [],
pipeline: {
manual_actions: [:metadata, :deployment],
- scheduled_actions: [:metadata]
+ scheduled_actions: [:metadata],
+ latest_successful_builds: []
},
project: project_associations,
deployment: []
diff --git a/app/services/projects/import_export/relation_export_service.rb b/app/services/projects/import_export/relation_export_service.rb
new file mode 100644
index 00000000000..dce40cf18ba
--- /dev/null
+++ b/app/services/projects/import_export/relation_export_service.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Projects
+ module ImportExport
+ class RelationExportService
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize(relation_export, jid)
+ @relation_export = relation_export
+ @jid = jid
+ @logger = Gitlab::Export::Logger.build
+ end
+
+ def execute
+ relation_export.update!(status_event: :start, jid: jid)
+
+ mkdir_p(shared.export_path)
+ mkdir_p(shared.archive_path)
+
+ if relation_saver.save
+ compress_export_path
+ upload_compressed_file
+ relation_export.finish!
+ else
+ fail_export(shared.errors.join(', '))
+ end
+ rescue StandardError => e
+ fail_export(e.message)
+ ensure
+ FileUtils.remove_entry(shared.export_path) if File.exist?(shared.export_path)
+ FileUtils.remove_entry(shared.archive_path) if File.exist?(shared.archive_path)
+ end
+
+ private
+
+ attr_reader :relation_export, :jid, :logger
+
+ delegate :relation, :project_export_job, to: :relation_export
+ delegate :project, to: :project_export_job
+
+ def shared
+ project.import_export_shared
+ end
+
+ def relation_saver
+ case relation
+ when Projects::ImportExport::RelationExport::UPLOADS_RELATION
+ Gitlab::ImportExport::UploadsSaver.new(project: project, shared: shared)
+ when Projects::ImportExport::RelationExport::REPOSITORY_RELATION
+ Gitlab::ImportExport::RepoSaver.new(exportable: project, shared: shared)
+ when Projects::ImportExport::RelationExport::WIKI_REPOSITORY_RELATION
+ Gitlab::ImportExport::WikiRepoSaver.new(exportable: project, shared: shared)
+ when Projects::ImportExport::RelationExport::LFS_OBJECTS_RELATION
+ Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared)
+ when Projects::ImportExport::RelationExport::SNIPPETS_REPOSITORY_RELATION
+ Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: nil)
+ when Projects::ImportExport::RelationExport::DESIGN_REPOSITORY_RELATION
+ Gitlab::ImportExport::DesignRepoSaver.new(exportable: project, shared: shared)
+ else
+ Gitlab::ImportExport::Project::RelationSaver.new(
+ project: project,
+ shared: shared,
+ relation: relation
+ )
+ end
+ end
+
+ def upload_compressed_file
+ upload = relation_export.build_upload
+ File.open(archive_file_full_path) { |file| upload.export_file = file }
+ upload.save!
+ end
+
+ def compress_export_path
+ tar_czf(archive: archive_file_full_path, dir: shared.export_path)
+ end
+
+ def archive_file_full_path
+ @archive_file ||= File.join(shared.archive_path, "#{relation}.tar.gz")
+ end
+
+ def fail_export(error_message)
+ relation_export.update!(status_event: :fail_op, export_error: error_message.truncate(300))
+
+ logger.error(
+ message: 'Project relation export failed',
+ export_error: error_message,
+ project_export_job_id: project_export_job.id,
+ project_name: project.name,
+ project_id: project.id
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/work_items/create_and_link_service.rb b/app/services/work_items/create_and_link_service.rb
index af9084e74a7..5cc358c4b4f 100644
--- a/app/services/work_items/create_and_link_service.rb
+++ b/app/services/work_items/create_and_link_service.rb
@@ -18,7 +18,7 @@ module WorkItems
create_result = CreateService.new(
project: @project,
current_user: @current_user,
- params: @params.reverse_merge(confidential: confidential_parent),
+ params: @params.merge(title: @params[:title].strip).reverse_merge(confidential: confidential_parent),
spam_params: @spam_params
).execute
return create_result if create_result.error?
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 911cb85de53..1d6a6c2aed5 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -57,7 +57,7 @@
= number_with_delimiter(issues_count)
- if header_link?(:merge_requests)
= nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do
- - top_level_link = current_user.mr_attention_requests_enabled? ? attention_requested_mrs_dashboard_path : assigned_mrs_dashboard_path
+ - top_level_link = assigned_mrs_dashboard_path
= link_to top_level_link, class: 'dashboard-shortcuts-merge_requests', title: _('Merge requests'), aria: { label: _('Merge requests') },
data: { qa_selector: 'merge_requests_shortcut_button',
toggle: "dropdown",
@@ -74,27 +74,14 @@
%ul
%li.dropdown-header
= _('Merge requests')
- - if current_user.mr_attention_requests_enabled?
- %li#js-need-attention-nav
- #js-need-attention-nav-onboarding
- = link_to attention_requested_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
- = _('Need your attention')
- = gl_badge_tag user_merge_requests_counts[:attention_requested_count], { size: :sm, variant: user_merge_requests_counts[:attention_requested_count] == 0 ? :neutral : :warning }, { class: 'merge-request-badge gl-ml-auto js-attention-count' }
- %li.divider
%li
= link_to assigned_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
- - if current_user.mr_attention_requests_enabled?
- = _('Assignee')
- - else
- = _('Assigned to you')
+ = _('Assigned to you')
= gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-assigned-mr-count gl-ml-auto" }) do
= user_merge_requests_counts[:assigned]
%li
= link_to reviewer_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
- - if current_user.mr_attention_requests_enabled?
- = _('Reviewer')
- - else
- = _('Review requests for you')
+ = _('Review requests for you')
= gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-reviewer-mr-count gl-ml-auto" }) do
= user_merge_requests_counts[:review_requested]
- if header_link?(:todos)
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 4ef557fbd8f..a84615e329f 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -102,8 +102,5 @@
- if Feature.enabled?(:mr_experience_survey, @project)
#js-mr-experience-survey
-- if current_user&.mr_attention_requests_enabled?
- #js-need-attention-sidebar-onboarding
-
= render 'projects/invite_members_modal', project: @project
= render 'shared/web_ide_path'
diff --git a/app/views/shared/issuable/_assignees.html.haml b/app/views/shared/issuable/_assignees.html.haml
index 112b0368a3a..5326b26d655 100644
--- a/app/views/shared/issuable/_assignees.html.haml
+++ b/app/views/shared/issuable/_assignees.html.haml
@@ -3,11 +3,8 @@
- render_count = assignees_rendering_overflow ? max_render - 1 : max_render
- more_assignees_count = issuable.assignees.size - render_count
-- if issuable.instance_of?(MergeRequest) && current_user&.mr_attention_requests_enabled?
- = render 'shared/issuable/merge_request_assignees', issuable: issuable, count: render_count
-- else
- - issuable.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord
- = link_to_member(@project, assignee, name: false, title: s_("MrList|Assigned to %{name}") % { name: assignee.name})
+- issuable.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord
+ = link_to_member(@project, assignee, name: false, title: s_("MrList|Assigned to %{name}") % { name: assignee.name})
- if more_assignees_count > 0
%span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', qa_selector: 'avatar_counter_content' }, title: _("+%{more_assignees_count} more assignees") % { more_assignees_count: more_assignees_count} }
diff --git a/app/views/shared/issuable/_merge_request_assignees.html.haml b/app/views/shared/issuable/_merge_request_assignees.html.haml
deleted file mode 100644
index 6c7a2496ec6..00000000000
--- a/app/views/shared/issuable/_merge_request_assignees.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-- issuable.merge_request_assignees.take(count).each do |merge_request_assignee| # rubocop: disable CodeReuse/ActiveRecord
- - assignee = merge_request_assignee.assignee
- - assignee_tooltip = ( merge_request_assignee.attention_requested? ? s_("MrList|Attention requested from assignee %{name}") : s_("MrList|Assigned to %{name}") ) % { name: assignee.name}
-
- = link_to_member(@project, assignee, name: false, title: assignee_tooltip, extra_class: "gl-flex-direction-row-reverse") do
- - if merge_request_assignee.attention_requested?
- %span.gl-display-inline-flex
- = sprite_icon('attention-solid-sm', css_class: 'gl-text-orange-500 icon-overlap-and-shadow')
diff --git a/app/views/shared/issuable/_merge_request_reviewers.html.haml b/app/views/shared/issuable/_merge_request_reviewers.html.haml
deleted file mode 100644
index 8dd74e12aff..00000000000
--- a/app/views/shared/issuable/_merge_request_reviewers.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-- issuable.merge_request_reviewers.take(count).each do |merge_request_reviewer| # rubocop: disable CodeReuse/ActiveRecord
- - reviewer = merge_request_reviewer.reviewer
- - reviewer_tooltip = ( merge_request_reviewer.attention_requested? ? s_("MrList|Attention requested from reviewer %{name}") : s_("MrList|Review requested from %{name}") ) % { name: reviewer.name}
-
- = link_to_member(@project, reviewer, name: false, title: reviewer_tooltip, extra_class: "gl-flex-direction-row-reverse") do
- - if merge_request_reviewer.attention_requested?
- %span.gl-display-inline-flex
- = sprite_icon('attention-solid-sm', css_class: 'gl-text-orange-500 icon-overlap-and-shadow')
diff --git a/app/views/shared/issuable/_reviewers.html.haml b/app/views/shared/issuable/_reviewers.html.haml
index 3bf923eb946..4adb7096181 100644
--- a/app/views/shared/issuable/_reviewers.html.haml
+++ b/app/views/shared/issuable/_reviewers.html.haml
@@ -3,11 +3,8 @@
- render_count = reviewers_rendering_overflow ? max_render - 1 : max_render
- more_reviewers_count = issuable.reviewers.size - render_count
-- if issuable.instance_of?(MergeRequest) && current_user&.mr_attention_requests_enabled?
- = render 'shared/issuable/merge_request_reviewers', issuable: issuable, count: render_count
-- else
- - issuable.reviewers.take(render_count).each do |reviewer| # rubocop: disable CodeReuse/ActiveRecord
- = link_to_member(@project, reviewer, name: false, title: s_("MrList|Review requested from %{name}") % { name: reviewer.name})
+- issuable.reviewers.take(render_count).each do |reviewer| # rubocop: disable CodeReuse/ActiveRecord
+ = link_to_member(@project, reviewer, name: false, title: s_("MrList|Review requested from %{name}") % { name: reviewer.name})
- if more_reviewers_count > 0
%span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old' }, title: _("+%{more_reviewers_count} more reviewers") % { more_reviewers_count: more_reviewers_count} }
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 6394e05ae24..21716710015 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -88,16 +88,6 @@
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
- - if current_user&.mr_attention_requests_enabled?
- #js-dropdown-attention-requested.filtered-search-input-dropdown-menu.dropdown-menu
- - if current_user
- %ul{ data: { dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
= render_if_exists 'shared/issuable/approver_dropdown'
= render_if_exists 'shared/issuable/approved_by_dropdown'
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 966a1202db2..a1a5248c060 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2812,6 +2812,15 @@
:weight: 1
:idempotent: false
:tags: []
+- :name: projects_import_export_relation_export
+ :worker_name: Projects::ImportExport::RelationExportWorker
+ :feature_category: :importers
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :memory
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: projects_inactive_projects_deletion_notification
:worker_name: Projects::InactiveProjectsDeletionNotificationWorker
:feature_category: :compliance_management
diff --git a/app/workers/projects/import_export/relation_export_worker.rb b/app/workers/projects/import_export/relation_export_worker.rb
new file mode 100644
index 00000000000..13ca33c4457
--- /dev/null
+++ b/app/workers/projects/import_export/relation_export_worker.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Projects
+ module ImportExport
+ class RelationExportWorker
+ include ApplicationWorker
+ include ExceptionBacktrace
+
+ idempotent!
+ data_consistency :always
+ deduplicate :until_executed
+ feature_category :importers
+ sidekiq_options status_expiration: StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION
+ urgency :low
+ worker_resource_boundary :memory
+
+ def perform(project_relation_export_id)
+ relation_export = Projects::ImportExport::RelationExport.find(project_relation_export_id)
+
+ if relation_export.queued?
+ Projects::ImportExport::RelationExportService.new(relation_export, jid).execute
+ end
+ end
+ end
+ end
+end
diff --git a/config/feature_flags/development/batch_load_environment_last_deployment_group.yml b/config/feature_flags/development/batch_load_environment_last_deployment_group.yml
deleted file mode 100644
index 4d35b638fbc..00000000000
--- a/config/feature_flags/development/batch_load_environment_last_deployment_group.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: batch_load_environment_last_deployment_group
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86584/
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363023
-milestone: '15.1'
-type: development
-group: group::release
-default_enabled: true
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index c901ea09f4c..ef57c86d324 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -363,6 +363,8 @@
- 1
- - projects_git_garbage_collect
- 1
+- - projects_import_export_relation_export
+ - 1
- - projects_inactive_projects_deletion_notification
- 1
- - projects_post_creation
diff --git a/db/post_migrate/20220719081542_drop_queued_at_index_from_ci_builds.rb b/db/post_migrate/20220719081542_drop_queued_at_index_from_ci_builds.rb
new file mode 100644
index 00000000000..4a16d022c70
--- /dev/null
+++ b/db/post_migrate/20220719081542_drop_queued_at_index_from_ci_builds.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class DropQueuedAtIndexFromCiBuilds < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_ci_builds_on_queued_at'
+
+ def up
+ remove_concurrent_index_by_name :ci_builds, INDEX_NAME
+ end
+
+ # rubocop:disable Migration/PreventIndexCreation
+ def down
+ add_concurrent_index :ci_builds, :queued_at, name: INDEX_NAME
+ end
+ # rubocop:enable Migration/PreventIndexCreation
+end
diff --git a/db/post_migrate/20220719165537_remove_ci_runners_semver_column.rb b/db/post_migrate/20220719165537_remove_ci_runners_semver_column.rb
new file mode 100644
index 00000000000..a6929153d10
--- /dev/null
+++ b/db/post_migrate/20220719165537_remove_ci_runners_semver_column.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveCiRunnersSemverColumn < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_ci_runners_on_id_and_semver_cidr'
+
+ def up
+ with_lock_retries do
+ remove_column :ci_runners, :semver
+ end
+ end
+
+ def down
+ with_lock_retries do
+ add_column :ci_runners, :semver, :text, null: true
+ end
+ add_text_limit :ci_runners, :semver, 16
+ add_concurrent_index :ci_runners, 'id, (semver::cidr)', name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20220719081542 b/db/schema_migrations/20220719081542
new file mode 100644
index 00000000000..4bd2a3008ca
--- /dev/null
+++ b/db/schema_migrations/20220719081542
@@ -0,0 +1 @@
+2d5bf23684afbd4dbf3251c4886c22eaaa144332901c1183bc474772f065c54f \ No newline at end of file
diff --git a/db/schema_migrations/20220719165537 b/db/schema_migrations/20220719165537
new file mode 100644
index 00000000000..f60f26fb705
--- /dev/null
+++ b/db/schema_migrations/20220719165537
@@ -0,0 +1 @@
+c9b214fd49c97d17f43faef4d86b811ea2ad5f573c3cb4a6725de8ee4c92262a \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5eb6bef1af9..3226771f3df 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -13154,8 +13154,6 @@ CREATE TABLE ci_runners (
maintainer_note text,
token_expires_at timestamp with time zone,
allowed_plans text[] DEFAULT '{}'::text[] NOT NULL,
- semver text,
- CONSTRAINT check_a4f24953fd CHECK ((char_length(semver) <= 16)),
CONSTRAINT check_ce275cee06 CHECK ((char_length(maintainer_note) <= 1024))
);
@@ -27537,8 +27535,6 @@ CREATE INDEX index_ci_builds_on_project_id_and_id ON ci_builds USING btree (proj
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 index_ci_builds_on_queued_at ON ci_builds USING btree (queued_at);
-
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 index_ci_builds_on_runner_id_and_id_desc ON ci_builds USING btree (runner_id, id DESC);
@@ -27747,8 +27743,6 @@ CREATE INDEX index_ci_runners_on_created_at_desc_and_id_desc ON ci_runners USING
CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);
-CREATE INDEX index_ci_runners_on_id_and_semver_cidr ON ci_runners USING btree (id, ((semver)::cidr));
-
CREATE INDEX index_ci_runners_on_locked ON ci_runners USING btree (locked);
CREATE INDEX index_ci_runners_on_runner_type ON ci_runners USING btree (runner_type);
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index b23c33ddc0d..f399f581c40 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -700,7 +700,7 @@ Example of response
"stage": "test",
"status": "canceled",
"tag": false,
- "web_url": "https://example.com/foo/bar/-/jobs/42",
+ "web_url": "https://example.com/foo/bar/-/jobs/1",
"user": null
}
```
@@ -750,7 +750,7 @@ Example of response
"stage": "test",
"status": "pending",
"tag": false,
- "web_url": "https://example.com/foo/bar/-/jobs/42",
+ "web_url": "https://example.com/foo/bar/-/jobs/1",
"user": null
}
```
@@ -805,7 +805,7 @@ Example of response
"queued_duration": 0.010,
"status": "failed",
"tag": false,
- "web_url": "https://example.com/foo/bar/-/jobs/42",
+ "web_url": "https://example.com/foo/bar/-/jobs/1",
"user": null
}
```
@@ -881,7 +881,7 @@ Example response:
"stage": "test",
"status": "pending",
"tag": false,
- "web_url": "https://example.com/foo/bar/-/jobs/42",
+ "web_url": "https://example.com/foo/bar/-/jobs/1",
"user": null
}
```
diff --git a/doc/api/users.md b/doc/api/users.md
index 06c0fadbbbf..12dbf416d07 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -39,7 +39,7 @@ GET /users
]
```
-You can also search for users by name, username or public email by using `?search=`. For example. `/users?search=John`.
+You can also search for users by name, username, or public email by using `?search=`. For example. `/users?search=John`.
In addition, you can lookup users by username:
@@ -1220,7 +1220,7 @@ Parameters:
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/user/gpg_keys/1"
```
-Returns `204 No Content` on success, or `404 Not found` if the key cannot be found.
+Returns `204 No Content` on success or `404 Not Found` if the key cannot be found.
## List all GPG keys for given user
@@ -1964,7 +1964,7 @@ Pre-requisite:
- You must be an administrator.
Lists all projects and groups a user is a member of.
-It returns the `source_id`, `source_name`, `source_type` and `access_level` of a membership.
+It returns the `source_id`, `source_name`, `source_type`, and `access_level` of a membership.
Source can be of type `Namespace` (representing a group) or `Project`. The response represents only direct memberships. Inherited memberships, for example in subgroups, are not included.
Access levels are represented by an integer value. For more details, read about the meaning of [access level values](access_requests.md#valid-access-levels).
diff --git a/doc/development/database/ci_mirrored_tables.md b/doc/development/database/ci_mirrored_tables.md
new file mode 100644
index 00000000000..280fbb26cb5
--- /dev/null
+++ b/doc/development/database/ci_mirrored_tables.md
@@ -0,0 +1,156 @@
+---
+stage: Data Stores
+group: Database
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# CI mirrored tables
+
+## Problem statement
+
+As part of the database [decomposition work](https://gitlab.com/groups/gitlab-org/-/epics/6168),
+which had the goal of splitting the single database GitLab is using, into two databases: `main` and
+`ci`, came the big challenge of removing all joins between the tables
+[that don't reside on the same database](multiple_databases.md#removing-joins-between-ci-and-non-ci-tables).
+PostgreSQL doesn't support joins between tables that belong to different databases. However,
+some core application models in the main database are queried very often by the CI side.
+For example:
+
+- `Namespace`, in the `namespaces` table.
+- `Project`, in the `projects` table.
+
+Not being able to do `joins` on these tables brings a great challenge. The team chose to perform logical
+replication of those tables from the main database to the CI database, in the new tables:
+
+- `ci_namespace_mirrors`, as a mirror of the `namespaces` table
+- `ci_project_mirrors`, as a mirror of the `projects` table
+
+This logical replication means two things:
+
+1. The `main` database tables can be queried and joined to the `namespaces` and `projects` tables.
+1. The `ci` database tables can be joined with the `ci_namespace_mirrors` and `ci_project_mirrors` tables.
+
+```mermaid
+graph LR
+
+ subgraph "Main database (tables)"
+ A[namespaces] -->|updates| B[namespaces_sync_events]
+ A -->|deletes| C[loose_foreign_keys_deleted_records]
+ D[projects] -->|deletes| C
+ D -->|updates| E[projects_sync_events]
+ end
+
+ B --> F
+ C --> G
+ E --> H
+
+ subgraph "Sidekiq worker jobs"
+ F[Namespaces::ProcessSyncEventsWorker]
+ G[LooseForeignKeys::CleanupWorker]
+ H[Projects::ProcessSyncEventsWorker]
+ end
+
+ F -->|do update| I
+ G -->|delete records| I
+ G -->|delete records| J
+ H -->|do update| J
+
+ subgraph "CI database (tables)"
+ I[ci_namespace_mirrors]
+ J[ci_project_mirrors]
+ end
+```
+
+This replication was restricted only to a few attributes that are needed from each model:
+
+- From `Namespace` we replicate `traversal_ids`.
+- From `Project` we replicate only the `namespace_id`, which represents the group which the project belongs to.
+
+## Keeping the CI mirrored tables in sync with the source tables
+
+We must care about two type 3 events to keep
+the source and the target tables in sync:
+
+1. Creation of new namespaces or projects.
+1. Updating the namespaces or projects.
+1. Deleting namespaces/projects.
+
+```mermaid
+graph TD
+
+ subgraph "CI database (tables)"
+ E[other CI tables]
+ F{queries with joins allowed}
+ G[ci_project_mirrors]
+ H[ci_namespace_mirrors]
+
+ E---F
+ F---G
+ F---H
+ end
+
+ A---B
+ B---C
+ B---D
+
+L["⛔ ← Joins are not allowed → ⛔"]
+
+ subgraph "Main database (tables)"
+ A[other main tables]
+ B{queries with joins allowed}
+ C[projects]
+ D[namespaces]
+ end
+```
+
+### Create and update
+
+Syncing the data of newly created or updated namespaces or projects happens in this
+order:
+
+1. **On the `main` database**: Any `INSERT` or `UPDATE` on the `namespaces` or `projects` tables
+ adds an entry to the tables `namespaces_sync_events`, and `projects_sync_events`. These tables
+ also exist on the `main` database. These entries are added by triggers on both of the tables.
+1. **On the model level**: After a commit happens on either of the source models `Namespace` or
+ `Project`, it schedules the corresponding Sidekiq jobs `Namespaces::ProcessSyncEventsWorker`
+ or `Projects::ProcessSyncEventsWorker` to run.
+1. These workers then:
+ 1. Read the entries from the tables `(namespaces/project)_sync_events`
+ from the `main` database, to check which namespaces or projects to sync.
+ 1. Copy the data for any updated records into the target
+ tables `ci_namespace_mirrors`, `ci_project_mirrors`.
+
+### Delete
+
+When any of `namespaces` or `projects` are deleted, the target records on the mirrored
+CI tables are deleted using the [loose foreign keys](loose_foreign_keys.md) (LFK) mechanism.
+
+By having these items in the `config/gitlab_loose_foreign_keys.yml`, the LFK mechanism
+was already working as expected. It deleted any records on the CI mirrored
+tables that mapped to deleted `namespaces` or `projects` in the `main` database.
+
+```yaml
+ci_namespace_mirrors:
+ - table: namespaces
+ column: namespace_id
+ on_delete: async_delete
+ci_project_mirrors:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
+```
+
+## Consistency Checking
+
+To make sure that both syncing mechanisms work as expected, we deploy
+two extra worker jobs, triggered by cron jobs every few minutes:
+
+1. `Database::CiNamespaceMirrorsConsistencyCheckWorker`
+1. `Database::CiProjectMirrorsConsistencyCheckWorker`
+
+These jobs:
+
+1. Scan both of the source tables on the `main` database, using a cursor.
+1. Compare the items in the `namespaces` and `projects` with the target tables on the `ci` database.
+1. Report the items that are not in sync to Kibana and Prometheus.
+1. Corrects any discrepancies.
diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md
index d55cbe28d9b..5dd7235917a 100644
--- a/doc/development/documentation/testing.md
+++ b/doc/development/documentation/testing.md
@@ -250,7 +250,8 @@ You can use Vale:
Vale returns three types of results:
-- **Error** - For branding and trademark issues, and words or phrases with ambiguous meanings.
+- **Error** - For branding and trademark issues, words or phrases with ambiguous meanings, and anything that causes content on
+ the docs site to render incorrectly.
- **Warning** - For Technical Writing team style preferences.
- **Suggestion** - For basic technical writing tenets and best practices.
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index cf864068e44..92bb5af984a 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -7,7 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Container Scanning **(FREE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3672) in GitLab 10.4.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3672) in GitLab 10.4.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86783) to Free tier in GitLab 15.0.
Your application's Docker image may itself be based on Docker images that contain known
vulnerabilities. By including an extra Container Scanning job in your pipeline that scans for those
diff --git a/doc/user/project/repository/mirror/index.md b/doc/user/project/repository/mirror/index.md
index 4537f8520cd..e9e61372378 100644
--- a/doc/user/project/repository/mirror/index.md
+++ b/doc/user/project/repository/mirror/index.md
@@ -113,6 +113,11 @@ GitLab supports these authentication methods:
- [SSH authentication](#ssh-authentication).
- Password.
+When using password authentication, ensure you specify the username.
+For a [project access token](../../settings/project_access_tokens.md) or
+[group access token](../../../group/settings/group_access_tokens.md),
+use the username (not token name) and the token as the password.
+
### SSH authentication
SSH authentication is mutual:
diff --git a/lib/gitlab/background_migration/backfill_ci_runner_semver.rb b/lib/gitlab/background_migration/backfill_ci_runner_semver.rb
deleted file mode 100644
index 0901649f789..00000000000
--- a/lib/gitlab/background_migration/backfill_ci_runner_semver.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # A job to update semver column in ci_runners in batches based on existing version values
- class BackfillCiRunnerSemver < Gitlab::BackgroundMigration::BatchedMigrationJob
- def perform
- each_sub_batch(
- operation_name: :backfill_ci_runner_semver,
- batching_scope: ->(relation) { relation.where('semver::cidr IS NULL') }
- ) do |sub_batch|
- ranged_query = sub_batch.select(
- %q(id AS r_id,
- substring(ci_runners.version FROM 'v?(\d+\.\d+\.\d+)') AS extracted_semver)
- )
-
- update_sql = <<~SQL
- UPDATE
- ci_runners
- SET semver = extracted_semver
- FROM (#{ranged_query.to_sql}) v
- WHERE id = v.r_id
- AND v.extracted_semver IS NOT NULL
- SQL
-
- connection.execute(update_sql)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
index a9d9c400a34..822995fa8b6 100644
--- a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
@@ -11,7 +11,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
DS_EXCLUDED_ANALYZERS: ""
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
DS_MAJOR_VERSION: 3
diff --git a/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
index f7945b46a59..40c05ffe92a 100644
--- a/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
@@ -11,7 +11,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
LICENSE_MANAGEMENT_VERSION: 4
diff --git a/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml
index 2a11976a7b0..f09e4450703 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml
@@ -6,7 +6,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SAST_IMAGE_SUFFIX: ""
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
diff --git a/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
index 2a11976a7b0..f09e4450703 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
@@ -6,7 +6,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SAST_IMAGE_SUFFIX: ""
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
diff --git a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
index be41553450c..91cbccbe3a7 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
@@ -6,7 +6,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SAST_IMAGE_SUFFIX: ""
SAST_EXCLUDED_ANALYZERS: ""
diff --git a/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
index f8e6e152ab9..96fbbc67d8e 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
@@ -6,7 +6,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SAST_IMAGE_SUFFIX: ""
SAST_EXCLUDED_ANALYZERS: ""
diff --git a/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml
index 3f18237a525..25fc2388f9e 100644
--- a/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml
@@ -5,7 +5,8 @@
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
variables:
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SECRET_DETECTION_IMAGE_SUFFIX: ""
SECRETS_ANALYZER_VERSION: "4"
diff --git a/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml
index e81e06d1a1d..ebf464074f8 100644
--- a/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Secret-Detection.latest.gitlab-ci.yml
@@ -5,7 +5,8 @@
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
variables:
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SECRET_DETECTION_IMAGE_SUFFIX: ""
SECRETS_ANALYZER_VERSION: "4"
SECRET_DETECTION_EXCLUDED_PATHS: ""
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
index 2fd5b409f5e..38fb3394c95 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml
@@ -24,7 +24,8 @@
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
#
FUZZAPI_VERSION: "2"
FUZZAPI_IMAGE_SUFFIX: ""
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
index 450969fcdab..1cbce8aaddd 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
@@ -24,7 +24,8 @@
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
#
FUZZAPI_VERSION: "2"
FUZZAPI_IMAGE_SUFFIX: ""
diff --git a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
index 893098d33c4..ca9c64fac47 100644
--- a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml
@@ -24,7 +24,8 @@
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
#
DAST_API_VERSION: "2"
DAST_API_IMAGE_SUFFIX: ""
diff --git a/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml
index 3acc3b06031..e13d3e0f270 100644
--- a/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml
@@ -24,7 +24,8 @@
variables:
# Setting this variable affects all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
#
DAST_API_VERSION: "2"
DAST_API_IMAGE_SUFFIX: ""
diff --git a/lib/gitlab/ci/templates/Security/DAST-On-Demand-API-Scan.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-On-Demand-API-Scan.gitlab-ci.yml
index 4a72f5e72b1..8543ee90297 100644
--- a/lib/gitlab/ci/templates/Security/DAST-On-Demand-API-Scan.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-On-Demand-API-Scan.gitlab-ci.yml
@@ -10,7 +10,8 @@ stages:
- dast
variables:
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
DAST_API_VERSION: "2"
DAST_API_IMAGE_SUFFIX: ""
DAST_API_IMAGE: api-security
diff --git a/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
index c71a1b1873a..17961b49a30 100644
--- a/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST-On-Demand-Scan.gitlab-ci.yml
@@ -13,7 +13,8 @@ variables:
DAST_VERSION: 3
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
dast:
stage: dast
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 3bc44fe5e1b..7359c108f13 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -25,7 +25,8 @@ variables:
DAST_VERSION: 3
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
dast:
stage: dast
diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
index 10549b56856..cd2e5089b78 100644
--- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
@@ -25,7 +25,8 @@ variables:
DAST_VERSION: 3
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
dast:
stage: dast
diff --git a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
index c414e70bfa3..edc59d0194e 100644
--- a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
@@ -16,7 +16,8 @@
variables:
# Setting this variable will affect all Security templates
# (SAST, Dependency Scanning, ...)
- SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
+ TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
+ SECURE_ANALYZERS_PREFIX: "$TEMPLATE_REGISTRY_HOST/security-products"
SECURE_BINARIES_ANALYZERS: >-
bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, secrets, sobelow, pmd-apex, kics, kubesec, semgrep, gemnasium, gemnasium-maven, gemnasium-python,
license-finder,
diff --git a/lib/gitlab/github_import/importer/events/base_importer.rb b/lib/gitlab/github_import/importer/events/base_importer.rb
new file mode 100644
index 00000000000..d9e16a7c91e
--- /dev/null
+++ b/lib/gitlab/github_import/importer/events/base_importer.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Importer
+ module Events
+ # Base class for importing issue events during project import from GitHub
+ class BaseImporter
+ # project - An instance of `Project`.
+ # user_finder - An instance of `Gitlab::GithubImport::UserFinder`.
+ def initialize(project, user_finder)
+ @project = project
+ @user_finder = user_finder
+ end
+
+ # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
+ def execute(issue_event)
+ raise NotImplementedError
+ end
+
+ private
+
+ attr_reader :project, :user_finder
+
+ def author_id(issue_event, author_key: :actor)
+ user_finder.author_id_for(issue_event, author_key: author_key).first
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/events/changed_assignee.rb b/lib/gitlab/github_import/importer/events/changed_assignee.rb
new file mode 100644
index 00000000000..6930ea0ef8d
--- /dev/null
+++ b/lib/gitlab/github_import/importer/events/changed_assignee.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Importer
+ module Events
+ class ChangedAssignee < BaseImporter
+ def execute(issue_event)
+ assignee_id = author_id(issue_event, author_key: :assignee)
+ assigner_id = author_id(issue_event, author_key: :assigner)
+
+ note_body = parse_body(issue_event, assigner_id, assignee_id)
+
+ create_note(issue_event, note_body, assigner_id)
+ end
+
+ private
+
+ def create_note(issue_event, note_body, assigner_id)
+ Note.create!(
+ system: true,
+ noteable_type: Issue.name,
+ noteable_id: issue_event.issue_db_id,
+ project: project,
+ author_id: assigner_id,
+ note: note_body,
+ system_note_metadata: SystemNoteMetadata.new(
+ {
+ action: "assignee",
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }
+ ),
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ )
+ end
+
+ def parse_body(issue_event, assigner_id, assignee_id)
+ Gitlab::I18n.with_default_locale do
+ if issue_event.event == "unassigned"
+ "unassigned #{User.find(assigner_id).to_reference}"
+ else
+ "assigned to #{User.find(assignee_id).to_reference}"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/events/changed_label.rb b/lib/gitlab/github_import/importer/events/changed_label.rb
index 6c408158b02..c3350251b2d 100644
--- a/lib/gitlab/github_import/importer/events/changed_label.rb
+++ b/lib/gitlab/github_import/importer/events/changed_label.rb
@@ -4,25 +4,17 @@ module Gitlab
module GithubImport
module Importer
module Events
- class ChangedLabel
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
+ class ChangedLabel < BaseImporter
def execute(issue_event)
create_event(issue_event)
end
private
- attr_reader :project, :user_id
-
def create_event(issue_event)
ResourceLabelEvent.create!(
issue_id: issue_event.issue_db_id,
- user_id: user_id,
+ user_id: author_id(issue_event),
label_id: label_finder.id_for(issue_event.label_title),
action: action(issue_event.event),
created_at: issue_event.created_at
diff --git a/lib/gitlab/github_import/importer/events/changed_milestone.rb b/lib/gitlab/github_import/importer/events/changed_milestone.rb
index 6f3ab5753de..a3e4d2efb73 100644
--- a/lib/gitlab/github_import/importer/events/changed_milestone.rb
+++ b/lib/gitlab/github_import/importer/events/changed_milestone.rb
@@ -4,20 +4,12 @@ module Gitlab
module GithubImport
module Importer
module Events
- class ChangedMilestone
- attr_reader :project, :user_id
-
+ class ChangedMilestone < BaseImporter
# GitHub API doesn't provide the historical state of an issue for
# de/milestoned issue events. So we'll assign the default state to
# those events that are imported from GitHub.
DEFAULT_STATE = Issue.available_states[:opened]
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
def execute(issue_event)
create_event(issue_event)
end
@@ -27,7 +19,7 @@ module Gitlab
def create_event(issue_event)
ResourceMilestoneEvent.create!(
issue_id: issue_event.issue_db_id,
- user_id: user_id,
+ user_id: author_id(issue_event),
created_at: issue_event.created_at,
milestone_id: project.milestones.find_by_title(issue_event.milestone_title)&.id,
action: action(issue_event.event),
diff --git a/lib/gitlab/github_import/importer/events/closed.rb b/lib/gitlab/github_import/importer/events/closed.rb
index 8b2136c9b24..1db326328f6 100644
--- a/lib/gitlab/github_import/importer/events/closed.rb
+++ b/lib/gitlab/github_import/importer/events/closed.rb
@@ -4,15 +4,7 @@ module Gitlab
module GithubImport
module Importer
module Events
- class Closed
- attr_reader :project, :user_id
-
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
+ class Closed < BaseImporter
def execute(issue_event)
create_event(issue_event)
create_state_event(issue_event)
@@ -23,7 +15,7 @@ module Gitlab
def create_event(issue_event)
Event.create!(
project_id: project.id,
- author_id: user_id,
+ author_id: author_id(issue_event),
action: 'closed',
target_type: Issue.name,
target_id: issue_event.issue_db_id,
@@ -34,7 +26,7 @@ module Gitlab
def create_state_event(issue_event)
ResourceStateEvent.create!(
- user_id: user_id,
+ user_id: author_id(issue_event),
issue_id: issue_event.issue_db_id,
source_commit: issue_event.commit_id,
state: 'closed',
diff --git a/lib/gitlab/github_import/importer/events/cross_referenced.rb b/lib/gitlab/github_import/importer/events/cross_referenced.rb
index 20b902cfe50..9a3a2536b7c 100644
--- a/lib/gitlab/github_import/importer/events/cross_referenced.rb
+++ b/lib/gitlab/github_import/importer/events/cross_referenced.rb
@@ -4,15 +4,7 @@ module Gitlab
module GithubImport
module Importer
module Events
- class CrossReferenced
- attr_reader :project, :user_id
-
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
+ class CrossReferenced < BaseImporter
def execute(issue_event)
mentioned_in_record_class = mentioned_in_type(issue_event)
mentioned_in_number = issue_event.source.dig(:issue, :number)
@@ -21,14 +13,15 @@ module Gitlab
)
return if mentioned_in_record.nil?
+ user_id = author_id(issue_event)
note_body = cross_reference_note_content(mentioned_in_record.gfm_reference(project))
- track_activity(mentioned_in_record_class)
- create_note(issue_event, note_body)
+ track_activity(mentioned_in_record_class, user_id)
+ create_note(issue_event, note_body, user_id)
end
private
- def track_activity(mentioned_in_class)
+ def track_activity(mentioned_in_class, user_id)
return if mentioned_in_class != Issue
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(
@@ -37,7 +30,7 @@ module Gitlab
)
end
- def create_note(issue_event, note_body)
+ def create_note(issue_event, note_body, user_id)
Note.create!(
system: true,
noteable_type: Issue.name,
diff --git a/lib/gitlab/github_import/importer/events/renamed.rb b/lib/gitlab/github_import/importer/events/renamed.rb
index 6a11c492210..44f05eb4af8 100644
--- a/lib/gitlab/github_import/importer/events/renamed.rb
+++ b/lib/gitlab/github_import/importer/events/renamed.rb
@@ -4,27 +4,19 @@ module Gitlab
module GithubImport
module Importer
module Events
- class Renamed
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`
+ class Renamed < BaseImporter
def execute(issue_event)
Note.create!(note_params(issue_event))
end
private
- attr_reader :project, :user_id
-
def note_params(issue_event)
{
noteable_id: issue_event.issue_db_id,
noteable_type: Issue.name,
project_id: project.id,
- author_id: user_id,
+ author_id: author_id(issue_event),
note: parse_body(issue_event),
system: true,
created_at: issue_event.created_at,
diff --git a/lib/gitlab/github_import/importer/events/reopened.rb b/lib/gitlab/github_import/importer/events/reopened.rb
index c0f3802bc46..923fee51394 100644
--- a/lib/gitlab/github_import/importer/events/reopened.rb
+++ b/lib/gitlab/github_import/importer/events/reopened.rb
@@ -4,15 +4,7 @@ module Gitlab
module GithubImport
module Importer
module Events
- class Reopened
- attr_reader :project, :user_id
-
- def initialize(project, user_id)
- @project = project
- @user_id = user_id
- end
-
- # issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
+ class Reopened < BaseImporter
def execute(issue_event)
create_event(issue_event)
create_state_event(issue_event)
@@ -23,7 +15,7 @@ module Gitlab
def create_event(issue_event)
Event.create!(
project_id: project.id,
- author_id: user_id,
+ author_id: author_id(issue_event),
action: 'reopened',
target_type: Issue.name,
target_id: issue_event.issue_db_id,
@@ -34,7 +26,7 @@ module Gitlab
def create_state_event(issue_event)
ResourceStateEvent.create!(
- user_id: user_id,
+ user_id: author_id(issue_event),
issue_id: issue_event.issue_db_id,
state: 'reopened',
created_at: issue_event.created_at
diff --git a/lib/gitlab/github_import/importer/issue_event_importer.rb b/lib/gitlab/github_import/importer/issue_event_importer.rb
index 1745e53c871..fb52c18853c 100644
--- a/lib/gitlab/github_import/importer/issue_event_importer.rb
+++ b/lib/gitlab/github_import/importer/issue_event_importer.rb
@@ -17,25 +17,25 @@ module Gitlab
end
def execute
- case issue_event.event
- when 'closed'
- Gitlab::GithubImport::Importer::Events::Closed.new(project, author_id)
- .execute(issue_event)
- when 'reopened'
- Gitlab::GithubImport::Importer::Events::Reopened.new(project, author_id)
- .execute(issue_event)
- when 'labeled', 'unlabeled'
- Gitlab::GithubImport::Importer::Events::ChangedLabel.new(project, author_id)
- .execute(issue_event)
- when 'renamed'
- Gitlab::GithubImport::Importer::Events::Renamed.new(project, author_id)
- .execute(issue_event)
- when 'milestoned', 'demilestoned'
- Gitlab::GithubImport::Importer::Events::ChangedMilestone.new(project, author_id)
- .execute(issue_event)
- when 'cross-referenced'
- Gitlab::GithubImport::Importer::Events::CrossReferenced.new(project, author_id)
- .execute(issue_event)
+ event_importer = case issue_event.event
+ when 'closed'
+ Gitlab::GithubImport::Importer::Events::Closed
+ when 'reopened'
+ Gitlab::GithubImport::Importer::Events::Reopened
+ when 'labeled', 'unlabeled'
+ Gitlab::GithubImport::Importer::Events::ChangedLabel
+ when 'renamed'
+ Gitlab::GithubImport::Importer::Events::Renamed
+ when 'milestoned', 'demilestoned'
+ Gitlab::GithubImport::Importer::Events::ChangedMilestone
+ when 'cross-referenced'
+ Gitlab::GithubImport::Importer::Events::CrossReferenced
+ when 'assigned', 'unassigned'
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+
+ if event_importer
+ event_importer.new(project, user_finder).execute(issue_event)
else
Gitlab::GithubImport::Logger.debug(
message: 'UNSUPPORTED_EVENT_TYPE',
@@ -43,13 +43,6 @@ module Gitlab
)
end
end
-
- private
-
- def author_id
- id, _status = user_finder.author_id_for(issue_event, author_key: :actor)
- id
- end
end
end
end
diff --git a/lib/gitlab/github_import/representation/issue_event.rb b/lib/gitlab/github_import/representation/issue_event.rb
index 36646121c9d..3561628bdde 100644
--- a/lib/gitlab/github_import/representation/issue_event.rb
+++ b/lib/gitlab/github_import/representation/issue_event.rb
@@ -10,36 +10,9 @@ module Gitlab
attr_reader :attributes
expose_attribute :id, :actor, :event, :commit_id, :label_title, :old_title, :new_title,
- :milestone_title, :source, :created_at
+ :milestone_title, :source, :assignee, :assigner, :created_at
expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
- # Builds a event from a GitHub API response.
- #
- # event - An instance of `Sawyer::Resource` containing the event details.
- def self.from_api_response(event)
- new(
- id: event.id,
- actor: event.actor && Representation::User.from_api_response(event.actor),
- event: event.event,
- commit_id: event.commit_id,
- label_title: event.label && event.label[:name],
- old_title: event.rename && event.rename[:from],
- new_title: event.rename && event.rename[:to],
- source: event.source,
- issue_db_id: event.issue_db_id,
- milestone_title: event.milestone && event.milestone[:title],
- created_at: event.created_at
- )
- end
-
- # Builds a event using a Hash that was built from a JSON payload.
- def self.from_json_hash(raw_hash)
- hash = Representation.symbolize_hash(raw_hash)
- hash[:actor] &&= Representation::User.from_json_hash(hash[:actor])
-
- new(hash)
- end
-
# attributes - A Hash containing the event details. The keys of this
# Hash (and any nested hashes) must be symbols.
def initialize(attributes)
@@ -49,6 +22,52 @@ module Gitlab
def github_identifiers
{ id: id }
end
+
+ class << self
+ # Builds an event from a GitHub API response.
+ #
+ # event - An instance of `Sawyer::Resource` containing the event details.
+ def from_api_response(event)
+ new(
+ id: event.id,
+ actor: user_representation(event.actor),
+ event: event.event,
+ commit_id: event.commit_id,
+ label_title: event.label && event.label[:name],
+ old_title: event.rename && event.rename[:from],
+ new_title: event.rename && event.rename[:to],
+ milestone_title: event.milestone && event.milestone[:title],
+ source: event.source,
+ assignee: user_representation(event.assignee),
+ assigner: user_representation(event.assigner),
+ issue_db_id: event.issue_db_id,
+ created_at: event.created_at
+ )
+ end
+
+ # Builds an event using a Hash that was built from a JSON payload.
+ def from_json_hash(raw_hash)
+ hash = Representation.symbolize_hash(raw_hash)
+ hash[:actor] = user_representation(hash[:actor], source: :hash)
+ hash[:assignee] = user_representation(hash[:assignee], source: :hash)
+ hash[:assigner] = user_representation(hash[:assigner], source: :hash)
+
+ new(hash)
+ end
+
+ private
+
+ def user_representation(data, source: :api_response)
+ return unless data
+
+ case source
+ when :api_response
+ Representation::User.from_api_response(data)
+ when :hash
+ Representation::User.from_json_hash(data)
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb
index efaa2ce3002..a288fc1851e 100644
--- a/lib/gitlab/github_import/user_finder.rb
+++ b/lib/gitlab/github_import/user_finder.rb
@@ -40,7 +40,17 @@ module Gitlab
# If the object has no author ID we'll use the ID of the GitLab ghost
# user.
def author_id_for(object, author_key: :author)
- user_info = author_key == :actor ? object&.actor : object&.author
+ user_info = case author_key
+ when :actor
+ object&.actor
+ when :assignee
+ object&.assignee
+ when :assigner
+ object&.assigner
+ else
+ object&.author
+ end
+
id = user_info ? user_id_for(user_info) : GithubImport.ghost_user_id
if id
diff --git a/lib/gitlab/import_export/project/relation_saver.rb b/lib/gitlab/import_export/project/relation_saver.rb
new file mode 100644
index 00000000000..b40827e36f8
--- /dev/null
+++ b/lib/gitlab/import_export/project/relation_saver.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ module Project
+ class RelationSaver
+ def initialize(project:, shared:, relation:)
+ @project = project
+ @relation = relation
+ @shared = shared
+ end
+
+ def save
+ if root_relation?
+ serializer.serialize_root
+ else
+ serializer.serialize_relation(relation_schema)
+ end
+
+ true
+ rescue StandardError => e
+ shared.error(e)
+ false
+ end
+
+ private
+
+ attr_reader :project, :relation, :shared
+
+ def serializer
+ @serializer ||= ::Gitlab::ImportExport::Json::StreamingSerializer.new(
+ project,
+ reader.project_tree,
+ json_writer,
+ exportable_path: 'project'
+ )
+ end
+
+ def root_relation?
+ relation == Projects::ImportExport::RelationExport::ROOT_RELATION
+ end
+
+ def relation_schema
+ reader.project_tree[:include].find { |include| include[relation.to_sym] }
+ end
+
+ def reader
+ @reader ||= ::Gitlab::ImportExport::Reader.new(shared: shared)
+ end
+
+ def json_writer
+ @json_writer ||= ::Gitlab::ImportExport::Json::NdjsonWriter.new(shared.export_path)
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2a9f0ebae0e..199561e1fd6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -998,9 +998,6 @@ msgstr[1] ""
msgid "%{strongOpen}Warning:%{strongClose} SAML group links can cause GitLab to automatically remove members from groups."
msgstr ""
-msgid "%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer."
-msgstr ""
-
msgid "%{strongStart}Tip:%{strongEnd} You can also check out merge requests locally. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
@@ -2189,9 +2186,6 @@ msgstr ""
msgid "Add approvers"
msgstr ""
-msgid "Add attention request"
-msgstr ""
-
msgid "Add broadcast message"
msgstr ""
@@ -4857,9 +4851,6 @@ msgstr ""
msgid "Approved-By"
msgstr ""
-msgid "Approved. Your attention request was removed."
-msgstr ""
-
msgid "Approver"
msgstr ""
@@ -5201,9 +5192,6 @@ msgstr ""
msgid "Assigned to you"
msgstr ""
-msgid "Assigned user(s). Your attention request was removed."
-msgstr ""
-
msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
@@ -5259,12 +5247,6 @@ msgstr[1] ""
msgid "Attaching the file failed."
msgstr ""
-msgid "Attention"
-msgstr ""
-
-msgid "Attention requested"
-msgstr ""
-
msgid "Audit Events"
msgstr ""
@@ -25492,12 +25474,6 @@ msgstr ""
msgid "MrList|Assigned to %{name}"
msgstr ""
-msgid "MrList|Attention requested from assignee %{name}"
-msgstr ""
-
-msgid "MrList|Attention requested from reviewer %{name}"
-msgstr ""
-
msgid "MrList|Review requested from %{name}"
msgstr ""
@@ -25707,9 +25683,6 @@ msgstr ""
msgid "Need help?"
msgstr ""
-msgid "Need your attention"
-msgstr ""
-
msgid "Needs"
msgstr ""
@@ -26012,9 +25985,6 @@ msgstr ""
msgid "No assignee"
msgstr ""
-msgid "No attention request"
-msgstr ""
-
msgid "No authentication methods configured."
msgstr ""
@@ -32438,9 +32408,6 @@ msgstr ""
msgid "Removed attention from %{users_sentence}."
msgstr ""
-msgid "Removed attention request from @%{username}"
-msgstr ""
-
msgid "Removed group can not be restored!"
msgstr ""
@@ -32998,21 +32965,12 @@ msgstr ""
msgid "Requested attention from %{users_sentence}."
msgstr ""
-msgid "Requested attention from @%{username}"
-msgstr ""
-
-msgid "Requested attention from @%{username}. Your own attention request was removed."
-msgstr ""
-
msgid "Requested design version does not exist."
msgstr ""
msgid "Requested review"
msgstr ""
-msgid "Requested review. Your attention request was removed."
-msgstr ""
-
msgid "Requested states are invalid"
msgstr ""
@@ -36480,9 +36438,6 @@ msgstr ""
msgid "Solution"
msgstr ""
-msgid "Some actions remove attention requests, like a reviewer approving or anyone merging the merge request."
-msgstr ""
-
msgid "Some changes are not shown"
msgstr ""
@@ -40461,9 +40416,6 @@ msgstr ""
msgid "To add the entry manually, provide the following details to the application on your phone."
msgstr ""
-msgid "To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request."
-msgstr ""
-
msgid "To complete registration, we need additional details from you."
msgstr ""
@@ -41586,9 +41538,6 @@ msgstr ""
msgid "Updating"
msgstr ""
-msgid "Updating the attention request for %{username} failed."
-msgstr ""
-
msgid "Updating…"
msgstr ""
diff --git a/spec/commands/sidekiq_cluster/cli_spec.rb b/spec/commands/sidekiq_cluster/cli_spec.rb
index 55e8ab7885e..4d1a07a6a75 100644
--- a/spec/commands/sidekiq_cluster/cli_spec.rb
+++ b/spec/commands/sidekiq_cluster/cli_spec.rb
@@ -245,9 +245,9 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubo
it 'expands multiple queue groups correctly' do
expected_workers =
if Gitlab.ee?
- [%w[chat_notification], %w[project_export project_template_export]]
+ [%w[chat_notification], %w[project_export projects_import_export_relation_export project_template_export]]
else
- [%w[chat_notification], %w[project_export]]
+ [%w[chat_notification], %w[project_export projects_import_export_relation_export]]
end
expect(Gitlab::SidekiqCluster)
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index fa866beb773..cf9760bcd7f 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -9,8 +9,6 @@ RSpec.describe 'Merge requests > User mass updates', :js do
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
before do
- stub_feature_flags(mr_attention_requests: false)
-
project.add_maintainer(user)
project.add_maintainer(user2)
sign_in(user)
@@ -63,18 +61,6 @@ RSpec.describe 'Merge requests > User mass updates', :js do
expect(find('.merge-request')).to have_link "Assigned to #{user.name}"
end
-
- describe 'with attention requests feature flag on' do
- before do
- stub_feature_flags(mr_attention_requests: true)
- end
-
- it 'updates merge request with assignee' do
- change_assignee(user2.name)
-
- expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user2.name}"
- end
- end
end
describe 'remove assignee' do
diff --git a/spec/frontend/attention_requests/components/navigation_popover_spec.js b/spec/frontend/attention_requests/components/navigation_popover_spec.js
deleted file mode 100644
index e4d53d5dbdb..00000000000
--- a/spec/frontend/attention_requests/components/navigation_popover_spec.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlPopover, GlButton, GlSprintf, GlIcon } from '@gitlab/ui';
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import NavigationPopover from '~/attention_requests/components/navigation_popover.vue';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
-
-let wrapper;
-let dismiss;
-
-function createComponent(provideData = {}, shouldShowCallout = true) {
- wrapper = shallowMount(NavigationPopover, {
- provide: {
- message: ['Test'],
- observerElSelector: '.js-test',
- observerElToggledClass: 'show',
- featureName: 'attention_requests',
- popoverTarget: '.js-test-popover',
- ...provideData,
- },
- stubs: {
- UserCalloutDismisser: makeMockUserCalloutDismisser({
- dismiss,
- shouldShowCallout,
- }),
- GlSprintf,
- },
- });
-}
-
-describe('Attention requests navigation popover', () => {
- beforeEach(() => {
- setHTMLFixture('<div><div class="js-test-popover"></div><div class="js-test"></div></div>');
- dismiss = jest.fn();
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- resetHTMLFixture();
- });
-
- it('hides popover if callout is disabled', () => {
- createComponent({}, false);
-
- expect(wrapper.findComponent(GlPopover).exists()).toBe(false);
- });
-
- it('shows popover if callout is enabled', () => {
- createComponent();
-
- expect(wrapper.findComponent(GlPopover).exists()).toBe(true);
- });
-
- it.each`
- isDesktop | device | expectedPlacement
- ${true} | ${'desktop'} | ${'left'}
- ${false} | ${'mobile'} | ${'bottom'}
- `(
- 'sets popover position to $expectedPlacement on $device',
- ({ isDesktop, expectedPlacement }) => {
- jest.spyOn(bp, 'isDesktop').mockReturnValue(isDesktop);
-
- createComponent();
-
- expect(wrapper.findComponent(GlPopover).props('placement')).toBe(expectedPlacement);
- },
- );
-
- it('calls dismiss when clicking action button', () => {
- createComponent();
-
- wrapper
- .findComponent(GlButton)
- .vm.$emit('click', { preventDefault() {}, stopPropagation() {} });
-
- expect(dismiss).toHaveBeenCalled();
- });
-
- it('shows icon in text', () => {
- createComponent({ showAttentionIcon: true, message: ['%{strongStart}Test%{strongEnd}'] });
-
- const icon = wrapper.findComponent(GlIcon);
-
- expect(icon.exists()).toBe(true);
- expect(icon.props('name')).toBe('attention');
- });
-});
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
index 87a7f07f7d4..d990d5ad22b 100644
--- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -1,5 +1,5 @@
import { GlAlert, GlBadge, GlLoadingIcon, GlTabs } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -30,8 +30,7 @@ import {
mockLintResponseWithoutMerged,
} from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
Vue.config.ignoredElements = ['gl-emoji'];
@@ -88,7 +87,6 @@ describe('Pipeline editor tabs component', () => {
provide,
mountFn,
options: {
- localVue,
apolloProvider: mockApollo,
},
});
diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js
index ed1a698d36f..19344a68f79 100644
--- a/spec/frontend/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_spec.js
@@ -1,5 +1,5 @@
import { GlToast } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RegistrationToken from '~/runner/components/registration/registration_token.vue';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
@@ -11,28 +11,17 @@ describe('RegistrationToken', () => {
let wrapper;
let showToast;
- const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
-
- const vueWithGlToast = () => {
- const localVue = createLocalVue();
- localVue.use(GlToast);
- return localVue;
- };
+ Vue.use(GlToast);
- const createComponent = ({
- props = {},
- withGlToast = true,
- mountFn = shallowMountExtended,
- } = {}) => {
- const localVue = withGlToast ? vueWithGlToast() : undefined;
+ const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RegistrationToken, {
propsData: {
value: mockToken,
inputId: 'token-value',
...props,
},
- localVue,
});
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
@@ -69,13 +58,5 @@ describe('RegistrationToken', () => {
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Registration token copied!');
});
-
- it('does not fail when toast is not defined', () => {
- createComponent({ withGlToast: false });
- findInputCopyToggleVisibility().vm.$emit('copy');
-
- // This block also tests for unhandled errors
- expect(showToast).toBeNull();
- });
});
});
diff --git a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js b/spec/frontend/sidebar/components/attention_requested_toggle_spec.js
deleted file mode 100644
index 58fa878a189..00000000000
--- a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import { GlButton } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import AttentionRequestedToggle from '~/sidebar/components/attention_requested_toggle.vue';
-
-let wrapper;
-
-function factory(propsData = {}) {
- wrapper = mount(AttentionRequestedToggle, { propsData });
-}
-
-const findToggle = () => wrapper.findComponent(GlButton);
-
-describe('Attention require toggle', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders button', () => {
- factory({
- type: 'reviewer',
- user: { attention_requested: false, can_update_merge_request: true },
- });
-
- expect(findToggle().exists()).toBe(true);
- });
-
- it.each`
- attentionRequested | icon
- ${true} | ${'attention-solid'}
- ${false} | ${'attention'}
- `(
- 'renders $icon icon when attention_requested is $attentionRequested',
- ({ attentionRequested, icon }) => {
- factory({
- type: 'reviewer',
- user: { attention_requested: attentionRequested, can_update_merge_request: true },
- });
-
- expect(findToggle().props('icon')).toBe(icon);
- },
- );
-
- it.each`
- attentionRequested | selected
- ${true} | ${true}
- ${false} | ${false}
- `(
- 'renders button with as selected when $selected when attention_requested is $attentionRequested',
- ({ attentionRequested, selected }) => {
- factory({
- type: 'reviewer',
- user: { attention_requested: attentionRequested, can_update_merge_request: true },
- });
-
- expect(findToggle().props('selected')).toBe(selected);
- },
- );
-
- it('emits toggle-attention-requested on click', async () => {
- factory({
- type: 'reviewer',
- user: { attention_requested: true, can_update_merge_request: true },
- });
-
- await findToggle().trigger('click');
-
- expect(wrapper.emitted('toggle-attention-requested')[0]).toEqual([
- {
- user: { attention_requested: true, can_update_merge_request: true },
- callback: expect.anything(),
- direction: 'remove',
- },
- ]);
- });
-
- it('does not emit toggle-attention-requested on click if can_update_merge_request is false', async () => {
- factory({
- type: 'reviewer',
- user: { attention_requested: true, can_update_merge_request: false },
- });
-
- await findToggle().trigger('click');
-
- expect(wrapper.emitted('toggle-attention-requested')).toBe(undefined);
- });
-
- it('sets loading on click', async () => {
- factory({
- type: 'reviewer',
- user: { attention_requested: true, can_update_merge_request: true },
- });
-
- await findToggle().trigger('click');
-
- expect(findToggle().props('loading')).toBe(true);
- });
-
- it.each`
- type | attentionRequested | tooltip | canUpdateMergeRequest
- ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.removeAttentionRequest} | ${true}
- ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.addAttentionRequest} | ${true}
- ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.addAttentionRequest} | ${true}
- ${'reviewer'} | ${true} | ${AttentionRequestedToggle.i18n.attentionRequestedNoPermission} | ${false}
- ${'reviewer'} | ${false} | ${AttentionRequestedToggle.i18n.noAttentionRequestedNoPermission} | ${false}
- ${'assignee'} | ${true} | ${AttentionRequestedToggle.i18n.attentionRequestedNoPermission} | ${false}
- ${'assignee'} | ${false} | ${AttentionRequestedToggle.i18n.noAttentionRequestedNoPermission} | ${false}
- `(
- 'sets tooltip as $tooltip when attention_requested is $attentionRequested, type is $type and, can_update_merge_request is $canUpdateMergeRequest',
- ({ type, attentionRequested, tooltip, canUpdateMergeRequest }) => {
- factory({
- type,
- user: {
- attention_requested: attentionRequested,
- can_update_merge_request: canUpdateMergeRequest,
- },
- });
-
- expect(findToggle().attributes('aria-label')).toBe(tooltip);
- },
- );
-});
diff --git a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
index 8999f120a0f..8ac85d4da81 100644
--- a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
@@ -1,6 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
-import AttentionRequestedToggle from '~/sidebar/components/attention_requested_toggle.vue';
import ReviewerAvatarLink from '~/sidebar/components/reviewers/reviewer_avatar_link.vue';
import UncollapsedReviewerList from '~/sidebar/components/reviewers/uncollapsed_reviewer_list.vue';
import userDataMock from '../../user_data_mock';
@@ -119,18 +118,4 @@ describe('UncollapsedReviewerList component', () => {
expect(wrapper.find('[data-testid="re-request-success"]').exists()).toBe(true);
});
});
-
- it('hides re-request review button when attentionRequired feature flag is enabled', () => {
- createComponent({ users: [userDataMock()] }, { mrAttentionRequests: true });
-
- expect(wrapper.findAll('[data-testid="re-request-button"]').length).toBe(0);
- });
-
- it('emits toggle-attention-requested', () => {
- createComponent({ users: [userDataMock()] }, { mrAttentionRequests: true });
-
- wrapper.find(AttentionRequestedToggle).vm.$emit('toggle-attention-requested', 'data');
-
- expect(wrapper.emitted('toggle-attention-requested')[0]).toEqual(['data']);
- });
});
diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js
index 82fb10ab1d2..e32694abcce 100644
--- a/spec/frontend/sidebar/sidebar_mediator_spec.js
+++ b/spec/frontend/sidebar/sidebar_mediator_spec.js
@@ -1,12 +1,9 @@
import MockAdapter from 'axios-mock-adapter';
-import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import * as urlUtility from '~/lib/utils/url_utility';
import SidebarService, { gqClient } from '~/sidebar/services/sidebar_service';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
-import toast from '~/vue_shared/plugins/global_toast';
-import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Mock from './mock_data';
jest.mock('~/flash');
@@ -122,93 +119,4 @@ describe('Sidebar mediator', () => {
urlSpy.mockRestore();
});
});
-
- describe('toggleAttentionRequested', () => {
- let requestAttentionMock;
- let removeAttentionRequestMock;
-
- beforeEach(() => {
- requestAttentionMock = jest.spyOn(mediator.service, 'requestAttention').mockResolvedValue();
- removeAttentionRequestMock = jest
- .spyOn(mediator.service, 'removeAttentionRequest')
- .mockResolvedValue();
- });
-
- it.each`
- attentionIsCurrentlyRequested | serviceMethod
- ${true} | ${'remove'}
- ${false} | ${'add'}
- `(
- "calls the $serviceMethod service method when the user's attention request is set to $attentionIsCurrentlyRequested",
- async ({ serviceMethod }) => {
- const methods = {
- add: requestAttentionMock,
- remove: removeAttentionRequestMock,
- };
- mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }];
-
- await mediator.toggleAttentionRequested('reviewer', {
- user: { id: 1, username: 'root' },
- callback: jest.fn(),
- direction: serviceMethod,
- });
-
- expect(methods[serviceMethod]).toHaveBeenCalledWith(1);
- expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
- },
- );
-
- it.each`
- type | method
- ${'reviewer'} | ${'findReviewer'}
- `('finds $type', ({ type, method }) => {
- const methodSpy = jest.spyOn(mediator.store, method);
-
- mediator.toggleAttentionRequested(type, { user: { id: 1 }, callback: jest.fn() });
-
- expect(methodSpy).toHaveBeenCalledWith({ id: 1 });
- });
-
- it.each`
- attentionRequested | toastMessage
- ${true} | ${'Removed attention request from @root'}
- ${false} | ${'Requested attention from @root'}
- `(
- 'it creates toast $toastMessage when attention_requested is $attentionRequested',
- async ({ attentionRequested, toastMessage }) => {
- mediator.store.reviewers = [
- { id: 1, attention_requested: attentionRequested, username: 'root' },
- ];
-
- await mediator.toggleAttentionRequested('reviewer', {
- user: { id: 1, username: 'root' },
- callback: jest.fn(),
- });
-
- expect(toast).toHaveBeenCalledWith(toastMessage);
- },
- );
-
- describe('errors', () => {
- beforeEach(() => {
- jest
- .spyOn(mediator.service, 'removeAttentionRequest')
- .mockRejectedValueOnce(new Error('Something went wrong'));
- });
-
- it('shows an error message', async () => {
- await mediator.toggleAttentionRequested('reviewer', {
- user: { id: 1, username: 'root' },
- callback: jest.fn(),
- direction: 'remove',
- });
-
- expect(createFlash).toHaveBeenCalledWith(
- expect.objectContaining({
- message: 'Updating the attention request for root failed.',
- }),
- );
- });
- });
- });
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 46d90ddc83c..188582d2e05 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,5 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import produce from 'immer';
@@ -71,8 +71,8 @@ const createTestService = () => ({
merge: jest.fn(),
poll: jest.fn().mockResolvedValue(),
});
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+
+Vue.use(VueApollo);
let wrapper;
let readyToMergeResponseSpy;
@@ -93,7 +93,6 @@ const createComponent = (
restructuredMrWidget = false,
) => {
wrapper = shallowMount(ReadyToMerge, {
- localVue,
propsData: {
mr: createTestMr(customConfig),
service: createTestService(),
diff --git a/spec/frontend/work_items_hierarchy/components/app_spec.js b/spec/frontend/work_items_hierarchy/components/app_spec.js
index 092e9c90553..1426fbfab80 100644
--- a/spec/frontend/work_items_hierarchy/components/app_spec.js
+++ b/spec/frontend/work_items_hierarchy/components/app_spec.js
@@ -1,19 +1,17 @@
-import { nextTick } from 'vue';
-import { createLocalVue, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { GlBanner } from '@gitlab/ui';
import App from '~/work_items_hierarchy/components/app.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('WorkItemsHierarchy App', () => {
let wrapper;
const createComponent = (props = {}, data = {}) => {
wrapper = extendedWrapper(
mount(App, {
- localVue,
provide: {
illustrationPath: '/foo.svg',
licensePlan: 'free',
diff --git a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
index 74774e38d6b..67420e7fc2a 100644
--- a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
+++ b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlBadge } from '@gitlab/ui';
import Hierarchy from '~/work_items_hierarchy/components/hierarchy.vue';
@@ -6,8 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import RESPONSE from '~/work_items_hierarchy/static_response';
import { workItemTypes } from '~/work_items_hierarchy/constants';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('WorkItemsHierarchy Hierarchy', () => {
let wrapper;
@@ -32,7 +32,6 @@ describe('WorkItemsHierarchy Hierarchy', () => {
const createComponent = (props = {}) => {
wrapper = extendedWrapper(
mount(Hierarchy, {
- localVue,
propsData: {
workItemTypes: props.workItemTypes,
...props,
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index ed93d31da0f..6b769230d92 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -195,8 +195,8 @@ RSpec.describe GitlabSchema.types['Project'] do
expect(secure_analyzers['type']).to eq('string')
expect(secure_analyzers['field']).to eq('SECURE_ANALYZERS_PREFIX')
expect(secure_analyzers['label']).to eq('Image prefix')
- expect(secure_analyzers['defaultValue']).to eq(secure_analyzers_prefix)
- expect(secure_analyzers['value']).to eq(secure_analyzers_prefix)
+ expect(secure_analyzers['defaultValue']).to eq('$TEMPLATE_REGISTRY_HOST/security-products')
+ expect(secure_analyzers['value']).to eq('$TEMPLATE_REGISTRY_HOST/security-products')
expect(secure_analyzers['size']).to eq('LARGE')
expect(secure_analyzers['options']).to be_nil
end
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
deleted file mode 100644
index 7c78d8b0305..00000000000
--- a/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::BackfillCiRunnerSemver, :migration, schema: 20220601151900 do
- let(:ci_runners) { table(:ci_runners, database: :ci) }
-
- subject do
- described_class.new(
- start_id: 10,
- end_id: 15,
- batch_table: :ci_runners,
- batch_column: :id,
- sub_batch_size: 10,
- pause_ms: 0,
- connection: Ci::ApplicationRecord.connection)
- end
-
- describe '#perform' do
- it 'populates semver column on all runners in range' do
- ci_runners.create!(id: 10, runner_type: 1, version: %q(HEAD-fd84d97))
- ci_runners.create!(id: 11, runner_type: 1, version: %q(v1.2.3))
- ci_runners.create!(id: 12, runner_type: 1, version: %q(2.1.0))
- ci_runners.create!(id: 13, runner_type: 1, version: %q(11.8.0~beta.935.g7f6d2abc))
- ci_runners.create!(id: 14, runner_type: 1, version: %q(13.2.2/1.1.0))
- ci_runners.create!(id: 15, runner_type: 1, version: %q('14.3.4'))
-
- subject.perform
-
- expect(ci_runners.all).to contain_exactly(
- an_object_having_attributes(id: 10, semver: nil),
- an_object_having_attributes(id: 11, semver: '1.2.3'),
- an_object_having_attributes(id: 12, semver: '2.1.0'),
- an_object_having_attributes(id: 13, semver: '11.8.0'),
- an_object_having_attributes(id: 14, semver: '13.2.2'),
- an_object_having_attributes(id: 15, semver: '14.3.4')
- )
- end
-
- it 'skips runners that already have semver value' do
- ci_runners.create!(id: 10, runner_type: 1, version: %q(1.2.4), semver: '1.2.3')
- ci_runners.create!(id: 11, runner_type: 1, version: %q(1.2.5))
- ci_runners.create!(id: 12, runner_type: 1, version: %q(HEAD), semver: '1.2.4')
-
- subject.perform
-
- expect(ci_runners.all).to contain_exactly(
- an_object_having_attributes(id: 10, semver: '1.2.3'),
- an_object_having_attributes(id: 11, semver: '1.2.5'),
- an_object_having_attributes(id: 12, semver: '1.2.4')
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
new file mode 100644
index 00000000000..f113ffcd0a7
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::BaseImporter do
+ let(:project) { instance_double('Project') }
+ let(:user_finder) { instance_double('Gitlab::GithubImport::UserFinder') }
+ let(:issue_event) { instance_double('Gitlab::GithubImport::Representation::IssueEvent') }
+ let(:importer_class) { Class.new(described_class) }
+ let(:importer_instance) { importer_class.new(project, user_finder) }
+
+ describe '#execute' do
+ it { expect { importer_instance.execute(issue_event) }.to raise_error(NotImplementedError) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
new file mode 100644
index 00000000000..a1918dd0da8
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
+ subject(:importer) { described_class.new(project, user_finder) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:assigner) { create(:user) }
+
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => event_type,
+ 'commit_id' => nil,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'assigner' => { 'id' => assigner.id, 'login' => assigner.username },
+ 'assignee' => { 'id' => assignee.id, 'login' => assignee.username },
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:note_attrs) do
+ {
+ noteable_id: issue.id,
+ noteable_type: Issue.name,
+ project_id: project.id,
+ author_id: assigner.id,
+ system: true,
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_system_note_metadata_attrs) do
+ {
+ action: "assignee",
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ shared_examples 'new note' do
+ it 'creates expected note' do
+ expect { importer.execute(issue_event) }.to change { issue.notes.count }
+ .from(0).to(1)
+
+ expect(issue.notes.last)
+ .to have_attributes(expected_note_attrs)
+ end
+
+ it 'creates expected system note metadata' do
+ expect { importer.execute(issue_event) }.to change { SystemNoteMetadata.count }
+ .from(0).to(1)
+
+ expect(SystemNoteMetadata.last)
+ .to have_attributes(
+ expected_system_note_metadata_attrs.merge(
+ note_id: Note.last.id
+ )
+ )
+ end
+ end
+
+ describe '#execute' do
+ before do
+ allow(user_finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
+ allow(user_finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ end
+
+ context 'when importing an assigned event' do
+ let(:event_type) { 'assigned' }
+ let(:expected_note_attrs) { note_attrs.merge(note: "assigned to @#{assignee.username}") }
+
+ it_behaves_like 'new note'
+ end
+
+ context 'when importing an unassigned event' do
+ let(:event_type) { 'unassigned' }
+ let(:expected_note_attrs) { note_attrs.merge(note: "unassigned @#{assigner.username}") }
+
+ it_behaves_like 'new note'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
index b773598853d..98a8daf1653 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
@@ -3,18 +3,20 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:label) { create(:label, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => event_type,
'commit_id' => nil,
'label_title' => label.title,
@@ -43,6 +45,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
context 'when importing a labeled event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
index 5db708b9049..a5852c967df 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
@@ -3,18 +3,20 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:milestone) { create(:milestone, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => event_type,
'commit_id' => nil,
'milestone_title' => milestone.title,
@@ -45,6 +47,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
describe '#execute' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(milestone.id)
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
context 'when importing a milestoned event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
index 116917d3e06..749c52a215e 100644
--- a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -3,11 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
@@ -16,7 +18,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
@@ -45,6 +47,10 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
}.stringify_keys
end
+ before do
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+
it 'creates expected event and state event' do
importer.execute(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
index 118c482a7d9..bf785d27f05 100644
--- a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
@@ -3,12 +3,14 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_cache do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sawyer_stub) { Struct.new(:iid, :issuable_type, keyword_init: true) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:referenced_in) { build_stubbed(:issue, project: project) }
@@ -19,7 +21,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'cross-referenced',
'source' => {
'type' => 'issue',
@@ -53,6 +55,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
other_issue_resource = sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'Issue')
Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
.cache_database_id(referenced_in.iid)
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
it 'creates expected note' do
@@ -75,6 +78,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'MergeRequest')
Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
.cache_database_id(referenced_in.iid)
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
end
it 'creates expected note' do
@@ -87,7 +91,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
end
context 'when referenced in out of project issue/pull_request' do
- it 'creates expected note' do
+ it 'does not create expected note' do
importer.execute(issue_event)
expect(issue.notes.count).to eq 0
diff --git a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
index a8c3fbcb05d..8acf82af40c 100644
--- a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
@@ -3,17 +3,19 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'renamed',
'commit_id' => nil,
'created_at' => '2022-04-26 18:30:53 UTC',
@@ -45,6 +47,10 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
end
describe '#execute' do
+ before do
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+
it 'creates expected note' do
expect { importer.execute(issue_event) }.to change { issue.notes.count }
.from(0).to(1)
diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
index 81653b0ecdc..39b8809dfa4 100644
--- a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
@@ -3,11 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, user_finder) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -15,7 +17,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'reopened',
'created_at' => '2022-04-26 18:30:53 UTC',
'issue_db_id' => issue.id
@@ -42,6 +44,10 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
}.stringify_keys
end
+ before do
+ allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+
it 'creates expected event and state event' do
importer.execute(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
index 41d777fb466..fee7c2708a4 100644
--- a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
- .to receive(:new).with(project, user.id)
+ .to receive(:new).with(project, anything)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
@@ -43,12 +43,6 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
describe '#execute' do
before do
- allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
- allow(finder).to receive(:author_id_for)
- .with(issue_event, author_key: :actor)
- .and_return(user.id, true)
- end
-
issue_event.attributes[:issue_db_id] = issue.id
end
@@ -108,6 +102,20 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
Gitlab::GithubImport::Importer::Events::CrossReferenced
end
+ context "when it's assigned issue event" do
+ let(:event_name) { 'assigned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+
+ context "when it's unassigned issue event" do
+ let(:event_name) { 'unassigned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+
context "when it's unknown issue event" do
let(:event_name) { 'fake' }
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
index 7382b0e2fff..cf796b55b14 100644
--- a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -91,6 +91,29 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
end
end
+ context 'when assignee and assigner data is present' do
+ it 'includes assignee and assigner details' do
+ expect(issue_event.assignee)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+ expect(issue_event.assignee.id).to eq(5)
+ expect(issue_event.assignee.login).to eq('tom')
+
+ expect(issue_event.assigner)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+ expect(issue_event.assigner.id).to eq(6)
+ expect(issue_event.assigner.login).to eq('jerry')
+ end
+ end
+
+ context 'when assignee and assigner data is empty' do
+ let(:with_assignee) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.assignee).to eq nil
+ expect(issue_event.assigner).to eq nil
+ end
+ end
+
it 'includes the created timestamp' do
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
end
@@ -106,8 +129,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
describe '.from_api_response' do
let(:response) do
event_resource = Struct.new(
- :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label,
- :rename, :milestone, :source, :issue_db_id, :created_at, :performed_via_github_app,
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
+ :source, :assignee, :assigner, :issue_db_id, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
@@ -124,6 +147,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
rename: with_rename ? { from: 'old title', to: 'new title' } : nil,
milestone: with_milestone ? { title: 'milestone title' } : nil,
source: { type: 'issue', id: 123456 },
+ assignee: with_assignee ? user_resource.new(id: 5, login: 'tom') : nil,
+ assigner: with_assignee ? user_resource.new(id: 6, login: 'jerry') : nil,
issue_db_id: 100500,
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
@@ -134,6 +159,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_label) { true }
let(:with_rename) { true }
let(:with_milestone) { true }
+ let(:with_assignee) { true }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
@@ -157,6 +183,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
'new_title' => with_rename ? 'new title' : nil,
'milestone_title' => (with_milestone ? 'milestone title' : nil),
'source' => { 'type' => 'issue', 'id' => 123456 },
+ 'assignee' => (with_assignee ? { 'id' => 5, 'login' => 'tom' } : nil),
+ 'assigner' => (with_assignee ? { 'id' => 6, 'login' => 'jerry' } : nil),
"issue_db_id" => 100500,
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
@@ -167,6 +195,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_label) { true }
let(:with_rename) { true }
let(:with_milestone) { true }
+ let(:with_assignee) { true }
let(:issue_event) { described_class.from_json_hash(hash) }
end
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 8eb6eedd72d..d85e298785c 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -15,32 +15,64 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:finder) { described_class.new(project, client) }
describe '#author_id_for' do
- it 'returns the user ID for the author of an object' do
- user = double(:user, id: 4, login: 'kittens')
- note = double(:note, author: user)
+ context 'with default author_key' do
+ it 'returns the user ID for the author of an object' do
+ user = double(:user, id: 4, login: 'kittens')
+ note = double(:note, author: user)
- expect(finder).to receive(:user_id_for).with(user).and_return(42)
+ expect(finder).to receive(:user_id_for).with(user).and_return(42)
- expect(finder.author_id_for(note)).to eq([42, true])
- end
+ expect(finder.author_id_for(note)).to eq([42, true])
+ end
- it 'returns the ID of the project creator if no user ID could be found' do
- user = double(:user, id: 4, login: 'kittens')
- note = double(:note, author: user)
+ it 'returns the ID of the project creator if no user ID could be found' do
+ user = double(:user, id: 4, login: 'kittens')
+ note = double(:note, author: user)
- expect(finder).to receive(:user_id_for).with(user).and_return(nil)
+ expect(finder).to receive(:user_id_for).with(user).and_return(nil)
- expect(finder.author_id_for(note)).to eq([project.creator_id, false])
- end
+ expect(finder.author_id_for(note)).to eq([project.creator_id, false])
+ end
+
+ it 'returns the ID of the ghost user when the object has no user' do
+ note = double(:note, author: nil)
- it 'returns the ID of the ghost user when the object has no user' do
- note = double(:note, author: nil)
+ expect(finder.author_id_for(note)).to eq([User.ghost.id, true])
+ end
- expect(finder.author_id_for(note)).to eq([User.ghost.id, true])
+ it 'returns the ID of the ghost user when the given object is nil' do
+ expect(finder.author_id_for(nil)).to eq([User.ghost.id, true])
+ end
end
- it 'returns the ID of the ghost user when the given object is nil' do
- expect(finder.author_id_for(nil)).to eq([User.ghost.id, true])
+ context 'with a non-default author_key' do
+ let(:user) { double(:user, id: 4, login: 'kittens') }
+
+ shared_examples 'user ID finder' do |author_key|
+ it 'returns the user ID for an object' do
+ expect(finder).to receive(:user_id_for).with(user).and_return(42)
+
+ expect(finder.author_id_for(issue_event, author_key: author_key)).to eq([42, true])
+ end
+ end
+
+ context 'when the author_key parameter is :actor' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', actor: user) }
+
+ it_behaves_like 'user ID finder', :actor
+ end
+
+ context 'when the author_key parameter is :assignee' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', assignee: user) }
+
+ it_behaves_like 'user ID finder', :assignee
+ end
+
+ context 'when the author_key parameter is :assigner' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', assigner: user) }
+
+ it_behaves_like 'user ID finder', :assigner
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project/relation_saver_spec.rb b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb
new file mode 100644
index 00000000000..dec51b3afd1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ImportExport::Project::RelationSaver do
+ include ImportExport::CommonUtil
+
+ subject(:relation_saver) { described_class.new(project: project, shared: shared, relation: relation) }
+
+ let_it_be(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let_it_be(:project) { setup_project }
+
+ let(:relation) { Projects::ImportExport::RelationExport::ROOT_RELATION }
+ let(:shared) do
+ shared = project.import_export_shared
+ allow(shared).to receive(:export_path).and_return(export_path)
+ shared
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
+ describe '#save' do
+ context 'when relation is the root node' do
+ let(:relation) { Projects::ImportExport::RelationExport::ROOT_RELATION }
+
+ it 'serializes the root node as a json file in the export path' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ json = read_json(File.join(shared.export_path, 'project.json'))
+ expect(json).to include({ 'description' => 'Project description' })
+ end
+
+ it 'serializes only allowed attributes' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ json = read_json(File.join(shared.export_path, 'project.json'))
+ expect(json).to include({ 'description' => 'Project description' })
+ expect(json.keys).not_to include('name')
+ end
+
+ it 'successfuly serializes without errors' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(true)
+ expect(shared.errors).to be_empty
+ end
+ end
+
+ context 'when relation is a child node' do
+ let(:relation) { 'labels' }
+
+ it 'serializes the child node as a ndjson file in the export path inside the project folder' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ ndjson = read_ndjson(File.join(shared.export_path, 'project', "#{relation}.ndjson"))
+ expect(ndjson.first).to include({ 'title' => 'Label 1' })
+ expect(ndjson.second).to include({ 'title' => 'Label 2' })
+ end
+
+ it 'serializes only allowed attributes' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ ndjson = read_ndjson(File.join(shared.export_path, 'project', "#{relation}.ndjson"))
+ expect(ndjson.first.keys).not_to include('description_html')
+ end
+
+ it 'successfuly serializes without errors' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(true)
+ expect(shared.errors).to be_empty
+ end
+ end
+
+ context 'when relation name is not supported' do
+ let(:relation) { 'unknown' }
+
+ it 'returns false and register the error' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(false)
+ expect(shared.errors).to be_present
+ end
+ end
+
+ context 'when an exception occurs during serialization' do
+ it 'returns false and register the exception error message' do
+ allow_next_instance_of(Gitlab::ImportExport::Json::StreamingSerializer) do |serializer|
+ allow(serializer).to receive(:serialize_root).and_raise('Error!')
+ end
+
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(false)
+ expect(shared.errors).to include('Error!')
+ end
+ end
+ end
+
+ def setup_project
+ project = create(:project,
+ description: 'Project description'
+ )
+
+ create(:label, project: project, title: 'Label 1')
+ create(:label, project: project, title: 'Label 2')
+
+ project
+ end
+
+ def read_json(path)
+ Gitlab::Json.parse(IO.read(path))
+ end
+
+ def read_ndjson(path)
+ relations = []
+ File.foreach(path) do |line|
+ json = Gitlab::Json.parse(line)
+ relations << json
+ end
+ relations
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index fa03c6920c6..26883fc57b8 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -856,16 +856,6 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
it 'returns the successful deployment jobs for the last deployment pipeline' do
expect(subject.pluck(:id)).to contain_exactly(deployment_a.id, deployment_b.id)
end
-
- context 'when the feature flag is disabled' do
- before do
- stub_feature_flags(batch_load_environment_last_deployment_group: false)
- end
-
- it 'returns the successful deployment jobs for the last deployment pipeline' do
- expect(subject.pluck(:id)).to contain_exactly(deployment_a.id, deployment_b.id)
- end
- end
end
end
diff --git a/spec/models/projects/import_export/relation_export_spec.rb b/spec/models/projects/import_export/relation_export_spec.rb
index c74ca82e161..8643fbc7b46 100644
--- a/spec/models/projects/import_export/relation_export_spec.rb
+++ b/spec/models/projects/import_export/relation_export_spec.rb
@@ -20,4 +20,36 @@ RSpec.describe Projects::ImportExport::RelationExport, type: :model do
it { is_expected.to validate_length_of(:jid).is_at_most(255) }
it { is_expected.to validate_length_of(:export_error).is_at_most(300) }
end
+
+ describe '.by_relation' do
+ it 'returns export relations filtered by relation name' do
+ project_relation_export_1 = create(:project_relation_export, relation: 'labels')
+ project_relation_export_2 = create(:project_relation_export, relation: 'labels')
+ create(:project_relation_export, relation: 'uploads')
+
+ relations = described_class.by_relation('labels').to_a
+
+ expect(relations).to match_array([project_relation_export_1, project_relation_export_2])
+ end
+ end
+
+ describe '.relation_names_list' do
+ it 'includes extra relations list' do
+ expect(described_class.relation_names_list).to include(
+ 'design_repository', 'lfs_objects', 'repository', 'snippets_repository', 'uploads', 'wiki_repository'
+ )
+ end
+
+ it 'includes root tree relation name project' do
+ expect(described_class.relation_names_list).to include('project')
+ end
+
+ it 'includes project tree top level relation nodes' do
+ expect(described_class.relation_names_list).to include('milestones', 'issues', 'snippets', 'releases')
+ end
+
+ it 'includes project tree nested relation nodes' do
+ expect(described_class.relation_names_list).not_to include('events', 'notes')
+ end
+ end
end
diff --git a/spec/services/projects/import_export/relation_export_service_spec.rb b/spec/services/projects/import_export/relation_export_service_spec.rb
new file mode 100644
index 00000000000..94f5653ee7d
--- /dev/null
+++ b/spec/services/projects/import_export/relation_export_service_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ImportExport::RelationExportService do
+ using RSpec::Parameterized::TableSyntax
+
+ subject(:service) { described_class.new(relation_export, 'jid') }
+
+ let_it_be(:project_export_job) { create(:project_export_job) }
+ let_it_be(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let_it_be(:archive_path) { "#{Dir.tmpdir}/project_archive_spec" }
+
+ let(:relation_export) { create(:project_relation_export, relation: relation, project_export_job: project_export_job) }
+
+ before do
+ stub_uploads_object_storage(ImportExportUploader, enabled: false)
+
+ allow(project_export_job.project.import_export_shared).to receive(:export_path).and_return(export_path)
+ allow(project_export_job.project.import_export_shared).to receive(:archive_path).and_return(archive_path)
+ allow(FileUtils).to receive(:remove_entry).with(any_args).and_call_original
+ end
+
+ describe '#execute' do
+ let(:relation) { 'labels' }
+
+ it 'removes temporary paths used to export files' do
+ expect(FileUtils).to receive(:remove_entry).with(export_path)
+ expect(FileUtils).to receive(:remove_entry).with(archive_path)
+
+ service.execute
+ end
+
+ context 'when saver fails to export relation' do
+ before do
+ allow_next_instance_of(Gitlab::ImportExport::Project::RelationSaver) do |saver|
+ allow(saver).to receive(:save).and_return(false)
+ end
+ end
+
+ it 'flags export as failed' do
+ service.execute
+
+ expect(relation_export.failed?).to eq(true)
+ end
+
+ it 'logs failed message' do
+ expect_next_instance_of(Gitlab::Export::Logger) do |logger|
+ expect(logger).to receive(:error).with(
+ export_error: '',
+ message: 'Project relation export failed',
+ project_export_job_id: project_export_job.id,
+ project_id: project_export_job.project.id,
+ project_name: project_export_job.project.name
+ )
+ end
+
+ service.execute
+ end
+ end
+
+ context 'when an exception is raised' do
+ before do
+ allow_next_instance_of(Gitlab::ImportExport::Project::RelationSaver) do |saver|
+ allow(saver).to receive(:save).and_raise('Error!')
+ end
+ end
+
+ it 'flags export as failed' do
+ service.execute
+
+ expect(relation_export.failed?).to eq(true)
+ expect(relation_export.export_error).to eq('Error!')
+ end
+
+ it 'logs exception error message' do
+ expect_next_instance_of(Gitlab::Export::Logger) do |logger|
+ expect(logger).to receive(:error).with(
+ export_error: 'Error!',
+ message: 'Project relation export failed',
+ project_export_job_id: project_export_job.id,
+ project_id: project_export_job.project.id,
+ project_name: project_export_job.project.name
+ )
+ end
+
+ service.execute
+ end
+ end
+
+ describe 'relation name and saver class' do
+ where(:relation_name, :saver) do
+ Projects::ImportExport::RelationExport::UPLOADS_RELATION | Gitlab::ImportExport::UploadsSaver
+ Projects::ImportExport::RelationExport::REPOSITORY_RELATION | Gitlab::ImportExport::RepoSaver
+ Projects::ImportExport::RelationExport::WIKI_REPOSITORY_RELATION | Gitlab::ImportExport::WikiRepoSaver
+ Projects::ImportExport::RelationExport::LFS_OBJECTS_RELATION | Gitlab::ImportExport::LfsSaver
+ Projects::ImportExport::RelationExport::SNIPPETS_REPOSITORY_RELATION | Gitlab::ImportExport::SnippetsRepoSaver
+ Projects::ImportExport::RelationExport::DESIGN_REPOSITORY_RELATION | Gitlab::ImportExport::DesignRepoSaver
+ Projects::ImportExport::RelationExport::ROOT_RELATION | Gitlab::ImportExport::Project::RelationSaver
+ 'labels' | Gitlab::ImportExport::Project::RelationSaver
+ end
+
+ with_them do
+ let(:relation) { relation_name }
+
+ it 'exports relation using correct saver' do
+ expect(saver).to receive(:new).and_call_original
+
+ service.execute
+ end
+
+ it 'assigns finished status and relation file' do
+ service.execute
+
+ expect(relation_export.finished?).to eq(true)
+ expect(relation_export.upload.export_file.filename).to eq("#{relation}.tar.gz")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/security/ci_configuration/sast_parser_service_spec.rb b/spec/services/security/ci_configuration/sast_parser_service_spec.rb
index 4346d0a9e07..b11f31a9086 100644
--- a/spec/services/security/ci_configuration/sast_parser_service_spec.rb
+++ b/spec/services/security/ci_configuration/sast_parser_service_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe Security::CiConfiguration::SastParserService do
let(:bandit) { configuration['analyzers'][0] }
let(:brakeman) { configuration['analyzers'][1] }
let(:sast_brakeman_level) { brakeman['variables'][0] }
+ let(:secure_analyzers_prefix) { '$TEMPLATE_REGISTRY_HOST/security-products' }
it 'parses the configuration for SAST' do
expect(secure_analyzers['default_value']).to eql(secure_analyzers_prefix)
diff --git a/spec/services/work_items/create_and_link_service_spec.rb b/spec/services/work_items/create_and_link_service_spec.rb
index 831af775101..e259a22d388 100644
--- a/spec/services/work_items/create_and_link_service_spec.rb
+++ b/spec/services/work_items/create_and_link_service_spec.rb
@@ -62,6 +62,8 @@ RSpec.describe WorkItems::CreateAndLinkService do
)
end
+ it_behaves_like 'title with extra spaces'
+
context 'when link params are valid' do
let(:link_params) { { parent_work_item: related_work_item } }
diff --git a/spec/services/work_items/create_from_task_service_spec.rb b/spec/services/work_items/create_from_task_service_spec.rb
index 7d2dab228b1..7c5430f038c 100644
--- a/spec/services/work_items/create_from_task_service_spec.rb
+++ b/spec/services/work_items/create_from_task_service_spec.rb
@@ -64,6 +64,8 @@ RSpec.describe WorkItems::CreateFromTaskService do
expect(list_work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
end
+
+ it_behaves_like 'title with extra spaces'
end
context 'when last operation fails' do
diff --git a/spec/support/helpers/ci/template_helpers.rb b/spec/support/helpers/ci/template_helpers.rb
index 598a5a0becc..119f8d001a1 100644
--- a/spec/support/helpers/ci/template_helpers.rb
+++ b/spec/support/helpers/ci/template_helpers.rb
@@ -5,6 +5,10 @@ module Ci
def secure_analyzers_prefix
'registry.gitlab.com/security-products'
end
+
+ def template_registry_host
+ 'registry.gitlab.com'
+ end
end
end
diff --git a/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
new file mode 100644
index 00000000000..7771e7f0e21
--- /dev/null
+++ b/spec/support/shared_examples/services/work_items/create_task_shared_examples.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'title with extra spaces' do
+ context 'when title has extra spaces' do
+ before do
+ params[:title] = " Awesome work item "
+ end
+
+ it 'removes extra leading and trailing whitespaces from title' do
+ subject
+
+ created_work_item = WorkItem.last
+ expect(created_work_item.title).to eq('Awesome work item')
+ end
+ end
+end
diff --git a/spec/workers/projects/import_export/relation_export_worker_spec.rb b/spec/workers/projects/import_export/relation_export_worker_spec.rb
new file mode 100644
index 00000000000..236650fe55b
--- /dev/null
+++ b/spec/workers/projects/import_export/relation_export_worker_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ImportExport::RelationExportWorker, type: :worker do
+ let(:project_relation_export) { create(:project_relation_export) }
+ let(:job_args) { [project_relation_export.id] }
+
+ it_behaves_like 'an idempotent worker'
+
+ describe '#perform' do
+ subject(:worker) { described_class.new }
+
+ context 'when relation export has initial state queued' do
+ let(:project_relation_export) { create(:project_relation_export) }
+
+ it 'calls RelationExportService' do
+ expect_next_instance_of(Projects::ImportExport::RelationExportService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ worker.perform(project_relation_export.id)
+ end
+ end
+
+ context 'when relation export does not have queued state' do
+ let(:project_relation_export) { create(:project_relation_export, status_event: :start) }
+
+ it 'does not call RelationExportService' do
+ expect(Projects::ImportExport::RelationExportService).not_to receive(:new)
+
+ worker.perform(project_relation_export.id)
+ end
+ end
+ end
+end