summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo.yml3
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue6
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js2
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue2
-rw-r--r--app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue24
-rw-r--r--app/assets/javascripts/pages/projects/labels/index/index.js102
-rw-r--r--app/assets/javascripts/releases/components/issuable_stats.vue99
-rw-r--r--app/assets/javascripts/releases/components/release_block_milestone_info.vue72
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue157
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js27
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues.js66
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues.query.graphql13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue10
-rw-r--r--app/assets/stylesheets/pages/groups.scss6
-rw-r--r--app/helpers/time_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb20
-rw-r--r--app/models/issues/csv_import.rb (renamed from app/models/csv_issue_import.rb)4
-rw-r--r--app/policies/base_policy.rb2
-rw-r--r--app/policies/project_policy.rb2
-rw-r--r--app/services/clusters/kubernetes/create_or_update_service_account_service.rb8
-rw-r--r--app/services/issues/import_csv_service.rb2
-rw-r--r--app/services/packages/create_event_service.rb2
-rw-r--r--app/views/groups/labels/index.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/shared/_label.html.haml5
-rw-r--r--changelogs/unreleased/205578-feature-flag-pkg-events.yml5
-rw-r--r--changelogs/unreleased/229703-aqualls-promote-label-modal.yml5
-rw-r--r--changelogs/unreleased/tancnle-fix-jupyter-notebook-overflow.yml5
-rw-r--r--changelogs/unreleased/vij-fix-inconsistent-branch-names.yml5
-rw-r--r--config/feature_flags/development/collect_package_events.yml7
-rw-r--r--config/feature_flags/development/saml_group_links.yml7
-rw-r--r--doc/development/new_fe_guide/modules/index.md4
-rw-r--r--doc/development/new_fe_guide/modules/widget_extensions.md50
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb18
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb2
-rw-r--r--lib/gitlab/robots_txt/parser.rb60
-rw-r--r--lib/gitlab/setup_helper.rb1
-rw-r--r--lib/gitlab/url_blocker.rb4
-rw-r--r--lib/gitlab/usage_data.rb2
-rw-r--r--locale/gitlab.pot46
-rw-r--r--public/robots.txt1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb55
-rw-r--r--spec/factories/issues/csv_import.rb (renamed from spec/factories/csv_issue_import.rb)2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb12
-rw-r--r--spec/frontend/pages/labels/components/promote_label_modal_spec.js7
-rw-r--r--spec/frontend/releases/components/__snapshots__/issuable_stats_spec.js.snap9
-rw-r--r--spec/frontend/releases/components/issuable_stats_spec.js114
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js63
-rw-r--r--spec/frontend/vue_mr_widget/components/extensions/index_spec.js31
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js4
-rw-r--r--spec/helpers/time_helper_spec.rb10
-rw-r--r--spec/helpers/tree_helper_spec.rb31
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/robots_txt/parser_spec.rb15
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/models/issues/csv_import_spec.rb (renamed from spec/models/csv_issue_import_spec.rb)2
-rw-r--r--spec/policies/base_policy_spec.rb74
-rw-r--r--spec/requests/robots_txt_spec.rb72
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb1
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb1
-rw-r--r--spec/services/issues/import_csv_service_spec.rb2
-rw-r--r--spec/services/packages/create_event_service_spec.rb28
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb5
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb4
75 files changed, 1113 insertions, 359 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 0f0844a7987..6b91fc8c428 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -842,7 +842,6 @@ Rails/SaveBang:
- 'spec/controllers/projects/imports_controller_spec.rb'
- 'spec/controllers/projects/issues_controller_spec.rb'
- 'spec/controllers/projects/labels_controller_spec.rb'
- - 'spec/controllers/projects/merge_requests_controller_spec.rb'
- 'spec/controllers/projects/milestones_controller_spec.rb'
- 'spec/controllers/projects/notes_controller_spec.rb'
- 'spec/controllers/projects/pipelines_controller_spec.rb'
@@ -1264,8 +1263,6 @@ FactoryBot/InlineAssociation:
- 'ee/spec/factories/geo/event_log.rb'
- 'ee/spec/factories/groups.rb'
- 'ee/spec/factories/merge_request_blocks.rb'
- - 'ee/spec/factories/resource_iteration_event.rb'
- - 'ee/spec/factories/resource_weight_events.rb'
- 'ee/spec/factories/vulnerabilities/feedback.rb'
- 'spec/factories/atlassian_identities.rb'
- 'spec/factories/design_management/design_at_version.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 03630f1d18e..2e96a9ff62e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b011445d9ffa82c36c771946e035852888df0730
+b85367529ca34c8c423b94b7486c44e109ed553f
diff --git a/Gemfile b/Gemfile
index ff47974edc2..d3671cac4f5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -317,7 +317,7 @@ gem 'premailer-rails', '~> 1.10.3'
gem 'gitlab-labkit', '0.13.1'
# I18n
-gem 'ruby_parser', '~> 3.8', require: false
+gem 'ruby_parser', '~> 3.15', require: false
gem 'rails-i18n', '~> 6.0'
gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index 21c36eb4c23..7b7223af8b6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1042,7 +1042,7 @@ GEM
nokogiri (>= 1.5.10)
ruby-statistics (2.1.2)
ruby2_keywords (0.0.2)
- ruby_parser (3.13.1)
+ ruby_parser (3.15.0)
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubypants (0.2.0)
@@ -1085,7 +1085,7 @@ GEM
sentry-raven (3.0.4)
faraday (>= 1.0)
settingslogic (2.0.9)
- sexp_processor (4.12.0)
+ sexp_processor (4.15.1)
shellany (0.0.1)
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
@@ -1474,7 +1474,7 @@ DEPENDENCIES
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 1.3.0)
ruby-progressbar
- ruby_parser (~> 3.8)
+ ruby_parser (~> 3.15)
rubyzip (~> 2.0.0)
rugged (~> 0.28)
sanitize (~> 5.2.1)
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index f8fb58cdca2..3e06e8264d3 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -8,10 +8,10 @@ import {
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlSprintf,
GlTable,
+ GlTooltipDirective,
} from '@gitlab/ui';
import AncestorNotice from './ancestor_notice.vue';
import NodeErrorHelpText from './node_error_help_text.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
@@ -30,7 +30,7 @@ export default {
NodeErrorHelpText,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
computed: {
...mapState([
@@ -227,7 +227,7 @@ export default {
<gl-loading-icon
v-if="item.status === 'deleting' || item.status === 'creating'"
- v-tooltip
+ v-gl-tooltip
:title="statusTitle(item.status)"
size="sm"
/>
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 37f887bcf0a..416ca88d6c9 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -14,6 +14,8 @@ const createTranslatedTextForFiles = (files, text) => {
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
+// Note: If changing the structure of the placeholder branch name, please also
+// update #patch_branch_name in app/helpers/tree_helper.rb
export const placeholderBranchName = (state, _, rootState) =>
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
-BRANCH_SUFFIX_COUNT,
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index 4d527baf730..a3d7ddd5bad 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -37,6 +37,6 @@ export default {
<template>
<div class="output">
<prompt type="Out" :count="count" :show-output="showOutput" />
- <div v-html="sanitizedOutput"></div>
+ <div class="gl-overflow-auto" v-html="sanitizedOutput"></div>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
index f58e4909a08..7b5e0f70b7b 100644
--- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
+++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
@@ -1,15 +1,21 @@
<script>
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlModal } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
-import { s__, sprintf } from '~/locale';
+import { s__, __, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
export default {
+ primaryProps: {
+ text: s__('Labels|Promote Label'),
+ attributes: [{ variant: 'warning' }, { category: 'primary' }],
+ },
+ cancelProps: {
+ text: __('Cancel'),
+ },
components: {
- GlModal: DeprecatedModal2,
+ GlModal,
GlSprintf,
},
props: {
@@ -72,12 +78,12 @@ export default {
</script>
<template>
<gl-modal
- id="promote-label-modal"
- :footer-primary-button-text="s__('Labels|Promote Label')"
- footer-primary-button-variant="warning"
- @submit="onSubmit"
+ modal-id="promote-label-modal"
+ :action-primary="$options.primaryProps"
+ :action-cancel="$options.cancelProps"
+ @primary="onSubmit"
>
- <div slot="title" class="modal-title-with-label">
+ <div slot="modal-title" class="modal-title-with-label">
<gl-sprintf
:message="
s__(
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
index 36cf485f33d..ee129011f9a 100644
--- a/app/assets/javascripts/pages/projects/labels/index/index.js
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -27,71 +27,55 @@ const initLabelIndex = () => {
eventHub.$once('promoteLabelModal.requestFinished', onRequestFinished);
};
- const onDeleteButtonClick = event => {
- const button = event.currentTarget;
- const modalProps = {
- labelTitle: button.dataset.labelTitle,
- labelColor: button.dataset.labelColor,
- labelTextColor: button.dataset.labelTextColor,
- url: button.dataset.url,
- groupName: button.dataset.groupName,
- };
- eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
- eventHub.$emit('promoteLabelModal.props', modalProps);
- };
-
const promoteLabelButtons = document.querySelectorAll('.js-promote-project-label-button');
- promoteLabelButtons.forEach(button => {
- button.addEventListener('click', onDeleteButtonClick);
- });
- eventHub.$once('promoteLabelModal.mounted', () => {
- promoteLabelButtons.forEach(button => {
- button.removeAttribute('disabled');
- });
- });
+ return new Vue({
+ el: '#js-promote-label-modal',
+ data() {
+ return {
+ modalProps: {
+ labelTitle: '',
+ labelColor: '',
+ labelTextColor: '',
+ url: '',
+ groupName: '',
+ },
+ };
+ },
+ mounted() {
+ eventHub.$on('promoteLabelModal.props', this.setModalProps);
+ eventHub.$emit('promoteLabelModal.mounted');
- const promoteLabelModal = document.getElementById('promote-label-modal');
- let promoteLabelModalComponent;
+ promoteLabelButtons.forEach(button => {
+ button.removeAttribute('disabled');
+ button.addEventListener('click', () => {
+ this.$root.$emit('bv::show::modal', 'promote-label-modal');
+ eventHub.$once('promoteLabelModal.requestStarted', onRequestStarted);
- if (promoteLabelModal) {
- promoteLabelModalComponent = new Vue({
- el: promoteLabelModal,
- components: {
- PromoteLabelModal,
- },
- data() {
- return {
- modalProps: {
- labelTitle: '',
- labelColor: '',
- labelTextColor: '',
- url: '',
- groupName: '',
- },
- };
- },
- mounted() {
- eventHub.$on('promoteLabelModal.props', this.setModalProps);
- eventHub.$emit('promoteLabelModal.mounted');
- },
- beforeDestroy() {
- eventHub.$off('promoteLabelModal.props', this.setModalProps);
- },
- methods: {
- setModalProps(modalProps) {
- this.modalProps = modalProps;
- },
- },
- render(createElement) {
- return createElement('promote-label-modal', {
- props: this.modalProps,
+ this.setModalProps({
+ labelTitle: button.dataset.labelTitle,
+ labelColor: button.dataset.labelColor,
+ labelTextColor: button.dataset.labelTextColor,
+ url: button.dataset.url,
+ groupName: button.dataset.groupName,
+ });
});
+ });
+ },
+ beforeDestroy() {
+ eventHub.$off('promoteLabelModal.props', this.setModalProps);
+ },
+ methods: {
+ setModalProps(modalProps) {
+ this.modalProps = modalProps;
},
- });
- }
-
- return promoteLabelModalComponent;
+ },
+ render(createElement) {
+ return createElement(PromoteLabelModal, {
+ props: this.modalProps,
+ });
+ },
+ });
};
document.addEventListener('DOMContentLoaded', initLabelIndex);
diff --git a/app/assets/javascripts/releases/components/issuable_stats.vue b/app/assets/javascripts/releases/components/issuable_stats.vue
new file mode 100644
index 00000000000..5f28331c543
--- /dev/null
+++ b/app/assets/javascripts/releases/components/issuable_stats.vue
@@ -0,0 +1,99 @@
+<script>
+import { GlLink, GlBadge, GlSprintf } from '@gitlab/ui';
+
+export default {
+ name: 'IssuableStats',
+ components: {
+ GlLink,
+ GlBadge,
+ GlSprintf,
+ },
+ props: {
+ label: {
+ type: String,
+ required: true,
+ },
+ total: {
+ type: Number,
+ required: true,
+ },
+ closed: {
+ type: Number,
+ required: true,
+ },
+ merged: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ openPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ closedPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ mergedPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ open() {
+ return this.total - (this.closed + (this.merged || 0));
+ },
+ showMerged() {
+ return this.merged != null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container"
+ >
+ <span class="gl-mb-2">
+ {{ label }}
+ <gl-badge variant="muted" size="sm">{{ total }}</gl-badge>
+ </span>
+ <div class="gl-display-flex">
+ <span class="gl-white-space-pre-wrap" data-testid="open-stat">
+ <gl-sprintf :message="__('Open: %{open}')">
+ <template #open>
+ <gl-link v-if="openPath" :href="openPath">{{ open }}</gl-link>
+ <template v-else>{{ open }}</template>
+ </template>
+ </gl-sprintf>
+ </span>
+
+ <template v-if="showMerged">
+ <span class="gl-mx-2">&bull;</span>
+
+ <span class="gl-white-space-pre-wrap" data-testid="merged-stat">
+ <gl-sprintf :message="__('Merged: %{merged}')">
+ <template #merged>
+ <gl-link v-if="mergedPath" :href="mergedPath">{{ merged }}</gl-link>
+ <template v-else>{{ merged }}</template>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+
+ <span class="gl-mx-2">&bull;</span>
+
+ <span class="gl-white-space-pre-wrap" data-testid="closed-stat">
+ <gl-sprintf :message="__('Closed: %{closed}')">
+ <template #closed>
+ <gl-link v-if="closedPath" :href="closedPath">{{ closed }}</gl-link>
+ <template v-else>{{ closed }}</template>
+ </template>
+ </gl-sprintf>
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/components/release_block_milestone_info.vue b/app/assets/javascripts/releases/components/release_block_milestone_info.vue
index deff673cc17..30598a5eec1 100644
--- a/app/assets/javascripts/releases/components/release_block_milestone_info.vue
+++ b/app/assets/javascripts/releases/components/release_block_milestone_info.vue
@@ -1,24 +1,17 @@
<script>
-import {
- GlProgressBar,
- GlLink,
- GlBadge,
- GlButton,
- GlTooltipDirective,
- GlSprintf,
-} from '@gitlab/ui';
+import { GlProgressBar, GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { sum } from 'lodash';
import { __, n__, sprintf } from '~/locale';
import { MAX_MILESTONES_TO_DISPLAY } from '../constants';
+import IssuableStats from './issuable_stats.vue';
export default {
name: 'ReleaseBlockMilestoneInfo',
components: {
GlProgressBar,
GlLink,
- GlBadge,
GlButton,
- GlSprintf,
+ IssuableStats,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -64,18 +57,9 @@ export default {
closedIssuesCount() {
return sum(this.allIssueStats.map(stats => stats.closed || 0));
},
- openIssuesCount() {
- return this.totalIssuesCount - this.closedIssuesCount;
- },
milestoneLabelText() {
return n__('Milestone', 'Milestones', this.milestones.length);
},
- issueCountsText() {
- return sprintf(__('Open: %{open} • Closed: %{closed}'), {
- open: this.openIssuesCount,
- closed: this.closedIssuesCount,
- });
- },
milestonesToDisplay() {
return this.showAllMilestones
? this.milestones
@@ -106,20 +90,22 @@ export default {
};
</script>
<template>
- <div class="release-block-milestone-info d-flex align-items-start flex-wrap">
+ <div class="release-block-milestone-info gl-display-flex gl-flex-wrap">
<div
v-gl-tooltip
- class="milestone-progress-bar-container js-milestone-progress-bar-container d-flex flex-column align-items-start flex-shrink-1 mr-4 mb-3"
+ class="milestone-progress-bar-container js-milestone-progress-bar-container gl-display-flex gl-flex-direction-column gl-mr-6 gl-mb-5"
:title="__('Closed issues')"
>
- <span class="mb-2">{{ percentCompleteText }}</span>
- <span class="w-100">
+ <span class="gl-mb-3">{{ percentCompleteText }}</span>
+ <span class="gl-w-full">
<gl-progress-bar :value="closedIssuesCount" :max="totalIssuesCount" variant="success" />
</span>
</div>
- <div class="d-flex flex-column align-items-start mr-4 mb-3 js-milestone-list-container">
- <span class="mb-1">{{ milestoneLabelText }}</span>
- <div class="d-flex flex-wrap align-items-end">
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-mr-6 gl-mb-5 js-milestone-list-container"
+ >
+ <span class="gl-mb-2">{{ milestoneLabelText }}</span>
+ <div class="gl-display-flex gl-flex-wrap gl-align-items-flex-end">
<template v-for="(milestone, index) in milestonesToDisplay">
<gl-link
:key="milestone.id"
@@ -141,32 +127,12 @@ export default {
</template>
</div>
</div>
- <div class="d-flex flex-column align-items-start flex-shrink-0 mr-4 mb-3 js-issues-container">
- <span class="mb-1">
- {{ __('Issues') }}
- <gl-badge variant="muted" size="sm">{{ totalIssuesCount }}</gl-badge>
- </span>
- <div class="d-flex">
- <gl-link v-if="openIssuesPath" ref="openIssuesLink" :href="openIssuesPath">
- <gl-sprintf :message="__('Open: %{openIssuesCount}')">
- <template #openIssuesCount>{{ openIssuesCount }}</template>
- </gl-sprintf>
- </gl-link>
- <span v-else ref="openIssuesText">
- {{ sprintf(__('Open: %{openIssuesCount}'), { openIssuesCount }) }}
- </span>
-
- <span class="mx-1">&bull;</span>
-
- <gl-link v-if="closedIssuesPath" ref="closedIssuesLink" :href="closedIssuesPath">
- <gl-sprintf :message="__('Closed: %{closedIssuesCount}')">
- <template #closedIssuesCount>{{ closedIssuesCount }}</template>
- </gl-sprintf>
- </gl-link>
- <span v-else ref="closedIssuesText">
- {{ sprintf(__('Closed: %{closedIssuesCount}'), { closedIssuesCount }) }}
- </span>
- </div>
- </div>
+ <issuable-stats
+ :label="__('Issues')"
+ :total="totalIssuesCount"
+ :closed="closedIssuesCount"
+ :open-path="openIssuesPath"
+ :closed-path="closedIssuesPath"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
new file mode 100644
index 00000000000..eff26729fa7
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -0,0 +1,157 @@
+<script>
+import { GlButton, GlLoadingIcon, GlIcon, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui';
+import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
+
+export const LOADING_STATES = {
+ collapsedLoading: 'collapsedLoading',
+ collapsedError: 'collapsedError',
+ expandedLoading: 'expandedLoading',
+};
+
+export default {
+ components: {
+ GlButton,
+ GlLoadingIcon,
+ GlIcon,
+ GlLink,
+ GlBadge,
+ SmartVirtualList,
+ StatusIcon,
+ },
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
+ data() {
+ return {
+ loadingState: LOADING_STATES.collapsedLoading,
+ collapsedData: null,
+ fullData: null,
+ isCollapsed: true,
+ };
+ },
+ computed: {
+ isLoadingSummary() {
+ return this.loadingState === LOADING_STATES.collapsedLoading;
+ },
+ isLoadingExpanded() {
+ return this.loadingState === LOADING_STATES.expandedLoading;
+ },
+ isCollapsible() {
+ if (this.isLoadingSummary) {
+ return false;
+ }
+
+ return true;
+ },
+ statusIconName() {
+ if (this.isLoadingSummary) {
+ return 'loading';
+ }
+
+ if (this.loadingState === LOADING_STATES.collapsedError) {
+ return 'warning';
+ }
+
+ return this.statusIcon(this.collapsedData);
+ },
+ },
+ watch: {
+ isCollapsed(newVal) {
+ if (!newVal) {
+ this.loadAllData();
+ } else {
+ this.loadingState = null;
+ }
+ },
+ },
+ mounted() {
+ this.fetchCollapsedData(this.$props)
+ .then(data => {
+ this.collapsedData = data;
+ this.loadingState = null;
+ })
+ .catch(e => {
+ this.loadingState = LOADING_STATES.collapsedError;
+ throw e;
+ });
+ },
+ methods: {
+ toggleCollapsed() {
+ this.isCollapsed = !this.isCollapsed;
+ },
+ loadAllData() {
+ if (this.fullData) return;
+
+ this.loadingState = LOADING_STATES.expandedLoading;
+
+ this.fetchFullData(this.$props)
+ .then(data => {
+ this.loadingState = null;
+ this.fullData = data;
+ })
+ .catch(e => {
+ this.loadingState = null;
+ throw e;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <section class="media-section mr-widget-border-top">
+ <div class="media gl-p-5">
+ <status-icon :status="statusIconName" class="align-self-center" />
+ <div class="media-body d-flex flex-align-self-center align-items-center">
+ <div class="code-text">
+ <template v-if="isLoadingSummary">
+ {{ __('Loading...') }}
+ </template>
+ <div v-else v-safe-html="summary(collapsedData)"></div>
+ </div>
+ <gl-button
+ v-if="isCollapsible"
+ size="small"
+ class="float-right align-self-center"
+ @click="toggleCollapsed"
+ >
+ {{ isCollapsed ? __('Expand') : __('Collapse') }}
+ </gl-button>
+ </div>
+ </div>
+ <div v-if="!isCollapsed" class="mr-widget-grouped-section">
+ <div v-if="isLoadingExpanded" class="report-block-container">
+ <gl-loading-icon inline /> {{ __('Loading...') }}
+ </div>
+ <smart-virtual-list
+ v-else-if="fullData"
+ :length="fullData.length"
+ :remain="20"
+ :size="32"
+ wtag="ul"
+ wclass="report-block-list"
+ class="report-block-container"
+ >
+ <li v-for="data in fullData" :key="data.id" class="d-flex align-items-center">
+ <div v-if="data.icon" :class="data.icon.class" class="d-flex">
+ <gl-icon :name="data.icon.name" :size="24" />
+ </div>
+ <div
+ class="gl-mt-2 gl-mb-2 align-content-around align-items-start flex-wrap align-self-center d-flex"
+ >
+ <div class="gl-mr-4">
+ {{ data.text }}
+ </div>
+ <div v-if="data.link">
+ <gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
+ </div>
+ <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
+ {{ data.badge.text }}
+ </gl-badge>
+ </div>
+ </li>
+ </smart-virtual-list>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js
new file mode 100644
index 00000000000..5014c12dc30
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js
@@ -0,0 +1,27 @@
+import { extensions } from './index';
+
+export default {
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ render(h) {
+ return h(
+ 'div',
+ {},
+ extensions.map(extension =>
+ h(extension, {
+ props: extensions[0].props.reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: this.mr[key],
+ }),
+ {},
+ ),
+ }),
+ ),
+ );
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
new file mode 100644
index 00000000000..2bfaec8a1c9
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
@@ -0,0 +1,30 @@
+import ExtensionBase from './base.vue';
+
+// Holds all the currently registered extensions
+export const extensions = [];
+
+export const registerExtension = extension => {
+ // Pushes into the extenions array a dynamically created Vue component
+ // that gets exteneded from `base.vue`
+ extensions.push({
+ extends: ExtensionBase,
+ name: extension.name,
+ props: extension.props,
+ computed: {
+ ...Object.keys(extension.computed).reduce(
+ (acc, computedKey) => ({
+ ...acc,
+ // Making the computed property a method allows us to pass in arguments
+ // this allows for each computed property to recieve some data
+ [computedKey]() {
+ return extension.computed[computedKey];
+ },
+ }),
+ {},
+ ),
+ },
+ methods: {
+ ...extension.methods,
+ },
+ });
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
new file mode 100644
index 00000000000..2d21ced1b28
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
@@ -0,0 +1,66 @@
+/* eslint-disable */
+import issuesCollapsedQuery from './issues_collapsed.query.graphql';
+import issuesQuery from './issues.query.graphql';
+
+export default {
+ // Give the extension a name
+ // Make it easier to track in Vue dev tools
+ name: 'WidgetIssues',
+ // Add an array of props
+ // These then get mapped to values stored in the MR Widget store
+ props: ['targetProjectFullPath'],
+ // Add any extra computed props in here
+ computed: {
+ // Small summary text to be displayed in the collapsed state
+ // Receives the collapsed data as an argument
+ summary(count) {
+ return `<strong>${count}</strong> open issue`;
+ },
+ // Status icon to be used next to the summary text
+ // Receives the collapsed data as an argument
+ statusIcon(count) {
+ return count > 0 ? 'warning' : 'success';
+ },
+ },
+ methods: {
+ // Fetches the collapsed data
+ // Ideally, this request should return the smallest amount of data possible
+ // Receives an object of all the props passed in to the extension
+ fetchCollapsedData({ targetProjectFullPath }) {
+ return this.$apollo
+ .query({ query: issuesCollapsedQuery, variables: { projectPath: targetProjectFullPath } })
+ .then(({ data }) => data.project.issues.count);
+ },
+ // Fetches the full data when the extension is expanded
+ // Receives an object of all the props passed in to the extension
+ fetchFullData({ targetProjectFullPath }) {
+ return this.$apollo
+ .query({ query: issuesQuery, variables: { projectPath: targetProjectFullPath } })
+ .then(({ data }) => {
+ // Return some transformed data to be rendered in the expanded state
+ return data.project.issues.nodes.map(issue => ({
+ id: issue.id, // Required: The ID of the object
+ text: issue.title, // Required: The text to get used on each row
+ // Icon to get rendered on the side of each row
+ icon: {
+ // Required: Name maps to an icon in GitLabs SVG
+ name:
+ issue.state === 'closed' ? 'status_failed_borderless' : 'status_success_borderless',
+ // Optional: An extra class to be added to the icon for additional styling
+ class: issue.state === 'closed' ? 'text-danger' : 'text-success',
+ },
+ // Badges get rendered next to the text on each row
+ badge: issue.state === 'closed' && {
+ text: 'Closed', // Required: Text to be used inside of the badge
+ // variant: 'info', // Optional: The variant of the badge, maps to GitLab UI variants
+ },
+ // Each row can have its own link that will take the user elsewhere
+ // link: {
+ // href: 'https://google.com', // Required: href for the link
+ // text: 'Link text', // Required: Text to be used inside the link
+ // },
+ }));
+ });
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.query.graphql b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.query.graphql
new file mode 100644
index 00000000000..690f571c083
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.query.graphql
@@ -0,0 +1,13 @@
+query getAllIssues($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ issues {
+ nodes {
+ id
+ title
+ webPath
+ webUrl
+ state
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql b/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql
new file mode 100644
index 00000000000..389a81e0a61
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues_collapsed.query.graphql
@@ -0,0 +1,7 @@
+query getIssues($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ issues {
+ count
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 87e56dfcbdf..72d4e7063ad 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -3,6 +3,8 @@ import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_optio
import VueApollo from 'vue-apollo';
import Translate from '../vue_shared/translate';
import createDefaultClient from '~/lib/graphql';
+import { registerExtension } from './components/extensions';
+import issueExtension from './extensions/issues';
Vue.use(Translate);
Vue.use(VueApollo);
@@ -17,6 +19,8 @@ export default () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
gl.mrWidgetData.defaultAvatarUrl = gon.default_avatar_url;
+ registerExtension(issueExtension);
+
const vm = new Vue({ ...MrWidgetOptions, apolloProvider });
window.gl.mrWidget = {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index f1eeb331b9b..190d790f584 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -37,6 +37,7 @@ import FailedToMerge from './components/states/mr_widget_failed_to_merge.vue';
import MrWidgetAutoMergeEnabled from './components/states/mr_widget_auto_merge_enabled.vue';
import AutoMergeFailed from './components/states/mr_widget_auto_merge_failed.vue';
import CheckingState from './components/states/mr_widget_checking.vue';
+// import ExtensionsContainer from './components/extensions/container';
import eventHub from './event_hub';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
@@ -57,6 +58,7 @@ export default {
},
components: {
Loading,
+ // ExtensionsContainer,
'mr-widget-header': WidgetHeader,
'mr-widget-suggest-pipeline': WidgetSuggestPipeline,
'mr-widget-merge-help': WidgetMergeHelp,
@@ -454,6 +456,7 @@ export default {
:service="service"
/>
<div class="mr-section-container mr-widget-workflow">
+ <!-- <extensions-container :mr="mr" /> -->
<grouped-codequality-reports-app
v-if="shouldRenderCodeQuality"
:base-path="mr.codeclimate.base_path"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
index c2ebf78d541..973cc314ee3 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -1,11 +1,10 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
export default {
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
@@ -45,12 +44,9 @@ export default {
<template>
<div
- v-tooltip
+ v-gl-tooltip.left.viewport
:title="labelsList"
class="sidebar-collapsed-icon"
- data-placement="left"
- data-container="body"
- data-boundary="viewport"
@click="handleClick"
>
<gl-icon name="labels" />
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index ee4f74882a1..1647bf9c7ce 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -43,12 +43,6 @@
}
}
-.ldap-group-links {
- .form-actions {
- margin-bottom: $gl-padding;
- }
-}
-
.save-group-loader {
margin-top: $gl-padding-50;
margin-bottom: $gl-padding-50;
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 719c351242c..ecedbfb2a4f 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -32,4 +32,8 @@ module TimeHelper
"%02d:%02d:%02d" % [hours, minutes, seconds]
end
end
+
+ def time_in_milliseconds
+ (Time.now.to_f * 1000).to_i
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 563450159b5..692971f4627 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -75,11 +75,27 @@ module TreeHelper
if user_access(project).can_push_to_branch?(ref)
ref
else
- project = tree_edit_project(project)
- project.repository.next_branch('patch')
+ patch_branch_name(ref)
end
end
+ # Generate a patch branch name that should look like:
+ # `username-branchname-patch-epoch`
+ # where `epoch` is the last 5 digits of the time since epoch (in
+ # milliseconds)
+ #
+ # Note: this correlates with how the WebIDE formats the branch name
+ # and if this implementation changes, so should the `placeholderBranchName`
+ # definition in app/assets/javascripts/ide/stores/modules/commit/getters.js
+ def patch_branch_name(ref)
+ return unless current_user
+
+ username = current_user.username
+ epoch = time_in_milliseconds.to_s.last(5)
+
+ "#{username}-#{ref}-patch-#{epoch}"
+ end
+
def tree_edit_project(project = @project)
if can?(current_user, :push_code, project)
project
diff --git a/app/models/csv_issue_import.rb b/app/models/issues/csv_import.rb
index 01f83c983d7..d141f126ec9 100644
--- a/app/models/csv_issue_import.rb
+++ b/app/models/issues/csv_import.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-class CsvIssueImport < ApplicationRecord
+class Issues::CsvImport < ApplicationRecord
+ self.table_name = 'csv_issue_imports'
+
belongs_to :project, optional: false
belongs_to :user, optional: false
end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 1c93073025d..580a348b408 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -57,6 +57,8 @@ class BasePolicy < DeclarativePolicy::Base
rule { default }.enable :read_cross_project
condition(:is_gitlab_com) { ::Gitlab.dev_env_or_com? }
+
+ rule { admin }.enable :change_repository_storage
end
BasePolicy.prepend_if_ee('EE::BasePolicy')
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 59e2d617bf7..da857aa2625 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -546,8 +546,6 @@ class ProjectPolicy < BasePolicy
prevent :create_pipeline
end
- rule { admin }.enable :change_repository_storage
-
rule { can?(:read_issue) }.policy do
enable :read_design
enable :read_design_activity
diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
index 2725a3aeaa5..5791b8ffcae 100644
--- a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
+++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
@@ -69,7 +69,13 @@ module Clusters
def create_role_or_cluster_role_binding
if namespace_creator
- kubeclient.create_or_update_role_binding(role_binding_resource)
+ begin
+ kubeclient.delete_role_binding(role_binding_name, service_account_namespace)
+ rescue Kubeclient::ResourceNotFoundError
+ # Do nothing as we will create new role binding below
+ end
+
+ kubeclient.update_role_binding(role_binding_resource)
else
kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource)
end
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
index e71d098950e..16af0c88279 100644
--- a/app/services/issues/import_csv_service.rb
+++ b/app/services/issues/import_csv_service.rb
@@ -20,7 +20,7 @@ module Issues
private
def record_import_attempt
- CsvIssueImport.create!(user: @user, project: @project)
+ Issues::CsvImport.create!(user: @user, project: @project)
end
def process_csv
diff --git a/app/services/packages/create_event_service.rb b/app/services/packages/create_event_service.rb
index d009cba2812..56480724031 100644
--- a/app/services/packages/create_event_service.rb
+++ b/app/services/packages/create_event_service.rb
@@ -3,6 +3,8 @@
module Packages
class CreateEventService < BaseService
def execute
+ return unless Feature.enabled?(:collect_package_events, default_enabled: false)
+
event_scope = scope.is_a?(::Packages::Package) ? scope.package_type : scope
::Packages::Event.create!(
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index debbe95d2aa..804d2da2c4b 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -5,7 +5,7 @@
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
- if labels_or_filters
- #promote-label-modal
+ #js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.gl-mt-2
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index ad485c08bac..357d4d193df 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -5,7 +5,7 @@
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
- if labels_or_filters
- #promote-label-modal
+ #js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.gl-mt-3
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 1dadb4384b9..4b09e8de896 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -34,10 +34,7 @@
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
- group_name: label.project.group.name,
- target: '#promote-label-modal',
- container: 'body',
- toggle: 'modal' } }
+ group_name: label.project.group.name } }
= _('Promote to group label')
- if can?(current_user, :admin_label, label)
%li
diff --git a/changelogs/unreleased/205578-feature-flag-pkg-events.yml b/changelogs/unreleased/205578-feature-flag-pkg-events.yml
new file mode 100644
index 00000000000..ce42a88d2b5
--- /dev/null
+++ b/changelogs/unreleased/205578-feature-flag-pkg-events.yml
@@ -0,0 +1,5 @@
+---
+title: Adds feature flag to disable package events
+merge_request: 45802
+author:
+type: changed
diff --git a/changelogs/unreleased/229703-aqualls-promote-label-modal.yml b/changelogs/unreleased/229703-aqualls-promote-label-modal.yml
new file mode 100644
index 00000000000..8d3557fa553
--- /dev/null
+++ b/changelogs/unreleased/229703-aqualls-promote-label-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate DeprecatedModal to GitLab UI Modal for promoted labels
+merge_request: 46047
+author:
+type: changed
diff --git a/changelogs/unreleased/tancnle-fix-jupyter-notebook-overflow.yml b/changelogs/unreleased/tancnle-fix-jupyter-notebook-overflow.yml
new file mode 100644
index 00000000000..bdb2f45a9a5
--- /dev/null
+++ b/changelogs/unreleased/tancnle-fix-jupyter-notebook-overflow.yml
@@ -0,0 +1,5 @@
+---
+title: Fix wide content overflow on Notebook output
+merge_request: 45971
+author:
+type: fixed
diff --git a/changelogs/unreleased/vij-fix-inconsistent-branch-names.yml b/changelogs/unreleased/vij-fix-inconsistent-branch-names.yml
new file mode 100644
index 00000000000..b15a11d8c77
--- /dev/null
+++ b/changelogs/unreleased/vij-fix-inconsistent-branch-names.yml
@@ -0,0 +1,5 @@
+---
+title: Fix single file editor patch branch name
+merge_request: 46044
+author:
+type: fixed
diff --git a/config/feature_flags/development/collect_package_events.yml b/config/feature_flags/development/collect_package_events.yml
new file mode 100644
index 00000000000..65b88f84d86
--- /dev/null
+++ b/config/feature_flags/development/collect_package_events.yml
@@ -0,0 +1,7 @@
+---
+name: collect_package_events
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45802
+rollout_issue_url:
+group: group::package
+type: development
+default_enabled: false
diff --git a/config/feature_flags/development/saml_group_links.yml b/config/feature_flags/development/saml_group_links.yml
new file mode 100644
index 00000000000..84c3c73882f
--- /dev/null
+++ b/config/feature_flags/development/saml_group_links.yml
@@ -0,0 +1,7 @@
+---
+name: saml_group_links
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45080
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267020
+type: development
+group: group::access
+default_enabled: false
diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/new_fe_guide/modules/index.md
index a7820442df0..1752be6edcd 100644
--- a/doc/development/new_fe_guide/modules/index.md
+++ b/doc/development/new_fe_guide/modules/index.md
@@ -3,3 +3,7 @@
- [DirtySubmit](dirty_submit.md)
Disable form submits until there are unsaved changes.
+
+- [Merge Request widget extensions](widget_extensions.md)
+
+ Easily add extensions into the merge request widget
diff --git a/doc/development/new_fe_guide/modules/widget_extensions.md b/doc/development/new_fe_guide/modules/widget_extensions.md
new file mode 100644
index 00000000000..70d15743207
--- /dev/null
+++ b/doc/development/new_fe_guide/modules/widget_extensions.md
@@ -0,0 +1,50 @@
+# Merge request widget extensions
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44616) in GitLab 13.6.
+
+## Summary
+
+Extensions in the merge request widget allow for others team to quickly and easily add new features
+into the widget that will match the existing design and interaction as other extensions.
+
+## Usage
+
+To use extensions you need to first create a new extension object that will be used to fetch the
+data that will be rendered in the extension. See the example file in
+app/assets/javascripts/vue_merge_request_widget/extensions/issues.js for a working example.
+
+The basic object structure is as below:
+
+```javascript
+export default {
+ name: '',
+ props: [],
+ computed: {
+ summary() {},
+ statusIcon() {},
+ },
+ methods: {
+ fetchCollapsedData() {},
+ fetchFullData() {},
+ },
+};
+```
+
+Following the same data structure allows each extension to follow the same registering structure
+but allows for each extension to manage where it gets its own data from.
+
+After creating this structure you need to register it. Registering the extension can happen at any
+point _after_ the widget has been created.
+
+To register a extension the following can be done:
+
+```javascript
+// Import the register method
+import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
+
+// Import the new extension
+import issueExtension from '~/vue_merge_request_widget/extensions/issues';
+
+// Register the imported extension
+registerExtension(issueExtension);
+```
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 13cd6dcad3f..8ec61d62279 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -61,18 +61,11 @@ module Gitlab
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
# group client
delegate :update_cluster_role_binding,
- to: :rbac_client
-
- # RBAC methods delegates to the apis/rbac.authorization.k8s.io api
- # group client
- delegate :create_role,
- :get_role,
- :update_role,
- to: :rbac_client
-
- # RBAC methods delegates to the apis/rbac.authorization.k8s.io api
- # group client
- delegate :update_role_binding,
+ :create_role,
+ :get_role,
+ :update_role,
+ :delete_role_binding,
+ :update_role_binding,
to: :rbac_client
# non-entity methods that can only work with the core client
@@ -186,6 +179,7 @@ module Gitlab
update_cluster_role_binding(resource)
end
+ # Note that we cannot update roleRef as that is immutable
def create_or_update_role_binding(resource)
update_role_binding(resource)
end
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index 3f9fd1b1a19..e60651300eb 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -36,7 +36,7 @@ module Gitlab
}
end
- @client = Client.new(credentials[:user], opts)
+ @client = Client.new(credentials[:user], **opts)
end
def execute
diff --git a/lib/gitlab/robots_txt/parser.rb b/lib/gitlab/robots_txt/parser.rb
index b9a3837e468..604d2f9b35b 100644
--- a/lib/gitlab/robots_txt/parser.rb
+++ b/lib/gitlab/robots_txt/parser.rb
@@ -3,34 +3,68 @@
module Gitlab
module RobotsTxt
class Parser
- attr_reader :disallow_rules
+ DISALLOW_REGEX = /^disallow: /i.freeze
+ ALLOW_REGEX = /^allow: /i.freeze
+
+ attr_reader :disallow_rules, :allow_rules
def initialize(content)
@raw_content = content
- @disallow_rules = parse_raw_content!
+ @disallow_rules, @allow_rules = parse_raw_content!
end
def disallowed?(path)
+ return false if allow_rules.any? { |rule| path =~ rule }
+
disallow_rules.any? { |rule| path =~ rule }
end
private
- # This parser is very basic as it only knows about `Disallow:` lines,
- # and simply ignores all other lines.
+ # This parser is very basic as it only knows about `Disallow:`
+ # and `Allow:` lines, and simply ignores all other lines.
#
- # Order of predecence, 'Allow:`, etc are ignored for now.
+ # Patterns ending in `$`, and `*` for 0 or more characters are recognized.
+ #
+ # It is case insensitive and `Allow` rules takes precedence
+ # over `Disallow`.
def parse_raw_content!
- @raw_content.each_line.map do |line|
- if line.start_with?('Disallow:')
- value = line.sub('Disallow:', '').strip
- value = Regexp.escape(value).gsub('\*', '.*')
- Regexp.new("^#{value}")
- else
- nil
+ disallowed = []
+ allowed = []
+
+ @raw_content.each_line.each do |line|
+ if disallow_rule?(line)
+ disallowed << get_disallow_pattern(line)
+ elsif allow_rule?(line)
+ allowed << get_allow_pattern(line)
end
- end.compact
+ end
+
+ [disallowed, allowed]
+ end
+
+ def disallow_rule?(line)
+ line =~ DISALLOW_REGEX
+ end
+
+ def get_disallow_pattern(line)
+ get_pattern(line, DISALLOW_REGEX)
+ end
+
+ def allow_rule?(line)
+ line =~ ALLOW_REGEX
+ end
+
+ def get_allow_pattern(line)
+ get_pattern(line, ALLOW_REGEX)
+ end
+
+ def get_pattern(line, rule_regex)
+ value = line.sub(rule_regex, '').strip
+ value = Regexp.escape(value).gsub('\*', '.*')
+ value = value.sub(/\\\$$/, '$')
+ Regexp.new("^#{value}")
end
end
end
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 4df6a50c8dd..259d3e300b6 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -99,6 +99,7 @@ module Gitlab
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
config[:gitlab] = { url: Gitlab.config.gitlab.url }
+ config[:logging] = { dir: Rails.root.join('log').to_s }
TomlRB.dump(config)
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 9213b5ebab2..d3475a67711 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -65,8 +65,8 @@ module Gitlab
protected_uri_with_hostname
end
- def blocked_url?(*args)
- validate!(*args)
+ def blocked_url?(url, **kwargs)
+ validate!(url, **kwargs)
false
rescue BlockedUrlError
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 030d50815a6..75a661d4c81 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -602,7 +602,7 @@ module Gitlab
jira: distinct_count(::JiraImportState.where(time_period), :user_id),
fogbugz: projects_imported_count('fogbugz', time_period),
phabricator: projects_imported_count('phabricator', time_period),
- csv: distinct_count(CsvIssueImport.where(time_period), :user_id)
+ csv: distinct_count(Issues::CsvImport.where(time_period), :user_id)
},
groups_imported: distinct_count(::GroupImportState.where(time_period), :user_id)
}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 26455f73a33..a9027e56ff8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5477,7 +5477,7 @@ msgstr ""
msgid "Closed this %{quick_action_target}."
msgstr ""
-msgid "Closed: %{closedIssuesCount}"
+msgid "Closed: %{closed}"
msgstr ""
msgid "Closes this %{quick_action_target}."
@@ -12912,6 +12912,12 @@ msgstr ""
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
msgstr ""
+msgid "GroupSAML|Active SAML Group Links (%{count})"
+msgstr ""
+
+msgid "GroupSAML|Are you sure you want to remove the SAML group link?"
+msgstr ""
+
msgid "GroupSAML|Certificate fingerprint"
msgstr ""
@@ -12921,6 +12927,9 @@ msgstr ""
msgid "GroupSAML|Copy SAML Response XML"
msgstr ""
+msgid "GroupSAML|Could not create SAML group link: %{errors}."
+msgstr ""
+
msgid "GroupSAML|Default membership role"
msgstr ""
@@ -12966,12 +12975,30 @@ msgstr ""
msgid "GroupSAML|NameID Format"
msgstr ""
+msgid "GroupSAML|New SAML group link saved."
+msgstr ""
+
+msgid "GroupSAML|No active SAML group links"
+msgstr ""
+
msgid "GroupSAML|Prohibit outer forks"
msgstr ""
msgid "GroupSAML|Prohibit outer forks for this group."
msgstr ""
+msgid "GroupSAML|Role to assign members of this SAML group."
+msgstr ""
+
+msgid "GroupSAML|SAML Group Links"
+msgstr ""
+
+msgid "GroupSAML|SAML Group Name"
+msgstr ""
+
+msgid "GroupSAML|SAML Group Name: %{saml_group_name}"
+msgstr ""
+
msgid "GroupSAML|SAML Response Output"
msgstr ""
@@ -12984,6 +13011,9 @@ msgstr ""
msgid "GroupSAML|SAML Single Sign On Settings"
msgstr ""
+msgid "GroupSAML|SAML group link was successfully removed."
+msgstr ""
+
msgid "GroupSAML|SCIM API endpoint URL"
msgstr ""
@@ -12996,6 +13026,9 @@ msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgstr ""
+msgid "GroupSAML|The case-sensitive group name that will be sent by the SAML identity provider."
+msgstr ""
+
msgid "GroupSAML|This will be set as the access level of users added to the group."
msgstr ""
@@ -13020,6 +13053,9 @@ msgstr ""
msgid "GroupSAML|Your SCIM token"
msgstr ""
+msgid "GroupSAML|as %{access_level}"
+msgstr ""
+
msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
msgstr ""
@@ -16601,6 +16637,9 @@ msgstr ""
msgid "Merged this merge request."
msgstr ""
+msgid "Merged: %{merged}"
+msgstr ""
+
msgid "Merges this merge request immediately."
msgstr ""
@@ -18596,10 +18635,7 @@ msgstr ""
msgid "Open sidebar"
msgstr ""
-msgid "Open: %{openIssuesCount}"
-msgstr ""
-
-msgid "Open: %{open} • Closed: %{closed}"
+msgid "Open: %{open}"
msgstr ""
msgid "Opened"
diff --git a/public/robots.txt b/public/robots.txt
index f3fe51a25b0..c89d26b0e40 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -21,6 +21,7 @@ Disallow: /dashboard
Disallow: /users
Disallow: /help
Disallow: /s/
+Disallow: /-/profile
# Only specifically allow the Sign In page to avoid very ugly search results
Allow: /users/sign_in
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index fefb80a44cc..d4f66220f4d 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -6,14 +6,10 @@ RSpec.describe Projects::MergeRequestsController do
include ProjectForksHelper
include Gitlab::Routing
- let(:project) { create(:project, :repository) }
- let(:user) { project.owner }
+ let_it_be_with_refind(:project) { create(:project, :repository) }
+ let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) }
+ let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
- let(:merge_request_with_conflicts) do
- create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr|
- mr.mark_as_unmergeable
- end
- end
before do
sign_in(user)
@@ -107,7 +103,7 @@ RSpec.describe Projects::MergeRequestsController do
render_views
it 'renders merge request page' do
- merge_request.merge_request_diff.destroy
+ merge_request.merge_request_diff.destroy!
go(format: :html)
@@ -147,7 +143,7 @@ RSpec.describe Projects::MergeRequestsController do
let(:new_project) { create(:project) }
before do
- project.route.destroy
+ project.route.destroy!
new_project.redirect_routes.create!(path: project.full_path)
new_project.add_developer(user)
end
@@ -359,12 +355,11 @@ RSpec.describe Projects::MergeRequestsController do
end
context 'there is no source project' do
- let(:project) { create(:project, :repository) }
let(:forked_project) { fork_project_with_submodules(project) }
let!(:merge_request) { create(:merge_request, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
before do
- forked_project.destroy
+ forked_project.destroy!
end
it 'closes MR without errors' do
@@ -435,7 +430,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'when the merge request is not mergeable' do
before do
- merge_request.update(title: "WIP: #{merge_request.title}")
+ merge_request.update!(title: "WIP: #{merge_request.title}")
post :merge, params: base_params
end
@@ -475,7 +470,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'when squash is passed as 1' do
it 'updates the squash attribute on the MR to true' do
- merge_request.update(squash: false)
+ merge_request.update!(squash: false)
merge_with_sha(squash: '1')
expect(merge_request.reload.squash_on_merge?).to be_truthy
@@ -484,7 +479,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'when squash is passed as 0' do
it 'updates the squash attribute on the MR to false' do
- merge_request.update(squash: true)
+ merge_request.update!(squash: true)
merge_with_sha(squash: '0')
expect(merge_request.reload.squash_on_merge?).to be_falsey
@@ -547,7 +542,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'and head pipeline is not the current one' do
before do
- head_pipeline.update(sha: 'not_current_sha')
+ head_pipeline.update!(sha: 'not_current_sha')
end
it 'returns :failed' do
@@ -667,9 +662,9 @@ RSpec.describe Projects::MergeRequestsController do
end
context "when the user is owner" do
- let(:owner) { create(:user) }
- let(:namespace) { create(:namespace, owner: owner) }
- let(:project) { create(:project, :repository, namespace: namespace) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:namespace) { create(:namespace, owner: owner) }
+ let_it_be(:project) { create(:project, :repository, namespace: namespace) }
before do
sign_in owner
@@ -765,7 +760,7 @@ RSpec.describe Projects::MergeRequestsController do
end
context 'with private builds on a public project' do
- let(:project) { create(:project, :repository, :public, :builds_private) }
+ let(:project) { project_public_with_private_builds }
context 'for a project owner' do
it 'responds with serialized pipelines' do
@@ -813,7 +808,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'with public builds' do
let(:forked_project) do
fork_project(project, fork_user, repository: true).tap do |new_project|
- new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED)
+ new_project.project_feature.update!(builds_access_level: ProjectFeature::ENABLED)
end
end
@@ -855,7 +850,7 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'GET exposed_artifacts' do
- let(:merge_request) do
+ let_it_be(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
target_project: project,
@@ -993,7 +988,7 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'GET coverage_reports' do
- let(:merge_request) do
+ let_it_be(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
target_project: project,
@@ -1123,7 +1118,7 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'GET terraform_reports' do
- let(:merge_request) do
+ let_it_be(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
target_project: project,
@@ -1271,7 +1266,7 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'GET test_reports' do
- let(:merge_request) do
+ let_it_be(:merge_request) do
create(:merge_request,
:with_diffs,
:with_merge_request_pipeline,
@@ -1382,7 +1377,7 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'GET accessibility_reports' do
- let(:merge_request) do
+ let_it_be(:merge_request) do
create(:merge_request,
:with_diffs,
:with_merge_request_pipeline,
@@ -1419,7 +1414,7 @@ RSpec.describe Projects::MergeRequestsController do
end
context 'permissions on a public project with private CI/CD' do
- let(:project) { create(:project, :repository, :public, :builds_private) }
+ let(:project) { project_public_with_private_builds }
let(:accessibility_comparison) { { status: :parsed, data: { summary: 1 } } }
context 'while signed out' do
@@ -1505,7 +1500,7 @@ RSpec.describe Projects::MergeRequestsController do
describe 'POST remove_wip' do
before do
merge_request.title = merge_request.wip_title
- merge_request.save
+ merge_request.save!
post :remove_wip,
params: {
@@ -1626,7 +1621,7 @@ RSpec.describe Projects::MergeRequestsController do
it 'links to the environment on that project', :sidekiq_might_not_need_inline do
get_ci_environments_status
- expect(json_response.first['url']).to match /#{forked.full_path}/
+ expect(json_response.first['url']).to match(/#{forked.full_path}/)
end
context "when environment_target is 'merge_commit'", :sidekiq_might_not_need_inline do
@@ -1653,7 +1648,7 @@ RSpec.describe Projects::MergeRequestsController do
get_ci_environments_status(environment_target: 'merge_commit')
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.first['url']).to match /#{project.full_path}/
+ expect(json_response.first['url']).to match(/#{project.full_path}/)
end
end
end
@@ -1773,7 +1768,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'with project member visibility on a public project' do
let(:user) { create(:user) }
- let(:project) { create(:project, :repository, :public, :builds_private) }
+ let(:project) { project_public_with_private_builds }
it 'returns pipeline data to project members' do
project.add_developer(user)
diff --git a/spec/factories/csv_issue_import.rb b/spec/factories/issues/csv_import.rb
index 7e6497248cf..94688cf6232 100644
--- a/spec/factories/csv_issue_import.rb
+++ b/spec/factories/issues/csv_import.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
- factory :csv_issue_import do
+ factory :issue_csv_import, class: 'Issues::CsvImport' do
project
user
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index c30c8dda852..3f1c10b3688 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -179,12 +179,14 @@ RSpec.describe 'Editing file blob', :js do
end
context 'with protected branch' do
- before do
- visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
- end
-
it 'shows blob editor with patch branch' do
- expect(find('.js-branch-name').value).to eq('patch-1')
+ freeze_time do
+ visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
+
+ epoch = Time.now.strftime('%s%L').last(5)
+
+ expect(find('.js-branch-name').value).to eq "#{user.username}-protected-branch-patch-#{epoch}"
+ end
end
end
end
diff --git a/spec/frontend/pages/labels/components/promote_label_modal_spec.js b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
index 1fa12cf1365..f969808d78b 100644
--- a/spec/frontend/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
@@ -32,10 +32,9 @@ describe('Promote label modal', () => {
});
it('contains a label span with the color', () => {
- const labelFromTitle = vm.$el.querySelector('.modal-header .label.color-label');
-
- expect(labelFromTitle.style.backgroundColor).not.toBe(null);
- expect(labelFromTitle.textContent).toContain(vm.labelTitle);
+ expect(vm.labelColor).not.toBe(null);
+ expect(vm.labelColor).toBe(labelMockData.labelColor);
+ expect(vm.labelTitle).toBe(labelMockData.labelTitle);
});
});
diff --git a/spec/frontend/releases/components/__snapshots__/issuable_stats_spec.js.snap b/spec/frontend/releases/components/__snapshots__/issuable_stats_spec.js.snap
new file mode 100644
index 00000000000..8cffa9c8d36
--- /dev/null
+++ b/spec/frontend/releases/components/__snapshots__/issuable_stats_spec.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`~/releases/components/issuable_stats.vue matches snapshot 1`] = `
+"<div class=\\"gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container\\"><span class=\\"gl-mb-2\\">
+ Items
+ <span class=\\"badge badge-muted badge-pill gl-badge sm\\"><!----> 10</span></span>
+ <div class=\\"gl-display-flex\\"><span data-testid=\\"open-stat\\" class=\\"gl-white-space-pre-wrap\\">Open: <a href=\\"path/to/open/items\\" class=\\"gl-link\\">1</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"merged-stat\\" class=\\"gl-white-space-pre-wrap\\">Merged: <a href=\\"path/to/merged/items\\" class=\\"gl-link\\">7</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"closed-stat\\" class=\\"gl-white-space-pre-wrap\\">Closed: <a href=\\"path/to/closed/items\\" class=\\"gl-link\\">2</a></span></div>
+</div>"
+`;
diff --git a/spec/frontend/releases/components/issuable_stats_spec.js b/spec/frontend/releases/components/issuable_stats_spec.js
new file mode 100644
index 00000000000..224ad1499af
--- /dev/null
+++ b/spec/frontend/releases/components/issuable_stats_spec.js
@@ -0,0 +1,114 @@
+import { GlLink } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { trimText } from 'helpers/text_helper';
+import IssuableStats from '~/releases/components/issuable_stats.vue';
+
+describe('~/releases/components/issuable_stats.vue', () => {
+ let wrapper;
+ let defaultProps;
+
+ const createComponent = propUpdates => {
+ wrapper = mount(IssuableStats, {
+ propsData: {
+ ...defaultProps,
+ ...propUpdates,
+ },
+ });
+ };
+
+ const findOpenStatLink = () => wrapper.find('[data-testid="open-stat"]').find(GlLink);
+ const findMergedStatLink = () => wrapper.find('[data-testid="merged-stat"]').find(GlLink);
+ const findClosedStatLink = () => wrapper.find('[data-testid="closed-stat"]').find(GlLink);
+
+ beforeEach(() => {
+ defaultProps = {
+ label: 'Items',
+ total: 10,
+ closed: 2,
+ merged: 7,
+ openPath: 'path/to/open/items',
+ closedPath: 'path/to/closed/items',
+ mergedPath: 'path/to/merged/items',
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('matches snapshot', () => {
+ createComponent();
+
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ describe('when only total and closed counts are provided', () => {
+ beforeEach(() => {
+ createComponent({ merged: undefined, mergedPath: undefined });
+ });
+
+ it('renders a label with the total count; also, the opened count and the closed count', () => {
+ expect(trimText(wrapper.text())).toMatchInterpolatedText('Items 10 Open: 8 • Closed: 2');
+ });
+ });
+
+ describe('when only total, merged, and closed counts are provided', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders a label with the total count; also, the opened count, the merged count, and the closed count', () => {
+ expect(wrapper.text()).toMatchInterpolatedText('Items 10 Open: 1 • Merged: 7 • Closed: 2');
+ });
+ });
+
+ describe('when path parameters are provided', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the "open" stat as a link', () => {
+ const link = findOpenStatLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(defaultProps.openPath);
+ });
+
+ it('renders the "merged" stat as a link', () => {
+ const link = findMergedStatLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(defaultProps.mergedPath);
+ });
+
+ it('renders the "closed" stat as a link', () => {
+ const link = findClosedStatLink();
+
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(defaultProps.closedPath);
+ });
+ });
+
+ describe('when path parameters are not provided', () => {
+ beforeEach(() => {
+ createComponent({
+ openPath: undefined,
+ closedPath: undefined,
+ mergedPath: undefined,
+ });
+ });
+
+ it('does not render the "open" stat as a link', () => {
+ expect(findOpenStatLink().exists()).toBe(false);
+ });
+
+ it('does not render the "merged" stat as a link', () => {
+ expect(findMergedStatLink().exists()).toBe(false);
+ });
+
+ it('does not render the "closed" stat as a link', () => {
+ expect(findClosedStatLink().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/releases/components/release_block_milestone_info_spec.js b/spec/frontend/releases/components/release_block_milestone_info_spec.js
index 45f4eaa01a9..a17a8b9059c 100644
--- a/spec/frontend/releases/components/release_block_milestone_info_spec.js
+++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js
@@ -187,67 +187,4 @@ describe('Release block milestone info', () => {
expectAllZeros();
});
-
- describe('Issue links', () => {
- const findOpenIssuesLink = () => wrapper.find({ ref: 'openIssuesLink' });
- const findOpenIssuesText = () => wrapper.find({ ref: 'openIssuesText' });
- const findClosedIssuesLink = () => wrapper.find({ ref: 'closedIssuesLink' });
- const findClosedIssuesText = () => wrapper.find({ ref: 'closedIssuesText' });
-
- describe('when openIssuePath is provided', () => {
- const openIssuesPath = '/path/to/open/issues';
-
- beforeEach(() => {
- return factory({ milestones, openIssuesPath });
- });
-
- it('renders the open issues as a link', () => {
- expect(findOpenIssuesLink().exists()).toBe(true);
- expect(findOpenIssuesText().exists()).toBe(false);
- });
-
- it('renders the open issues link with the correct href', () => {
- expect(findOpenIssuesLink().attributes().href).toBe(openIssuesPath);
- });
- });
-
- describe('when openIssuePath is not provided', () => {
- beforeEach(() => {
- return factory({ milestones });
- });
-
- it('renders the open issues as plain text', () => {
- expect(findOpenIssuesLink().exists()).toBe(false);
- expect(findOpenIssuesText().exists()).toBe(true);
- });
- });
-
- describe('when closedIssuePath is provided', () => {
- const closedIssuesPath = '/path/to/closed/issues';
-
- beforeEach(() => {
- return factory({ milestones, closedIssuesPath });
- });
-
- it('renders the closed issues as a link', () => {
- expect(findClosedIssuesLink().exists()).toBe(true);
- expect(findClosedIssuesText().exists()).toBe(false);
- });
-
- it('renders the closed issues link with the correct href', () => {
- expect(findClosedIssuesLink().attributes().href).toBe(closedIssuesPath);
- });
- });
-
- describe('when closedIssuePath is not provided', () => {
- beforeEach(() => {
- return factory({ milestones });
- });
-
- it('renders the closed issues as plain text', () => {
- expect(findClosedIssuesLink().exists()).toBe(false);
- expect(findClosedIssuesText().exists()).toBe(true);
- });
- });
- });
});
diff --git a/spec/frontend/vue_mr_widget/components/extensions/index_spec.js b/spec/frontend/vue_mr_widget/components/extensions/index_spec.js
new file mode 100644
index 00000000000..8f6fe3cd37a
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/extensions/index_spec.js
@@ -0,0 +1,31 @@
+import { registerExtension, extensions } from '~/vue_merge_request_widget/components/extensions';
+import ExtensionBase from '~/vue_merge_request_widget/components/extensions/base.vue';
+
+describe('MR widget extension registering', () => {
+ it('registers a extension', () => {
+ registerExtension({
+ name: 'Test',
+ props: ['helloWorld'],
+ computed: {
+ test() {},
+ },
+ methods: {
+ test() {},
+ },
+ });
+
+ expect(extensions[0]).toEqual(
+ expect.objectContaining({
+ extends: ExtensionBase,
+ name: 'Test',
+ props: ['helloWorld'],
+ computed: {
+ test: expect.any(Function),
+ },
+ methods: {
+ test: expect.any(Function),
+ },
+ }),
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
index 7847e0ee71d..71c040c6633 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -81,9 +81,7 @@ describe('DropdownValueCollapsedComponent', () => {
describe('template', () => {
it('renders component container element with tooltip`', () => {
- expect(vm.$el.dataset.placement).toBe('left');
- expect(vm.$el.dataset.container).toBe('body');
- expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList);
+ expect(vm.$el.title).toBe(vm.labelsList);
});
it('renders tags icon element', () => {
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 6663a5c81c8..3e406f5e74e 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -37,4 +37,14 @@ RSpec.describe TimeHelper do
it { expect(duration_in_numbers(duration)).to eq formatted_string }
end
end
+
+ describe "#time_in_milliseconds" do
+ it "returns the time in milliseconds" do
+ freeze_time do
+ time = (Time.now.to_f * 1000).to_i
+
+ expect(time_in_milliseconds).to eq time
+ end
+ end
+ end
end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index b5d356b985c..620bf248d7b 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe TreeHelper do
let(:repository) { project.repository }
let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' }
+ let_it_be(:user) { create(:user) }
+
def create_file(filename)
project.repository.create_file(
project.creator,
@@ -219,7 +221,6 @@ RSpec.describe TreeHelper do
context 'user does not have write access but a personal fork exists' do
include ProjectForksHelper
- let_it_be(:user) { create(:user) }
let(:forked_project) { create(:project, :repository, namespace: user.namespace) }
before do
@@ -277,8 +278,6 @@ RSpec.describe TreeHelper do
end
context 'user has write access' do
- let_it_be(:user) { create(:user) }
-
before do
project.add_developer(user)
@@ -314,8 +313,6 @@ RSpec.describe TreeHelper do
end
context 'gitpod feature is enabled' do
- let_it_be(:user) { create(:user) }
-
before do
stub_feature_flags(gitpod: true)
allow(Gitlab::CurrentSettings)
@@ -358,4 +355,28 @@ RSpec.describe TreeHelper do
end
end
end
+
+ describe '.patch_branch_name' do
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ subject { helper.patch_branch_name('master') }
+
+ it 'returns a patch branch name' do
+ freeze_time do
+ epoch = Time.now.strftime('%s%L').last(5)
+
+ expect(subject).to eq "#{user.username}-master-patch-#{epoch}"
+ end
+ end
+
+ context 'without a current_user' do
+ let(:user) { nil }
+
+ it 'returns nil' do
+ expect(subject).to be nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
index add554992f1..188c56ae81f 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_object_importer_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectImporter do
}
end
- let(:lfs_download_object) { LfsDownloadObject.new(lfs_attributes) }
+ let(:lfs_download_object) { LfsDownloadObject.new(**lfs_attributes) }
let(:github_lfs_object) { Gitlab::GithubImport::Representation::LfsObject.new(lfs_attributes) }
let(:importer) { described_class.new(github_lfs_object, project, nil) }
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
index 1f7b14661c2..6188ba8ec3f 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
}
end
- let(:lfs_download_object) { LfsDownloadObject.new(lfs_attributes) }
+ let(:lfs_download_object) { LfsDownloadObject.new(**lfs_attributes) }
describe '#parallel?' do
it 'returns true when running in parallel mode' do
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index c8cecb576da..2b602c80640 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::JiraImport do
let_it_be(:project, reload: true) { create(:project) }
let(:additional_params) { {} }
- subject { described_class.validate_project_settings!(project, additional_params) }
+ subject { described_class.validate_project_settings!(project, **additional_params) }
shared_examples 'raise Jira import error' do |message|
it 'returns error' do
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 7b6d143dda9..7d11cf35e76 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -302,6 +302,8 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
:create_role,
:get_role,
:update_role,
+ :delete_role_binding,
+ :update_role_binding,
:update_cluster_role_binding
].each do |method|
describe "##{method}" do
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 56d708a1e11..7baf826e71d 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -274,7 +274,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
allow(project).to receive(:import_data).and_return(double(credentials: credentials))
expect(Gitlab::LegacyGithubImport::Client).to receive(:new).with(
credentials[:user],
- {}
+ **{}
)
subject.client
diff --git a/spec/lib/gitlab/robots_txt/parser_spec.rb b/spec/lib/gitlab/robots_txt/parser_spec.rb
index bb88003ce20..f4e97e5e897 100644
--- a/spec/lib/gitlab/robots_txt/parser_spec.rb
+++ b/spec/lib/gitlab/robots_txt/parser_spec.rb
@@ -14,8 +14,13 @@ RSpec.describe Gitlab::RobotsTxt::Parser do
<<~TXT
User-Agent: *
Disallow: /autocomplete/users
- Disallow: /search
+ disallow: /search
Disallow: /api
+ Allow: /users
+ Disallow: /help
+ allow: /help
+ Disallow: /test$
+ Disallow: /ex$mple$
TXT
end
@@ -28,6 +33,12 @@ RSpec.describe Gitlab::RobotsTxt::Parser do
'/api/grapql' | true
'/api/index.html' | true
'/projects' | false
+ '/users' | false
+ '/help' | false
+ '/test' | true
+ '/testfoo' | false
+ '/ex$mple' | true
+ '/ex$mplefoo' | false
end
with_them do
@@ -47,6 +58,7 @@ RSpec.describe Gitlab::RobotsTxt::Parser do
Disallow: /*/*.git
Disallow: /*/archive/
Disallow: /*/repository/archive*
+ Allow: /*/repository/archive/foo
TXT
end
@@ -61,6 +73,7 @@ RSpec.describe Gitlab::RobotsTxt::Parser do
'/projects' | false
'/git' | false
'/projects/git' | false
+ '/project/repository/archive/foo' | false
end
with_them do
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index ada1feb2632..135d9757113 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -212,7 +212,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
jira_project = create(:project, creator_id: user.id)
create(:jira_import_state, :finished, project: jira_project)
- create(:csv_issue_import, user: user)
+ create(:issue_csv_import, user: user)
end
expect(described_class.usage_activity_by_stage_manage({})).to include(
diff --git a/spec/models/csv_issue_import_spec.rb b/spec/models/issues/csv_import_spec.rb
index 8109ad34d4a..2911a79e505 100644
--- a/spec/models/csv_issue_import_spec.rb
+++ b/spec/models/issues/csv_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe CsvIssueImport, type: :model do
+RSpec.describe Issues::CsvImport, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:project).required }
it { is_expected.to belong_to(:user).required }
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index 103f2e9bc39..226660dc955 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -22,6 +22,34 @@ RSpec.describe BasePolicy do
end
end
+ shared_examples 'admin only access' do |policy|
+ let(:current_user) { build_stubbed(:user) }
+
+ subject { described_class.new(current_user, nil) }
+
+ it { is_expected.not_to be_allowed(policy) }
+
+ context 'for admins' do
+ let(:current_user) { build_stubbed(:admin) }
+
+ it 'allowed when in admin mode' do
+ enable_admin_mode!(current_user)
+
+ is_expected.to be_allowed(policy)
+ end
+
+ it 'prevented when not in admin mode' do
+ is_expected.not_to be_allowed(policy)
+ end
+ end
+
+ context 'for anonymous' do
+ let(:current_user) { nil }
+
+ it { is_expected.not_to be_allowed(policy) }
+ end
+ end
+
describe 'read cross project' do
let(:current_user) { build_stubbed(:user) }
let(:user) { build_stubbed(:user) }
@@ -41,51 +69,15 @@ RSpec.describe BasePolicy do
enable_external_authorization_service_check
end
- it { is_expected.not_to be_allowed(:read_cross_project) }
-
- context 'for admins' do
- let(:current_user) { build_stubbed(:admin) }
-
- subject { described_class.new(current_user, nil) }
-
- it 'allowed when in admin mode' do
- enable_admin_mode!(current_user)
-
- is_expected.to be_allowed(:read_cross_project)
- end
-
- it 'prevented when not in admin mode' do
- is_expected.not_to be_allowed(:read_cross_project)
- end
- end
-
- context 'for anonymous' do
- let(:current_user) { nil }
-
- it { is_expected.not_to be_allowed(:read_cross_project) }
- end
+ it_behaves_like 'admin only access', :read_cross_project
end
end
describe 'full private access' do
- let(:current_user) { build_stubbed(:user) }
-
- subject { described_class.new(current_user, nil) }
-
- it { is_expected.not_to be_allowed(:read_all_resources) }
-
- context 'for admins' do
- let(:current_user) { build_stubbed(:admin) }
-
- it 'allowed when in admin mode' do
- enable_admin_mode!(current_user)
-
- is_expected.to be_allowed(:read_all_resources)
- end
+ it_behaves_like 'admin only access', :read_all_resources
+ end
- it 'prevented when not in admin mode' do
- is_expected.not_to be_allowed(:read_all_resources)
- end
- end
+ describe 'change_repository_storage' do
+ it_behaves_like 'admin only access', :change_repository_storage
end
end
diff --git a/spec/requests/robots_txt_spec.rb b/spec/requests/robots_txt_spec.rb
new file mode 100644
index 00000000000..524db5f442b
--- /dev/null
+++ b/spec/requests/robots_txt_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Robots.txt Requests', :aggregate_failures do
+ before do
+ Gitlab::Testing::RobotsBlockerMiddleware.block_requests!
+ end
+
+ after do
+ Gitlab::Testing::RobotsBlockerMiddleware.allow_requests!
+ end
+
+ it 'allows the requests' do
+ requests = [
+ '/users/sign_in'
+ ]
+
+ requests.each do |request|
+ get request
+
+ expect(response).not_to have_gitlab_http_status(:service_unavailable), "#{request} must be allowed"
+ end
+ end
+
+ it 'blocks the requests' do
+ requests = [
+ '/autocomplete/users',
+ '/search',
+ '/admin',
+ '/profile',
+ '/dashboard',
+ '/users',
+ '/users/foo',
+ '/help',
+ '/s/',
+ '/-/profile',
+ '/foo/bar/new',
+ '/foo/bar/edit',
+ '/foo/bar/raw',
+ '/groups/foo/analytics',
+ '/groups/foo/contribution_analytics',
+ '/groups/foo/group_members',
+ '/foo/bar/project.git',
+ '/foo/bar/archive/foo',
+ '/foo/bar/repository/archive',
+ '/foo/bar/activity',
+ '/foo/bar/blame',
+ '/foo/bar/commits',
+ '/foo/bar/commit',
+ '/foo/bar/compare',
+ '/foo/bar/network',
+ '/foo/bar/graphs',
+ '/foo/bar/merge_requests/1.patch',
+ '/foo/bar/merge_requests/1.diff',
+ '/foo/bar/merge_requests/1/diffs',
+ '/foo/bar/deploy_keys',
+ '/foo/bar/hooks',
+ '/foo/bar/services',
+ '/foo/bar/protected_branches',
+ '/foo/bar/uploads/foo',
+ '/foo/bar/project_members',
+ '/foo/bar/settings'
+ ]
+
+ requests.each do |request|
+ get request
+
+ expect(response).to have_gitlab_http_status(:service_unavailable), "#{request} must be disallowed"
+ end
+ end
+end
diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
index 7e3f1fdb379..90956e7b4ea 100644
--- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
@@ -28,6 +28,7 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute'
stub_kubeclient_get_secret_error(api_url, 'gitlab-token')
stub_kubeclient_create_secret(api_url)
+ stub_kubeclient_delete_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace)
diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
index 257e2e53733..3020ec2bf6f 100644
--- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
@@ -141,6 +141,7 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
before do
cluster.platform_kubernetes.rbac!
+ stub_kubeclient_delete_role_binding(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_put_role_binding(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
index b34f77697a3..3e18c5ad64a 100644
--- a/spec/services/issues/import_csv_service_spec.rb
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Issues::ImportCsvService do
shared_examples_for 'an issue importer' do
it 'records the import attempt' do
expect { subject }
- .to change { CsvIssueImport.where(project: project, user: user).count }
+ .to change { Issues::CsvImport.where(project: project, user: user).count }
.by 1
end
end
diff --git a/spec/services/packages/create_event_service_spec.rb b/spec/services/packages/create_event_service_spec.rb
index 7e66b430a8c..c2f1bd48bda 100644
--- a/spec/services/packages/create_event_service_spec.rb
+++ b/spec/services/packages/create_event_service_spec.rb
@@ -16,13 +16,29 @@ RSpec.describe Packages::CreateEventService do
describe '#execute' do
shared_examples 'package event creation' do |originator_type, expected_scope|
- it 'creates the event' do
- expect { subject }.to change { Packages::Event.count }.by(1)
+ context 'with feature flag disable' do
+ before do
+ stub_feature_flags(collect_package_events: false)
+ end
- expect(subject.originator_type).to eq(originator_type)
- expect(subject.originator).to eq(user&.id)
- expect(subject.event_scope).to eq(expected_scope)
- expect(subject.event_type).to eq(event_name)
+ it 'returns nil' do
+ expect(subject).to be nil
+ end
+ end
+
+ context 'with feature flag enabled' do
+ before do
+ stub_feature_flags(collect_package_events: true)
+ end
+
+ it 'creates the event' do
+ expect { subject }.to change { Packages::Event.count }.by(1)
+
+ expect(subject.originator_type).to eq(originator_type)
+ expect(subject.originator).to eq(user&.id)
+ expect(subject.event_scope).to eq(expected_scope)
+ expect(subject.event_type).to eq(event_name)
+ end
end
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 113bb31e4be..82692abd4cf 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -250,6 +250,11 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_delete_role_binding(api_url, name, namespace: 'default')
+ WebMock.stub_request(:delete, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
+ .to_return(kube_response({}))
+ end
+
def stub_kubeclient_put_role_binding(api_url, name, namespace: 'default')
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
.to_return(kube_response({}))
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
index ba31e8e6056..39433cf0fd0 100644
--- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -128,6 +128,10 @@ RSpec.shared_examples 'job token for package uploads' do
end
RSpec.shared_examples 'a package tracking event' do |category, action|
+ before do
+ stub_feature_flags(collect_package_events: true)
+ end
+
it "creates a gitlab tracking event #{action}" do
expect(Gitlab::Tracking).to receive(:event).with(category, action)