summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-30 21:09:29 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-30 21:09:29 +0000
commit89245154567c6ea01821606f3127bef766462d5e (patch)
tree2fa9adfe5c3bad9deadcbf80b07c0a1da821d060
parentf06ebebadece98495408c29112abf1b65a43ecb6 (diff)
downloadgitlab-ce-89245154567c6ea01821606f3127bef766462d5e.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml8
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/persistent_user_callouts.js1
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue24
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js8
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete.vue111
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue76
-rw-r--r--app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql3
-rw-r--r--app/assets/javascripts/runner/graphql/list/local_state.js63
-rw-r--r--app/assets/javascripts/runner/graphql/list/typedefs.graphql3
-rw-r--r--app/assets/javascripts/runner/utils.js3
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue8
-rw-r--r--app/assets/javascripts/security_configuration/index.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/help_popover.vue2
-rw-r--r--app/controllers/admin/runners_controller.rb3
-rw-r--r--app/helpers/projects/security/configuration_helper.rb4
-rw-r--r--app/helpers/users/group_callouts_helper.rb2
-rw-r--r--app/models/users/group_callout.rb3
-rw-r--r--app/presenters/issue_presenter.rb16
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/projects/empty.html.haml1
-rw-r--r--app/views/projects/no_repo.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml2
-rw-r--r--app/views/projects/security/configuration/show.html.haml1
-rw-r--r--app/views/projects/show.html.haml1
-rw-r--r--config/feature_flags/development/admin_runners_bulk_delete.yml8
-rw-r--r--data/removals/15_0/15-0-rerequest-review.yml16
-rw-r--r--doc/.vale/gitlab/Wordy.yml1
-rw-r--r--doc/install/installation.md8
-rw-r--r--doc/operations/incident_management/img/incident_list_v13_5.pngbin43685 -> 0 bytes
-rw-r--r--doc/operations/metrics/dashboards/default.md2
-rw-r--r--doc/operations/metrics/dashboards/develop.md2
-rw-r--r--doc/operations/metrics/dashboards/index.md2
-rw-r--r--doc/operations/metrics/dashboards/panel_types.md2
-rw-r--r--doc/operations/metrics/dashboards/settings.md2
-rw-r--r--doc/operations/metrics/dashboards/templating_variables.md2
-rw-r--r--doc/operations/metrics/dashboards/variables.md2
-rw-r--r--doc/operations/metrics/dashboards/yaml.md2
-rw-r--r--doc/update/removals.md6
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_12.pngbin36706 -> 0 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.pngbin21148 -> 0 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_path_nav_v13_11.pngbin22421 -> 0 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.pngbin79595 -> 0 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_time_metrics_v13_12.pngbin18354 -> 0 bytes
-rw-r--r--doc/user/permissions.md2
-rw-r--r--doc/user/project/deploy_keys/img/deploy_keys_v13_0.pngbin27295 -> 0 bytes
-rw-r--r--doc/user/project/integrations/prometheus_library/cloudwatch.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/index.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md2
-rw-r--r--doc/user/project/merge_requests/reviews/index.md8
-rw-r--r--locale/gitlab.pot41
-rw-r--r--qa/qa/tools/test_resources_handler.rb7
-rw-r--r--spec/factories/issues.rb5
-rw-r--r--spec/factories/work_items/work_item_types.rb5
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js4
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js64
-rw-r--r--spec/frontend/runner/components/runner_bulk_delete_spec.js103
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js31
-rw-r--r--spec/frontend/runner/graphql/local_state_spec.js72
-rw-r--r--spec/frontend/runner/utils_spec.js4
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js2
-rw-r--r--spec/helpers/projects/security/configuration_helper_spec.rb6
-rw-r--r--spec/presenters/issue_presenter_spec.rb61
-rw-r--r--spec/views/groups/show.html.haml_spec.rb118
71 files changed, 769 insertions, 192 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 5d4521b3f36..6e84d4f3914 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -293,14 +293,14 @@ coverage-frontend:
- *yarn-install
- run_timed_command "retry yarn run webpack-prod"
-qa-frontend-node:12:
- extends: .qa-frontend-node
- image: ${GITLAB_DEPENDENCY_PROXY}node:12
-
qa-frontend-node:14:
extends: .qa-frontend-node
image: ${GITLAB_DEPENDENCY_PROXY}node:14
+qa-frontend-node:16:
+ extends: .qa-frontend-node
+ image: ${GITLAB_DEPENDENCY_PROXY}node:16
+
qa-frontend-node:latest:
extends:
- .qa-frontend-node
diff --git a/Gemfile.lock b/Gemfile.lock
index c4b952d8216..7fc00cd0004 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1252,7 +1252,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sqlite3 (1.3.13)
+ sqlite3 (1.4.2)
ssh_data (1.2.0)
ssrf_filter (1.0.7)
stackprof (0.2.15)
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index f6de21ec0c5..dee832c01d5 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -12,6 +12,7 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-security-newsletter-callout',
'.js-approaching-seats-count-threshold',
'.js-storage-enforcement-banner',
+ '.js-user-over-limit-free-plan-alert',
];
const initCallouts = () => {
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index 9abd45424e7..3853a7d8666 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -4,9 +4,11 @@ import { createAlert } from '~/flash';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber } from '~/locale';
import { fetchPolicies } from '~/lib/graphql';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
+import RunnerBulkDelete from '../components/runner_bulk_delete.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
import RunnerStats from '../components/stat/runner_stats.vue';
@@ -53,6 +55,7 @@ export default {
GlLink,
RegistrationDropdown,
RunnerFilteredSearchBar,
+ RunnerBulkDelete,
RunnerList,
RunnerName,
RunnerStats,
@@ -60,6 +63,8 @@ export default {
RunnerTypeTabs,
RunnerActionsCell,
},
+ mixins: [glFeatureFlagMixin()],
+ inject: ['localMutations'],
props: {
registrationToken: {
type: String,
@@ -180,6 +185,11 @@ export default {
},
];
},
+ isBulkDeleteEnabled() {
+ // Feature flag: admin_runners_bulk_delete
+ // Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+ return this.glFeatures.adminRunnersBulkDelete;
+ },
},
watch: {
search: {
@@ -238,6 +248,12 @@ export default {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
+ onChecked({ runner, isChecked }) {
+ this.localMutations.setRunnerChecked({
+ runner,
+ isChecked,
+ });
+ },
},
filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE,
INSTANCE_TYPE,
@@ -286,7 +302,13 @@ export default {
{{ __('No runners found') }}
</div>
<template v-else>
- <runner-list :runners="runners.items" :loading="runnersLoading">
+ <runner-bulk-delete v-if="isBulkDeleteEnabled" />
+ <runner-list
+ :runners="runners.items"
+ :loading="runnersLoading"
+ :checkable="isBulkDeleteEnabled"
+ @checked="onChecked"
+ >
<template #runner-name="{ runner }">
<gl-link :href="runner.adminUrl">
<runner-name :runner="runner" />
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 3b8a8fe9cd1..2405bab7957 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -1,9 +1,10 @@
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
import { visitUrl } from '~/lib/utils/url_utility';
import { updateOutdatedUrl } from '~/runner/runner_search_utils';
+import createDefaultClient from '~/lib/graphql';
+import { createLocalState } from '../graphql/list/local_state';
import AdminRunnersApp from './admin_runners_app.vue';
Vue.use(GlToast);
@@ -27,8 +28,10 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
const { runnerInstallHelpPage, registrationToken } = el.dataset;
+ const { cacheConfig, typeDefs, localMutations } = createLocalState();
+
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { cacheConfig, typeDefs }),
});
return new Vue({
@@ -36,6 +39,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
apolloProvider,
provide: {
runnerInstallHelpPage,
+ localMutations,
},
render(h) {
return h(AdminRunnersApp, {
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
new file mode 100644
index 00000000000..50791de0bda
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlButton, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import { n__, sprintf } from '~/locale';
+import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlSprintf,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ inject: ['localMutations'],
+ data() {
+ return {
+ checkedRunnerIds: [],
+ };
+ },
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ },
+ },
+ computed: {
+ checkedCount() {
+ return this.checkedRunnerIds.length || 0;
+ },
+ bannerMessage() {
+ return sprintf(
+ n__(
+ 'Runners|%{strongStart}%{count}%{strongEnd} runner selected',
+ 'Runners|%{strongStart}%{count}%{strongEnd} runners selected',
+ this.checkedCount,
+ ),
+ {
+ count: this.checkedCount,
+ },
+ );
+ },
+ modalTitle() {
+ return n__('Runners|Delete %d runner', 'Runners|Delete %d runners', this.checkedCount);
+ },
+ modalHtmlMessage() {
+ return sprintf(
+ n__(
+ 'Runners|%{strongStart}%{count}%{strongEnd} runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?',
+ 'Runners|%{strongStart}%{count}%{strongEnd} runners will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?',
+ this.checkedCount,
+ ),
+ {
+ strongStart: '<strong>',
+ strongEnd: '</strong>',
+ count: this.checkedCount,
+ },
+ false,
+ );
+ },
+ primaryBtnText() {
+ return n__(
+ 'Runners|Permanently delete %d runner',
+ 'Runners|Permanently delete %d runners',
+ this.checkedCount,
+ );
+ },
+ },
+ methods: {
+ onClearChecked() {
+ this.localMutations.clearChecked();
+ },
+ onClickDelete: ignoreWhilePending(async function onClickDelete() {
+ const confirmed = await confirmAction(null, {
+ title: this.modalTitle,
+ modalHtmlMessage: this.modalHtmlMessage,
+ primaryBtnVariant: 'danger',
+ primaryBtnText: this.primaryBtnText,
+ });
+
+ if (confirmed) {
+ // TODO Call $apollo.mutate with list of runner
+ // ids in `this.checkedRunnerIds`.
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
+ }
+ }),
+ },
+};
+</script>
+
+<template>
+ <div v-if="checkedCount" class="gl-my-4 gl-p-4 gl-border-1 gl-border-solid gl-border-gray-100">
+ <div class="gl-display-flex gl-align-items-center">
+ <div>
+ <gl-sprintf :message="bannerMessage">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </div>
+ <div class="gl-ml-auto">
+ <gl-button data-testid="clear-btn" variant="default" @click="onClearChecked">{{
+ s__('Runners|Clear selection')
+ }}</gl-button>
+ <gl-button data-testid="delete-btn" variant="danger" @click="onClickDelete">{{
+ s__('Runners|Delete selected')
+ }}</gl-button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index 819b6951e13..05cd1879d9d 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -4,11 +4,22 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/toolt
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import { formatJobCount, tableField } from '../utils';
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
import RunnerTags from './runner_tags.vue';
+const defaultFields = [
+ tableField({ key: 'status', label: s__('Runners|Status') }),
+ tableField({ key: 'summary', label: s__('Runners|Runner'), thClasses: ['gl-lg-w-25p'] }),
+ tableField({ key: 'version', label: __('Version') }),
+ tableField({ key: 'jobCount', label: __('Jobs') }),
+ tableField({ key: 'tagList', label: __('Tags'), thClasses: ['gl-lg-w-25p'] }),
+ tableField({ key: 'contactedAt', label: __('Last contact') }),
+ tableField({ key: 'actions', label: '' }),
+];
+
export default {
components: {
GlTableLite,
@@ -22,7 +33,20 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ skip() {
+ return !this.checkable;
+ },
+ },
+ },
props: {
+ checkable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
loading: {
type: Boolean,
required: false,
@@ -33,6 +57,10 @@ export default {
required: true,
},
},
+ emits: ['checked'],
+ data() {
+ return { checkedRunnerIds: [] };
+ },
computed: {
tableClass() {
// <gl-table-lite> does not provide a busy state, add
@@ -42,6 +70,18 @@ export default {
'gl-opacity-6': this.loading,
};
},
+ fields() {
+ if (this.checkable) {
+ const checkboxField = tableField({
+ key: 'checkbox',
+ label: s__('Runners|Checkbox'),
+ thClasses: ['gl-w-9'],
+ tdClass: ['gl-text-center'],
+ });
+ return [checkboxField, ...defaultFields];
+ }
+ return defaultFields;
+ },
},
methods: {
formatJobCount(jobCount) {
@@ -55,16 +95,16 @@ export default {
}
return {};
},
+ onCheckboxChange(runner, isChecked) {
+ this.$emit('checked', {
+ runner,
+ isChecked,
+ });
+ },
+ isChecked(runner) {
+ return this.checkedRunnerIds.includes(runner.id);
+ },
},
- fields: [
- tableField({ key: 'status', label: s__('Runners|Status') }),
- tableField({ key: 'summary', label: s__('Runners|Runner'), thClasses: ['gl-lg-w-25p'] }),
- tableField({ key: 'version', label: __('Version') }),
- tableField({ key: 'jobCount', label: __('Jobs') }),
- tableField({ key: 'tagList', label: __('Tags'), thClasses: ['gl-lg-w-25p'] }),
- tableField({ key: 'contactedAt', label: __('Last contact') }),
- tableField({ key: 'actions', label: '' }),
- ],
};
</script>
<template>
@@ -73,13 +113,29 @@ export default {
:aria-busy="loading"
:class="tableClass"
:items="runners"
- :fields="$options.fields"
+ :fields="fields"
:tbody-tr-attr="runnerTrAttr"
data-testid="runner-list"
stacked="md"
primary-key="id"
fixed
>
+ <template #head(checkbox)>
+ <!--
+ Checkbox to select all to be added here
+ See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
+ -->
+ <span></span>
+ </template>
+
+ <template #cell(checkbox)="{ item }">
+ <input
+ type="checkbox"
+ :checked="isChecked(item)"
+ @change="onCheckboxChange(item, $event.target.checked)"
+ />
+ </template>
+
<template #cell(status)="{ item }">
<runner-status-cell :runner="item" />
</template>
diff --git a/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
new file mode 100644
index 00000000000..c01f1edb451
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
@@ -0,0 +1,3 @@
+query getCheckedRunnerIds {
+ checkedRunnerIds @client
+}
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
new file mode 100644
index 00000000000..e87bc72c86a
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -0,0 +1,63 @@
+import { makeVar } from '@apollo/client/core';
+import typeDefs from './typedefs.graphql';
+
+/**
+ * Local state for checkable runner items.
+ *
+ * Usage:
+ *
+ * ```
+ * import { createLocalState } from '~/runner/graphql/list/local_state';
+ *
+ * // initialize local state
+ * const { cacheConfig, typeDefs, localMutations } = createLocalState();
+ *
+ * // configure the client
+ * apolloClient = createApolloClient({}, { cacheConfig, typeDefs });
+ *
+ * // modify local state
+ * localMutations.setRunnerChecked( ... )
+ * ```
+ *
+ * Note: Currently only in use behind a feature flag:
+ * admin_runners_bulk_delete for the admin list, rollout issue:
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+ *
+ * @returns {Object} An object to configure an Apollo client:
+ * contains cacheConfig, typeDefs, localMutations.
+ */
+export const createLocalState = () => {
+ const checkedRunnerIdsVar = makeVar({});
+
+ const cacheConfig = {
+ typePolicies: {
+ Query: {
+ fields: {
+ checkedRunnerIds() {
+ return Object.entries(checkedRunnerIdsVar())
+ .filter(([, isChecked]) => isChecked)
+ .map(([key]) => key);
+ },
+ },
+ },
+ },
+ };
+
+ const localMutations = {
+ setRunnerChecked({ runner, isChecked }) {
+ checkedRunnerIdsVar({
+ ...checkedRunnerIdsVar(),
+ [runner.id]: isChecked,
+ });
+ },
+ clearChecked() {
+ checkedRunnerIdsVar({});
+ },
+ };
+
+ return {
+ cacheConfig,
+ typeDefs,
+ localMutations,
+ };
+};
diff --git a/app/assets/javascripts/runner/graphql/list/typedefs.graphql b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
new file mode 100644
index 00000000000..24e9e20cc8c
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
@@ -0,0 +1,3 @@
+extend type Query {
+ checkedRunnerIds: [ID!]!
+}
diff --git a/app/assets/javascripts/runner/utils.js b/app/assets/javascripts/runner/utils.js
index 6e4c8c45e7b..1f7794720de 100644
--- a/app/assets/javascripts/runner/utils.js
+++ b/app/assets/javascripts/runner/utils.js
@@ -24,7 +24,7 @@ export const formatJobCount = (jobCount) => {
* @param {Object} options
* @returns Field object to add to GlTable fields
*/
-export const tableField = ({ key, label = '', thClasses = [] }) => {
+export const tableField = ({ key, label = '', thClasses = [], ...options }) => {
return {
key,
label,
@@ -32,6 +32,7 @@ export const tableField = ({ key, label = '', thClasses = [] }) => {
tdAttr: {
'data-testid': `td-${key}`,
},
+ ...options,
};
};
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index 3d4074fbb02..6892404322f 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -4,7 +4,6 @@ import { __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
-import { helpPagePath } from '~/helpers/help_page_helper';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
@@ -52,7 +51,7 @@ export default {
TrainingProviderList,
},
mixins: [glFeatureFlagsMixin()],
- inject: ['projectFullPath'],
+ inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
props: {
augmentedSecurityFeatures: {
type: Array,
@@ -127,9 +126,6 @@ export default {
},
},
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
- securityTraininDocLink: helpPagePath('user/application_security/vulnerabilities/index', {
- anchor: 'enable-security-training-for-vulnerabilities',
- }),
};
</script>
@@ -268,7 +264,7 @@ export default {
{{ $options.i18n.securityTrainingDescription }}
</p>
<p>
- <gl-link :href="$options.securityTraininDocLink">{{
+ <gl-link :href="vulnerabilityTrainingDocsPath">{{
$options.i18n.securityTrainingDoc
}}</gl-link>
</p>
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 8416692dd27..65cf1ec27a3 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -25,6 +25,7 @@ export const initSecurityConfiguration = (el) => {
gitlabCiHistoryPath,
autoDevopsHelpPagePath,
autoDevopsPath,
+ vulnerabilityTrainingDocsPath,
} = el.dataset;
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
@@ -41,6 +42,7 @@ export const initSecurityConfiguration = (el) => {
upgradePath,
autoDevopsHelpPagePath,
autoDevopsPath,
+ vulnerabilityTrainingDocsPath,
},
render(createElement) {
return createElement(SecurityConfigurationApp, {
diff --git a/app/assets/javascripts/vue_shared/components/help_popover.vue b/app/assets/javascripts/vue_shared/components/help_popover.vue
index f3b871c91b6..1568337cd93 100644
--- a/app/assets/javascripts/vue_shared/components/help_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/help_popover.vue
@@ -25,7 +25,7 @@ export default {
</script>
<template>
<span>
- <gl-button ref="popoverTrigger" variant="link" icon="question" :aria-label="__('Help')" />
+ <gl-button ref="popoverTrigger" variant="link" icon="question-o" :aria-label="__('Help')" />
<gl-popover :target="() => $refs.popoverTrigger.$el" v-bind="options">
<template v-if="options.title" #title>
<span v-safe-html="options.title"></span>
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 2744be0150c..06880ace899 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -4,6 +4,9 @@ class Admin::RunnersController < Admin::ApplicationController
include RunnerSetupScripts
before_action :runner, except: [:index, :tag_list, :runner_setup_scripts]
+ before_action only: [:index] do
+ push_frontend_feature_flag(:admin_runners_bulk_delete, default_enabled: :yaml)
+ end
feature_category :runner
diff --git a/app/helpers/projects/security/configuration_helper.rb b/app/helpers/projects/security/configuration_helper.rb
index 8281b1f8522..efc77550c90 100644
--- a/app/helpers/projects/security/configuration_helper.rb
+++ b/app/helpers/projects/security/configuration_helper.rb
@@ -6,6 +6,10 @@ module Projects
def security_upgrade_path
"https://#{ApplicationHelper.promo_host}/pricing/"
end
+
+ def vulnerability_training_docs_path
+ help_page_path('user/application_security/vulnerabilities/index', anchor: 'enable-security-training-for-vulnerabilities')
+ end
end
end
end
diff --git a/app/helpers/users/group_callouts_helper.rb b/app/helpers/users/group_callouts_helper.rb
index 0aa4eb89499..9a9fce4d7e3 100644
--- a/app/helpers/users/group_callouts_helper.rb
+++ b/app/helpers/users/group_callouts_helper.rb
@@ -31,3 +31,5 @@ module Users
end
end
end
+
+Users::GroupCalloutsHelper.prepend_mod
diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb
index 839be8d2a48..7c0a756733c 100644
--- a/app/models/users/group_callout.rb
+++ b/app/models/users/group_callout.rb
@@ -14,7 +14,8 @@ module Users
storage_enforcement_banner_first_enforcement_threshold: 3,
storage_enforcement_banner_second_enforcement_threshold: 4,
storage_enforcement_banner_third_enforcement_threshold: 5,
- storage_enforcement_banner_fourth_enforcement_threshold: 6
+ storage_enforcement_banner_fourth_enforcement_threshold: 6,
+ preview_user_over_limit_free_plan_alert: 7 # EE-only
}
validates :group, presence: true
diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb
index 72fe14d224d..75f6d749acb 100644
--- a/app/presenters/issue_presenter.rb
+++ b/app/presenters/issue_presenter.rb
@@ -4,7 +4,9 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
presents ::Issue, as: :issue
def issue_path
- url_builder.build(issue, only_path: true)
+ return url_builder.build(issue, only_path: true) unless use_work_items_path?
+
+ project_work_items_path(issue.project, work_items_path: issue.id)
end
delegator_override :subscribed?
@@ -15,6 +17,18 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
def project_emails_disabled?
issue.project.emails_disabled?
end
+
+ def web_url
+ return super unless use_work_items_path?
+
+ project_work_items_url(issue.project, work_items_path: issue.id)
+ end
+
+ private
+
+ def use_work_items_path?
+ issue.issue_type == 'task' && issue.project.work_items_feature_flag_enabled?
+ end
end
IssuePresenter.prepend_mod_with('IssuePresenter')
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 5c579cf6488..3e619096d4e 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,6 +1,8 @@
- add_page_specific_style 'page_bundles/members'
- page_title _('Group members')
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
+
.row.gl-mt-3
.col-lg-12
.gl-display-flex.gl-flex-wrap
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 0f91ecae3c9..65613efbf63 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -7,6 +7,7 @@
= render_if_exists 'shared/thanks_for_purchase_banner', plan_title: plan_title, quantity: params[:purchased_quantity].to_i
= render_if_exists 'shared/qrtly_reconciliation_alert', group: @group
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
- if show_invite_banner?(@group)
= content_for :group_invite_members_banner do
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index a656b61dc8f..3c4b612f33f 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -20,6 +20,7 @@
= dispensable_render_if_exists "shared/namespace_user_cap_reached_alert"
= dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
= yield :page_level_alert
+ = yield :user_over_limit_free_plan_alert
= yield :group_invite_members_banner
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 6a54eedf6c8..b2338fa6c55 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -3,6 +3,7 @@
- escaped_default_branch_name = default_branch_name.shellescape
- @skip_current_level_breadcrumb = true
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
= render partial: 'flash_messages', locals: { project: @project }
= render "home_panel"
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index e3f46d601a3..1331ed24307 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -1,6 +1,8 @@
- page_title _('No repository')
- @skip_current_level_breadcrumb = true
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+
%h2.gl-display-flex
.gl-display-flex.gl-align-items-center.gl-justify-content-center
= sprite_icon('warning-solid', size: 24, css_class: 'gl-mr-2')
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index f97b9a2b02f..298c2074062 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,6 +1,8 @@
- add_page_specific_style 'page_bundles/members'
- page_title _("Members")
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+
.row.gl-mt-3
.col-lg-12
- if can_invite_members_for_project?(@project)
diff --git a/app/views/projects/security/configuration/show.html.haml b/app/views/projects/security/configuration/show.html.haml
index df14bd09a4d..4b82f74d035 100644
--- a/app/views/projects/security/configuration/show.html.haml
+++ b/app/views/projects/security/configuration/show.html.haml
@@ -3,5 +3,6 @@
- @content_class = "limit-container-width" unless fluid_layout
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
+ vulnerability_training_docs_path: vulnerability_training_docs_path,
upgrade_path: security_upgrade_path,
project_full_path: @project.full_path } }
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index d840ea01b89..dad2822feb4 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,6 +6,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
+= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
= render partial: 'flash_messages', locals: { project: @project }
= render "projects/last_push"
diff --git a/config/feature_flags/development/admin_runners_bulk_delete.yml b/config/feature_flags/development/admin_runners_bulk_delete.yml
new file mode 100644
index 00000000000..ff285855e96
--- /dev/null
+++ b/config/feature_flags/development/admin_runners_bulk_delete.yml
@@ -0,0 +1,8 @@
+---
+name: admin_runners_bulk_delete
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81894
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+milestone: '14.9'
+type: development
+group: group::runner
+default_enabled: false
diff --git a/data/removals/15_0/15-0-rerequest-review.yml b/data/removals/15_0/15-0-rerequest-review.yml
new file mode 100644
index 00000000000..e692df05670
--- /dev/null
+++ b/data/removals/15_0/15-0-rerequest-review.yml
@@ -0,0 +1,16 @@
+- name: "Request a new review" # the name of the feature being removed. Avoid the words `deprecation`, `deprecate`, `removal`, and `remove` in this field because these are implied.
+ announcement_milestone: "15.0" # The milestone when this feature was deprecated.
+ announcement_date: "2022-05-22" # The date of the milestone release when this feature was deprecated. This should almost always be the 22nd of a month (YYYY-MM-DD), unless you did an out of band blog post.
+ removal_milestone: "15.0" # The milestone when this feature is being removed.
+ removal_date: "2022-05-22" # This should almost always be the 22nd of a month (YYYY-MM-DD), the date of the milestone release when this feature will be removed.
+ breaking_change: false # Change to true if this removal is a breaking change.
+ reporter: phikai # GitLab username of the person reporting the removal
+ body: | # Do not modify this line, instead modify the lines below.
+ The ability to [request a new review](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review) has been removed in GitLab 15.0. This feature is replaced with [requesting attention](https://docs.gitlab.com/ee/user/project/merge_requests/#request-attention-to-a-merge-request) to a merge request.
+# The following items are not published on the docs page, but may be used in the future.
+ stage: Create # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ issue_url: # (optional) This is a link to the deprecation issue in GitLab
+ documentation_url: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/doc/.vale/gitlab/Wordy.yml b/doc/.vale/gitlab/Wordy.yml
index 716fdc6c38d..7888d16dadb 100644
--- a/doc/.vale/gitlab/Wordy.yml
+++ b/doc/.vale/gitlab/Wordy.yml
@@ -14,3 +14,4 @@ swap:
needs? to: "Rewrite the sentence, or use 'must', instead of"
note that: "Be concise: rewrite the sentence to not use"
please: "Remove this word from the sentence: "
+ respectively: "Rewrite the sentence to be more precise, instead of using "
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 733a0344d51..c03c600fe0b 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -51,7 +51,7 @@ If the highest number stable branch is unclear, check the [GitLab blog](https://
| [Ruby](#2-ruby) | `2.7` | From GitLab 13.6, Ruby 2.7 is required. Ruby 3.0 is not supported yet (see [the relevant epic](https://gitlab.com/groups/gitlab-org/-/epics/5149) for the current status). You must use the standard MRI implementation of Ruby. We love [JRuby](https://www.jruby.org/) and [Rubinius](https://github.com/rubinius/rubinius#the-rubinius-language-platform), but GitLab needs several Gems that have native extensions. |
| [Go](#3-go) | `1.16` | |
| [Git](#git) | `2.33.x` | From GitLab 14.4, Git 2.33.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). |
-| [Node.js](#4-node) | `12.22.1` | GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets. Node.js 14.x is recommended, as it's faster. You can check which version you're running with `node -v`. You need to update it to a newer version if needed. |
+| [Node.js](#4-node) | `14.15.0` | GitLab uses [webpack](https://webpack.js.org/) to compile frontend assets. Node.js 16.x is recommended, as it's faster. You can check which version you're running with `node -v`. You need to update it to a newer version if needed. |
## GitLab directory structure
@@ -263,7 +263,7 @@ GitLab requires the use of Node to compile JavaScript
assets, and Yarn to manage JavaScript dependencies. The current minimum
requirements for these are:
-- `node` >= v12.22.1. (We recommend node 14.x as it is faster)
+- `node` >= v14.15.0. (We recommend node 16.x as it is faster)
- `yarn` = v1.22.x (Yarn 2 is not supported yet)
In many distributions,
@@ -271,8 +271,8 @@ the versions provided by the official package repositories are out of date, so
we need to install through the following commands:
```shell
-# install node v14.x
-curl --location "https://deb.nodesource.com/setup_14.x" | sudo bash -
+# install node v16.x
+curl --location "https://deb.nodesource.com/setup_16.x" | sudo bash -
sudo apt-get install -y nodejs
npm install --global yarn
diff --git a/doc/operations/incident_management/img/incident_list_v13_5.png b/doc/operations/incident_management/img/incident_list_v13_5.png
deleted file mode 100644
index 88942a70e88..00000000000
--- a/doc/operations/incident_management/img/incident_list_v13_5.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/metrics/dashboards/default.md b/doc/operations/metrics/dashboards/default.md
index 3e14917209a..1b494a7385d 100644
--- a/doc/operations/metrics/dashboards/default.md
+++ b/doc/operations/metrics/dashboards/default.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab provides some dashboards out-of-the-box for any project with
[Prometheus available](../../../user/project/integrations/prometheus.md). You can
diff --git a/doc/operations/metrics/dashboards/develop.md b/doc/operations/metrics/dashboards/develop.md
index fc7686c8f86..3525584aa15 100644
--- a/doc/operations/metrics/dashboards/develop.md
+++ b/doc/operations/metrics/dashboards/develop.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab provides a template to make it easier for you to create templates for
[custom dashboards](index.md). Templates provide helpful guidance and
diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md
index a8ca23b7002..3d444b5512b 100644
--- a/doc/operations/metrics/dashboards/index.md
+++ b/doc/operations/metrics/dashboards/index.md
@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
By default, all projects include a [GitLab-defined Prometheus dashboard](default.md), which
includes a few key metrics, but you can also define your own custom dashboards.
diff --git a/doc/operations/metrics/dashboards/panel_types.md b/doc/operations/metrics/dashboards/panel_types.md
index 734b560bf13..999431c7104 100644
--- a/doc/operations/metrics/dashboards/panel_types.md
+++ b/doc/operations/metrics/dashboards/panel_types.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
The below panel types are supported in monitoring dashboards.
diff --git a/doc/operations/metrics/dashboards/settings.md b/doc/operations/metrics/dashboards/settings.md
index 14da5cf4a04..ff268a5a4fe 100644
--- a/doc/operations/metrics/dashboards/settings.md
+++ b/doc/operations/metrics/dashboards/settings.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
You can configure your [Monitoring dashboard](../index.md) to
display the time zone of your choice, and the links of your choice.
diff --git a/doc/operations/metrics/dashboards/templating_variables.md b/doc/operations/metrics/dashboards/templating_variables.md
index d751a96f379..0fc49668c43 100644
--- a/doc/operations/metrics/dashboards/templating_variables.md
+++ b/doc/operations/metrics/dashboards/templating_variables.md
@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
Templating variables can be used to make your metrics dashboard more versatile.
diff --git a/doc/operations/metrics/dashboards/variables.md b/doc/operations/metrics/dashboards/variables.md
index 369bcd1ddeb..1b98fcabae6 100644
--- a/doc/operations/metrics/dashboards/variables.md
+++ b/doc/operations/metrics/dashboards/variables.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
## Query variables
diff --git a/doc/operations/metrics/dashboards/yaml.md b/doc/operations/metrics/dashboards/yaml.md
index 81f1354d3c0..37f960498a4 100644
--- a/doc/operations/metrics/dashboards/yaml.md
+++ b/doc/operations/metrics/dashboards/yaml.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
Dashboards have several components:
diff --git a/doc/update/removals.md b/doc/update/removals.md
index 7e2b4f84fa1..80f5006982e 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -28,6 +28,12 @@ For removal reviewers (Technical Writers only):
https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc
-->
+## 15.0
+
+### Request a new review
+
+The ability to [request a new review](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-new-review) has been removed in GitLab 15.0. This feature is replaced with [requesting attention](https://docs.gitlab.com/ee/user/project/merge_requests/#request-attention-to-a-merge-request) to a merge request.
+
## 14.9
### Integrated error tracking disabled by default
diff --git a/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_12.png b/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_12.png
deleted file mode 100644
index 834556df051..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_12.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png b/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png
deleted file mode 100644
index 8d77c53db7f..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_overview_stage_v13_11.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_path_nav_v13_11.png b/doc/user/group/value_stream_analytics/img/vsa_path_nav_v13_11.png
deleted file mode 100644
index 5dd79d06463..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_path_nav_v13_11.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png b/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png
deleted file mode 100644
index 7c3305792bb..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_stage_table_v14_7.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_time_metrics_v13_12.png b/doc/user/group/value_stream_analytics/img/vsa_time_metrics_v13_12.png
deleted file mode 100644
index 68d9741bed8..00000000000
--- a/doc/user/group/value_stream_analytics/img/vsa_time_metrics_v13_12.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index d47b0fcbb75..a4a345cb287 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -422,7 +422,7 @@ The following table lists group permissions available for each role:
| View 2FA status of members | | | | | ✓ |
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (4) |
| View [Usage Quotas](usage_quotas.md) Page | | | | ✓ | ✓ (4) |
-| Manage runners | | | | | ✓ |
+| Manage group runners | | | | | ✓ |
<!-- markdownlint-disable MD029 -->
diff --git a/doc/user/project/deploy_keys/img/deploy_keys_v13_0.png b/doc/user/project/deploy_keys/img/deploy_keys_v13_0.png
deleted file mode 100644
index 15e6e71803c..00000000000
--- a/doc/user/project/deploy_keys/img/deploy_keys_v13_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md
index e8d611af30d..f42dee0f4ad 100644
--- a/doc/user/project/integrations/prometheus_library/cloudwatch.md
+++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab supports automatically detecting and monitoring AWS resources, starting
with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/) (ELB).
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
index 76d13d5487c..82d54542e81 100644
--- a/doc/user/project/integrations/prometheus_library/haproxy.md
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
diff --git a/doc/user/project/integrations/prometheus_library/index.md b/doc/user/project/integrations/prometheus_library/index.md
index 9bdd4945f5d..184ae4a4cf0 100644
--- a/doc/user/project/integrations/prometheus_library/index.md
+++ b/doc/user/project/integrations/prometheus_library/index.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/).
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 33a06958e0c..b6525a5cf8e 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab has support for automatically detecting and monitoring Kubernetes metrics.
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index ecf75d7b17a..9d75361df90 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index e123000e0c5..3ae80ef9db1 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
GitLab has support for automatically detecting and monitoring the Kubernetes NGINX Ingress controller. This is provided by leveraging the built-in Prometheus metrics included with Kubernetes NGINX Ingress controller [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160) onward.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
index fda7744e847..367b423fbd0 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
-for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
+for use in GitLab 14.7, and is planned for removal in GitLab 16.0.
NOTE:
[NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
diff --git a/doc/user/project/merge_requests/reviews/index.md b/doc/user/project/merge_requests/reviews/index.md
index 280ae07b401..0848a861c10 100644
--- a/doc/user/project/merge_requests/reviews/index.md
+++ b/doc/user/project/merge_requests/reviews/index.md
@@ -112,7 +112,13 @@ This example shows reviewers and approval rules in a merge request sidebar:
### Request a new review
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/293933) in GitLab 13.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/293933) in GitLab 13.9.
+> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/357271) in GitLab 14.10.
+
+WARNING:
+This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/357271)
+for use in GitLab 14.10, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/357271) in GitLab 15.0.
+Use [attention requests](../index.md#request-attention-to-a-merge-request) instead.
After a reviewer completes their [merge request reviews](../../../discussions/index.md),
the author of the merge request can request a new review from the reviewer:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8e99fe0818c..1c96ca2ccc8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15153,6 +15153,9 @@ msgstr ""
msgid "Explore groups"
msgstr ""
+msgid "Explore paid plans"
+msgstr ""
+
msgid "Explore projects"
msgstr ""
@@ -16258,6 +16261,9 @@ msgstr ""
msgid "From %{providerTitle}"
msgstr ""
+msgid "From June 22, 2022 (GitLab 15.1), free personal namespaces and top-level groups will be limited to %{free_limit} members"
+msgstr ""
+
msgid "From issue creation until deploy to production"
msgstr ""
@@ -22896,6 +22902,9 @@ msgstr ""
msgid "Manage labels"
msgstr ""
+msgid "Manage members"
+msgstr ""
+
msgid "Manage milestones"
msgstr ""
@@ -32184,6 +32193,16 @@ msgstr ""
msgid "Runners|%{percentage} spot."
msgstr ""
+msgid "Runners|%{strongStart}%{count}%{strongEnd} runner selected"
+msgid_plural "Runners|%{strongStart}%{count}%{strongEnd} runners selected"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "Runners|%{strongStart}%{count}%{strongEnd} runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?"
+msgid_plural "Runners|%{strongStart}%{count}%{strongEnd} runners will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet."
msgstr ""
@@ -32229,9 +32248,15 @@ msgstr ""
msgid "Runners|Change to project runner"
msgstr ""
+msgid "Runners|Checkbox"
+msgstr ""
+
msgid "Runners|Choose your preferred GitLab Runner"
msgstr ""
+msgid "Runners|Clear selection"
+msgstr ""
+
msgid "Runners|Command to register runner"
msgstr ""
@@ -32244,12 +32269,20 @@ msgstr ""
msgid "Runners|Copy registration token"
msgstr ""
+msgid "Runners|Delete %d runner"
+msgid_plural "Runners|Delete %d runners"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Runners|Delete runner"
msgstr ""
msgid "Runners|Delete runner %{name}?"
msgstr ""
+msgid "Runners|Delete selected"
+msgstr ""
+
msgid "Runners|Deploy GitLab Runner in AWS"
msgstr ""
@@ -32340,6 +32373,11 @@ msgstr ""
msgid "Runners|Paused"
msgstr ""
+msgid "Runners|Permanently delete %d runner"
+msgid_plural "Runners|Permanently delete %d runners"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Runners|Platform"
msgstr ""
@@ -43243,6 +43281,9 @@ msgstr ""
msgid "YouTube URL or ID"
msgstr ""
+msgid "Your %{doc_link_start}namespace%{doc_link_end}, %{strong_start}%{namespace_name}%{strong_end} has more than %{free_limit} members. From June 22, 2022, it will be limited to %{free_limit}, and the remaining members will get a %{link_start}status of Over limit%{link_end} and lose access to the namespace. You can go to the Usage Quotas page to manage which %{free_limit} members will remain in your namespace. To get more members, an owner can start a trial or upgrade to a paid tier."
+msgstr ""
+
msgid "Your %{group} membership will now expire in %{days}."
msgstr ""
diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb
index d90e11a1681..5218e6df217 100644
--- a/qa/qa/tools/test_resources_handler.rb
+++ b/qa/qa/tools/test_resources_handler.rb
@@ -75,13 +75,16 @@ module QA
# Download files from GCS bucket by environment name
# Delete the files afterward
def download(ci_project_name)
- files_list = gcs_storage.list_objects(BUCKET, prefix: ci_project_name).items.each_with_object([]) do |obj, arr|
+ bucket_items = gcs_storage.list_objects(BUCKET, prefix: ci_project_name).items
+
+ files_list = bucket_items&.each_with_object([]) do |obj, arr|
arr << obj.name
end
- return puts "\nNothing to download!" if files_list.empty?
+ return puts "\nNothing to download!" if files_list.blank?
FileUtils.mkdir_p('tmp/')
+
files_list.each do |file_name|
local_path = "tmp/#{file_name.split('/').last}"
Runtime::Logger.info("Downloading #{file_name} to #{local_path}")
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 26c858665a8..8c714f7736f 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -58,6 +58,11 @@ FactoryBot.define do
end
end
+ trait :task do
+ issue_type { :task }
+ association :work_item_type, :default, :task
+ end
+
factory :incident do
issue_type { :incident }
association :work_item_type, :default, :incident
diff --git a/spec/factories/work_items/work_item_types.rb b/spec/factories/work_items/work_item_types.rb
index 0920b36bcbd..1b6137503d3 100644
--- a/spec/factories/work_items/work_item_types.rb
+++ b/spec/factories/work_items/work_item_types.rb
@@ -37,5 +37,10 @@ FactoryBot.define do
base_type { WorkItems::Type.base_types[:requirement] }
icon_name { 'issue-type-requirements' }
end
+
+ trait :task do
+ base_type { WorkItems::Type.base_types[:task] }
+ icon_name { 'issue-type-task' }
+ end
end
end
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
index 1f923f41274..b61b65c2713 100644
--- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -135,7 +135,7 @@ describe('Grouped code quality reports app', () => {
});
it('does not render a help icon', () => {
- expect(findWidget().find('[data-testid="question-icon"]').exists()).toBe(false);
+ expect(findWidget().find('[data-testid="question-o-icon"]').exists()).toBe(false);
});
describe('when base report was not found', () => {
@@ -144,7 +144,7 @@ describe('Grouped code quality reports app', () => {
});
it('renders a help icon with more information', () => {
- expect(findWidget().find('[data-testid="question-icon"]').exists()).toBe(true);
+ expect(findWidget().find('[data-testid="question-o-icon"]').exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
index 313870e8ea1..edc25eb7e02 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -13,6 +13,7 @@ import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
+import { createLocalState } from '~/runner/graphql/list/local_state';
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
@@ -43,6 +44,7 @@ import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered
import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
+const mockRunners = runnersData.data.runners.nodes;
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@@ -58,6 +60,8 @@ describe('AdminRunnersApp', () => {
let wrapper;
let mockRunnersQuery;
let mockRunnersCountQuery;
+ let cacheConfig;
+ let localMutations;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
@@ -69,18 +73,30 @@ describe('AdminRunnersApp', () => {
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
- const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = ({
+ props = {},
+ mountFn = shallowMountExtended,
+ provide,
+ ...options
+ } = {}) => {
+ ({ cacheConfig, localMutations } = createLocalState());
+
const handlers = [
[adminRunnersQuery, mockRunnersQuery],
[adminRunnersCountQuery, mockRunnersCountQuery],
];
wrapper = mountFn(AdminRunnersApp, {
- apolloProvider: createMockApollo(handlers),
+ apolloProvider: createMockApollo(handlers, {}, cacheConfig),
propsData: {
registrationToken: mockRegistrationToken,
...props,
},
+ provide: {
+ localMutations,
+ ...provide,
+ },
+ ...options,
});
};
@@ -173,7 +189,7 @@ describe('AdminRunnersApp', () => {
});
it('shows the runners list', () => {
- expect(findRunnerList().props('runners')).toEqual(runnersData.data.runners.nodes);
+ expect(findRunnerList().props('runners')).toEqual(mockRunners);
});
it('runner item links to the runner admin page', async () => {
@@ -181,7 +197,7 @@ describe('AdminRunnersApp', () => {
await waitForPromises();
- const { id, shortSha } = runnersData.data.runners.nodes[0];
+ const { id, shortSha } = mockRunners[0];
const numericId = getIdFromGraphQLId(id);
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
@@ -197,7 +213,7 @@ describe('AdminRunnersApp', () => {
const runnerActions = wrapper.find('tr [data-testid="td-actions"]').find(RunnerActionsCell);
- const runner = runnersData.data.runners.nodes[0];
+ const runner = mockRunners[0];
expect(runnerActions.props()).toEqual({
runner,
@@ -232,8 +248,7 @@ describe('AdminRunnersApp', () => {
describe('Single runner row', () => {
let showToast;
- const mockRunner = runnersData.data.runners.nodes[0];
- const { id: graphqlId, shortSha } = mockRunner;
+ const { id: graphqlId, shortSha } = mockRunners[0];
const id = getIdFromGraphQLId(graphqlId);
const COUNT_QUERIES = 7; // Smart queries that display a filtered count of runners
const FILTERED_COUNT_QUERIES = 4; // Smart queries that display a count of runners in tabs
@@ -333,6 +348,41 @@ describe('AdminRunnersApp', () => {
expect(findRunnerList().props('loading')).toBe(true);
});
+ describe('when bulk delete is enabled', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ glFeatures: { adminRunnersBulkDelete: true },
+ },
+ });
+ });
+
+ it('runner list is checkable', () => {
+ expect(findRunnerList().props('checkable')).toBe(true);
+ });
+
+ it('responds to checked items by updating the local cache', () => {
+ const setRunnerCheckedMock = jest
+ .spyOn(localMutations, 'setRunnerChecked')
+ .mockImplementation(() => {});
+
+ const runner = mockRunners[0];
+
+ expect(setRunnerCheckedMock).toHaveBeenCalledTimes(0);
+
+ findRunnerList().vm.$emit('checked', {
+ runner,
+ isChecked: true,
+ });
+
+ expect(setRunnerCheckedMock).toHaveBeenCalledTimes(1);
+ expect(setRunnerCheckedMock).toHaveBeenCalledWith({
+ runner,
+ isChecked: true,
+ });
+ });
+ });
+
describe('when no runners are found', () => {
beforeEach(async () => {
mockRunnersQuery = jest.fn().mockResolvedValue({
diff --git a/spec/frontend/runner/components/runner_bulk_delete_spec.js b/spec/frontend/runner/components/runner_bulk_delete_spec.js
new file mode 100644
index 00000000000..f5b56396cf1
--- /dev/null
+++ b/spec/frontend/runner/components/runner_bulk_delete_spec.js
@@ -0,0 +1,103 @@
+import Vue from 'vue';
+import { GlSprintf } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createLocalState } from '~/runner/graphql/list/local_state';
+import waitForPromises from 'helpers/wait_for_promises';
+
+Vue.use(VueApollo);
+
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
+
+describe('RunnerBulkDelete', () => {
+ let wrapper;
+ let mockState;
+ let mockCheckedRunnerIds;
+
+ const findClearBtn = () => wrapper.findByTestId('clear-btn');
+ const findDeleteBtn = () => wrapper.findByTestId('delete-btn');
+
+ const createComponent = () => {
+ const { cacheConfig, localMutations } = mockState;
+
+ wrapper = shallowMountExtended(RunnerBulkDelete, {
+ apolloProvider: createMockApollo(undefined, undefined, cacheConfig),
+ provide: {
+ localMutations,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockState = createLocalState();
+
+ jest
+ .spyOn(mockState.cacheConfig.typePolicies.Query.fields, 'checkedRunnerIds')
+ .mockImplementation(() => mockCheckedRunnerIds);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('When no runners are checked', () => {
+ beforeEach(async () => {
+ mockCheckedRunnerIds = [];
+
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('shows no contents', () => {
+ expect(wrapper.html()).toBe('');
+ });
+ });
+
+ describe.each`
+ count | ids | text
+ ${1} | ${['gid:Runner/1']} | ${'1 runner'}
+ ${2} | ${['gid:Runner/1', 'gid:Runner/2']} | ${'2 runners'}
+ `('When $count runner(s) are checked', ({ count, ids, text }) => {
+ beforeEach(() => {
+ mockCheckedRunnerIds = ids;
+
+ createComponent();
+
+ jest.spyOn(mockState.localMutations, 'clearChecked').mockImplementation(() => {});
+ });
+
+ it(`shows "${text}"`, () => {
+ expect(wrapper.text()).toContain(text);
+ });
+
+ it('clears selection', () => {
+ expect(mockState.localMutations.clearChecked).toHaveBeenCalledTimes(0);
+
+ findClearBtn().vm.$emit('click');
+
+ expect(mockState.localMutations.clearChecked).toHaveBeenCalledTimes(1);
+ });
+
+ it('shows confirmation modal', () => {
+ expect(confirmAction).toHaveBeenCalledTimes(0);
+
+ findDeleteBtn().vm.$emit('click');
+
+ expect(confirmAction).toHaveBeenCalledTimes(1);
+
+ const [, confirmOptions] = confirmAction.mock.calls[0];
+ const { title, modalHtmlMessage, primaryBtnText } = confirmOptions;
+
+ expect(title).toMatch(text);
+ expect(primaryBtnText).toMatch(text);
+ expect(modalHtmlMessage).toMatch(`<strong>${count}</strong>`);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 951a7ac048c..fab94771990 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -55,7 +55,7 @@ describe('RunnerList', () => {
});
it('Sets runner id as a row key', () => {
- createComponent({});
+ createComponent();
expect(findTable().attributes('primary-key')).toBe('id');
});
@@ -90,6 +90,35 @@ describe('RunnerList', () => {
expect(findCell({ fieldKey: 'actions' }).exists()).toBe(true);
});
+ describe('When the list is checkable', () => {
+ beforeEach(() => {
+ createComponent(
+ {
+ props: {
+ checkable: true,
+ },
+ },
+ mountExtended,
+ );
+ });
+
+ it('Displays a checkbox field', () => {
+ expect(findCell({ fieldKey: 'checkbox' }).find('input').exists()).toBe(true);
+ });
+
+ it('Emits a checked event', () => {
+ const checkbox = findCell({ fieldKey: 'checkbox' }).find('input');
+
+ checkbox.setChecked();
+
+ expect(wrapper.emitted('checked')).toHaveLength(1);
+ expect(wrapper.emitted('checked')[0][0]).toEqual({
+ isChecked: true,
+ runner: mockRunners[0],
+ });
+ });
+ });
+
describe('Scoped cell slots', () => {
it('Render #runner-name slot in "summary" cell', () => {
createComponent(
diff --git a/spec/frontend/runner/graphql/local_state_spec.js b/spec/frontend/runner/graphql/local_state_spec.js
new file mode 100644
index 00000000000..5c4302e4aa2
--- /dev/null
+++ b/spec/frontend/runner/graphql/local_state_spec.js
@@ -0,0 +1,72 @@
+import createApolloClient from '~/lib/graphql';
+import { createLocalState } from '~/runner/graphql/list/local_state';
+import getCheckedRunnerIdsQuery from '~/runner/graphql/list/checked_runner_ids.query.graphql';
+
+describe('~/runner/graphql/list/local_state', () => {
+ let localState;
+ let apolloClient;
+
+ const createSubject = () => {
+ if (apolloClient) {
+ throw new Error('test subject already exists!');
+ }
+
+ localState = createLocalState();
+
+ const { cacheConfig, typeDefs } = localState;
+
+ apolloClient = createApolloClient({}, { cacheConfig, typeDefs });
+ };
+
+ const queryCheckedRunnerIds = () => {
+ const { checkedRunnerIds } = apolloClient.readQuery({
+ query: getCheckedRunnerIdsQuery,
+ });
+ return checkedRunnerIds;
+ };
+
+ beforeEach(() => {
+ createSubject();
+ });
+
+ afterEach(() => {
+ localState = null;
+ apolloClient = null;
+ });
+
+ describe('default', () => {
+ it('has empty checked list', () => {
+ expect(queryCheckedRunnerIds()).toEqual([]);
+ });
+ });
+
+ describe.each`
+ inputs | expected
+ ${[['a', true], ['b', true], ['b', true]]} | ${['a', 'b']}
+ ${[['a', true], ['b', true], ['a', false]]} | ${['b']}
+ ${[['c', true], ['b', true], ['a', true], ['d', false]]} | ${['c', 'b', 'a']}
+ `('setRunnerChecked', ({ inputs, expected }) => {
+ beforeEach(() => {
+ inputs.forEach(([id, isChecked]) => {
+ localState.localMutations.setRunnerChecked({ runner: { id }, isChecked });
+ });
+ });
+ it(`for inputs="${inputs}" has a ids="[${expected}]"`, () => {
+ expect(queryCheckedRunnerIds()).toEqual(expected);
+ });
+ });
+
+ describe('clearChecked', () => {
+ it('clears all checked items', () => {
+ ['a', 'b', 'c'].forEach((id) => {
+ localState.localMutations.setRunnerChecked({ runner: { id }, isChecked: true });
+ });
+
+ expect(queryCheckedRunnerIds()).toEqual(['a', 'b', 'c']);
+
+ localState.localMutations.clearChecked();
+
+ expect(queryCheckedRunnerIds()).toEqual([]);
+ });
+ });
+});
diff --git a/spec/frontend/runner/utils_spec.js b/spec/frontend/runner/utils_spec.js
index 3fa9784ecdf..1db9815dfd8 100644
--- a/spec/frontend/runner/utils_spec.js
+++ b/spec/frontend/runner/utils_spec.js
@@ -44,6 +44,10 @@ describe('~/runner/utils', () => {
thClass: expect.arrayContaining(mockClasses),
});
});
+
+ it('a field with custom options', () => {
+ expect(tableField({ foo: 'bar' })).toMatchObject({ foo: 'bar' });
+ });
});
describe('getPaginationVariables', () => {
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index 5787747b00b..9a18cb636b2 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -33,6 +33,7 @@ const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
const autoDevopsPath = '/autoDevopsPath';
const gitlabCiHistoryPath = 'test/historyPath';
const projectFullPath = 'namespace/project';
+const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
useLocalStorageSpy();
@@ -55,6 +56,7 @@ describe('App component', () => {
autoDevopsHelpPagePath,
autoDevopsPath,
projectFullPath,
+ vulnerabilityTrainingDocsPath,
glFeatures: {
secureVulnerabilityTraining,
},
@@ -462,9 +464,7 @@ describe('App component', () => {
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
- expect(trainingLink.attributes('href')).toBe(
- '/help/user/application_security/vulnerabilities/index#enable-security-training-for-vulnerabilities',
- );
+ expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
});
});
diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js
index 597fb63d95c..64dce194327 100644
--- a/spec/frontend/vue_shared/components/help_popover_spec.js
+++ b/spec/frontend/vue_shared/components/help_popover_spec.js
@@ -34,7 +34,7 @@ describe('HelpPopover', () => {
it('renders a link button with an icon question', () => {
expect(findQuestionButton().props()).toMatchObject({
- icon: 'question',
+ icon: 'question-o',
variant: 'link',
});
});
diff --git a/spec/helpers/projects/security/configuration_helper_spec.rb b/spec/helpers/projects/security/configuration_helper_spec.rb
index 4c30ba87897..034bfd27844 100644
--- a/spec/helpers/projects/security/configuration_helper_spec.rb
+++ b/spec/helpers/projects/security/configuration_helper_spec.rb
@@ -10,4 +10,10 @@ RSpec.describe Projects::Security::ConfigurationHelper do
it { is_expected.to eq("https://#{ApplicationHelper.promo_host}/pricing/") }
end
+
+ describe 'vulnerability_training_docs_path' do
+ subject { helper.vulnerability_training_docs_path }
+
+ it { is_expected.to eq(help_page_path('user/application_security/vulnerabilities/index', anchor: 'enable-security-training-for-vulnerabilities')) }
+ end
end
diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb
index 55a6b50ffa7..e17ae218cd3 100644
--- a/spec/presenters/issue_presenter_spec.rb
+++ b/spec/presenters/issue_presenter_spec.rb
@@ -5,19 +5,42 @@ require 'spec_helper'
RSpec.describe IssuePresenter do
include Gitlab::Routing.url_helpers
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
- let(:issue) { create(:issue, project: project) }
- let(:presenter) { described_class.new(issue, current_user: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:task) { create(:issue, :task, project: project) }
- before do
+ let(:presented_issue) { issue }
+ let(:presenter) { described_class.new(presented_issue, current_user: user) }
+
+ before_all do
group.add_developer(user)
end
describe '#web_url' do
it 'returns correct path' do
- expect(presenter.web_url).to eq("http://localhost/#{group.name}/#{project.name}/-/issues/#{issue.iid}")
+ expect(presenter.web_url).to eq("http://localhost/#{group.name}/#{project.name}/-/issues/#{presented_issue.iid}")
+ end
+
+ context 'when issue type is task' do
+ let(:presented_issue) { task }
+
+ context 'when work_items feature flag is enabled' do
+ it 'returns a work item url for the task' do
+ expect(presenter.web_url).to eq(project_work_items_url(project, work_items_path: presented_issue.id))
+ end
+ end
+
+ context 'when work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'returns an issue url for the task' do
+ expect(presenter.web_url).to eq("http://localhost/#{group.name}/#{project.name}/-/issues/#{presented_issue.iid}")
+ end
+ end
end
end
@@ -29,7 +52,7 @@ RSpec.describe IssuePresenter do
end
it 'returns subscribed' do
- create(:subscription, user: user, project: project, subscribable: issue, subscribed: true)
+ create(:subscription, user: user, project: project, subscribable: presented_issue, subscribed: true)
is_expected.to be(true)
end
@@ -37,7 +60,27 @@ RSpec.describe IssuePresenter do
describe '#issue_path' do
it 'returns correct path' do
- expect(presenter.issue_path).to eq("/#{group.name}/#{project.name}/-/issues/#{issue.iid}")
+ expect(presenter.issue_path).to eq("/#{group.name}/#{project.name}/-/issues/#{presented_issue.iid}")
+ end
+
+ context 'when issue type is task' do
+ let(:presented_issue) { task }
+
+ context 'when work_items feature flag is enabled' do
+ it 'returns a work item path for the task' do
+ expect(presenter.issue_path).to eq(project_work_items_path(project, work_items_path: presented_issue.id))
+ end
+ end
+
+ context 'when work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'returns an issue path for the task' do
+ expect(presenter.issue_path).to eq("/#{group.name}/#{project.name}/-/issues/#{presented_issue.iid}")
+ end
+ end
end
end
diff --git a/spec/views/groups/show.html.haml_spec.rb b/spec/views/groups/show.html.haml_spec.rb
deleted file mode 100644
index 43e11d31611..00000000000
--- a/spec/views/groups/show.html.haml_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'groups/edit.html.haml' do
- include Devise::Test::ControllerHelpers
-
- describe '"Share with group lock" setting' do
- let(:root_owner) { create(:user) }
- let(:root_group) { create(:group) }
-
- before do
- root_group.add_owner(root_owner)
- end
-
- shared_examples_for '"Share with group lock" setting' do |checkbox_options|
- it 'has the correct label, help text, and checkbox options' do
- assign(:group, test_group)
- allow(view).to receive(:can?).with(test_user, :admin_group, test_group).and_return(true)
- allow(view).to receive(:can_change_group_visibility_level?).and_return(false)
- allow(view).to receive(:current_user).and_return(test_user)
- expect(view).to receive(:can_change_share_with_group_lock?).and_return(!checkbox_options[:disabled])
- expect(view).to receive(:share_with_group_lock_help_text).and_return('help text here')
-
- render
-
- expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups")
- expect(rendered).to have_content('help text here')
- expect(rendered).to have_field('group_share_with_group_lock', **checkbox_options)
- end
- end
-
- context 'for a root group' do
- let(:test_group) { root_group }
- let(:test_user) { root_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
- end
-
- context 'for a subgroup' do
- let!(:subgroup) { create(:group, parent: root_group) }
- let(:sub_owner) { create(:user) }
- let(:test_group) { subgroup }
-
- context 'when the root_group has "Share with group lock" disabled' do
- context 'when the subgroup has "Share with group lock" disabled' do
- context 'as the root_owner' do
- let(:test_user) { root_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
- end
-
- context 'as the sub_owner' do
- let(:test_user) { sub_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
- end
- end
-
- context 'when the subgroup has "Share with group lock" enabled' do
- before do
- subgroup.update_column(:share_with_group_lock, true)
- end
-
- context 'as the root_owner' do
- let(:test_user) { root_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: true }
- end
-
- context 'as the sub_owner' do
- let(:test_user) { sub_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: true }
- end
- end
- end
-
- context 'when the root_group has "Share with group lock" enabled' do
- before do
- root_group.update_column(:share_with_group_lock, true)
- end
-
- context 'when the subgroup has "Share with group lock" disabled (parent overridden)' do
- context 'as the root_owner' do
- let(:test_user) { root_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
- end
-
- context 'as the sub_owner' do
- let(:test_user) { sub_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
- end
- end
-
- context 'when the subgroup has "Share with group lock" enabled (same as parent)' do
- before do
- subgroup.update_column(:share_with_group_lock, true)
- end
-
- context 'as the root_owner' do
- let(:test_user) { root_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: false, checked: true }
- end
-
- context 'as the sub_owner' do
- let(:test_user) { sub_owner }
-
- it_behaves_like '"Share with group lock" setting', { disabled: true, checked: true }
- end
- end
- end
- end
- end
-end