Add latest changes from gitlab-org/gitlab@master
|
@ -293,14 +293,14 @@ coverage-frontend:
|
||||||
- *yarn-install
|
- *yarn-install
|
||||||
- run_timed_command "retry yarn run webpack-prod"
|
- 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:
|
qa-frontend-node:14:
|
||||||
extends: .qa-frontend-node
|
extends: .qa-frontend-node
|
||||||
image: ${GITLAB_DEPENDENCY_PROXY}node:14
|
image: ${GITLAB_DEPENDENCY_PROXY}node:14
|
||||||
|
|
||||||
|
qa-frontend-node:16:
|
||||||
|
extends: .qa-frontend-node
|
||||||
|
image: ${GITLAB_DEPENDENCY_PROXY}node:16
|
||||||
|
|
||||||
qa-frontend-node:latest:
|
qa-frontend-node:latest:
|
||||||
extends:
|
extends:
|
||||||
- .qa-frontend-node
|
- .qa-frontend-node
|
||||||
|
|
|
@ -1252,7 +1252,7 @@ GEM
|
||||||
actionpack (>= 4.0)
|
actionpack (>= 4.0)
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 4.0)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (1.3.13)
|
sqlite3 (1.4.2)
|
||||||
ssh_data (1.2.0)
|
ssh_data (1.2.0)
|
||||||
ssrf_filter (1.0.7)
|
ssrf_filter (1.0.7)
|
||||||
stackprof (0.2.15)
|
stackprof (0.2.15)
|
||||||
|
|
|
@ -12,6 +12,7 @@ const PERSISTENT_USER_CALLOUTS = [
|
||||||
'.js-security-newsletter-callout',
|
'.js-security-newsletter-callout',
|
||||||
'.js-approaching-seats-count-threshold',
|
'.js-approaching-seats-count-threshold',
|
||||||
'.js-storage-enforcement-banner',
|
'.js-storage-enforcement-banner',
|
||||||
|
'.js-user-over-limit-free-plan-alert',
|
||||||
];
|
];
|
||||||
|
|
||||||
const initCallouts = () => {
|
const initCallouts = () => {
|
||||||
|
|
|
@ -4,9 +4,11 @@ import { createAlert } from '~/flash';
|
||||||
import { updateHistory } from '~/lib/utils/url_utility';
|
import { updateHistory } from '~/lib/utils/url_utility';
|
||||||
import { formatNumber } from '~/locale';
|
import { formatNumber } from '~/locale';
|
||||||
import { fetchPolicies } from '~/lib/graphql';
|
import { fetchPolicies } from '~/lib/graphql';
|
||||||
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
|
||||||
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
|
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
|
||||||
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.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 RunnerList from '../components/runner_list.vue';
|
||||||
import RunnerName from '../components/runner_name.vue';
|
import RunnerName from '../components/runner_name.vue';
|
||||||
import RunnerStats from '../components/stat/runner_stats.vue';
|
import RunnerStats from '../components/stat/runner_stats.vue';
|
||||||
|
@ -53,6 +55,7 @@ export default {
|
||||||
GlLink,
|
GlLink,
|
||||||
RegistrationDropdown,
|
RegistrationDropdown,
|
||||||
RunnerFilteredSearchBar,
|
RunnerFilteredSearchBar,
|
||||||
|
RunnerBulkDelete,
|
||||||
RunnerList,
|
RunnerList,
|
||||||
RunnerName,
|
RunnerName,
|
||||||
RunnerStats,
|
RunnerStats,
|
||||||
|
@ -60,6 +63,8 @@ export default {
|
||||||
RunnerTypeTabs,
|
RunnerTypeTabs,
|
||||||
RunnerActionsCell,
|
RunnerActionsCell,
|
||||||
},
|
},
|
||||||
|
mixins: [glFeatureFlagMixin()],
|
||||||
|
inject: ['localMutations'],
|
||||||
props: {
|
props: {
|
||||||
registrationToken: {
|
registrationToken: {
|
||||||
type: String,
|
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: {
|
watch: {
|
||||||
search: {
|
search: {
|
||||||
|
@ -238,6 +248,12 @@ export default {
|
||||||
reportToSentry(error) {
|
reportToSentry(error) {
|
||||||
captureException({ error, component: this.$options.name });
|
captureException({ error, component: this.$options.name });
|
||||||
},
|
},
|
||||||
|
onChecked({ runner, isChecked }) {
|
||||||
|
this.localMutations.setRunnerChecked({
|
||||||
|
runner,
|
||||||
|
isChecked,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE,
|
filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE,
|
||||||
INSTANCE_TYPE,
|
INSTANCE_TYPE,
|
||||||
|
@ -286,7 +302,13 @@ export default {
|
||||||
{{ __('No runners found') }}
|
{{ __('No runners found') }}
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<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 }">
|
<template #runner-name="{ runner }">
|
||||||
<gl-link :href="runner.adminUrl">
|
<gl-link :href="runner.adminUrl">
|
||||||
<runner-name :runner="runner" />
|
<runner-name :runner="runner" />
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { GlToast } from '@gitlab/ui';
|
import { GlToast } from '@gitlab/ui';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueApollo from 'vue-apollo';
|
import VueApollo from 'vue-apollo';
|
||||||
import createDefaultClient from '~/lib/graphql';
|
|
||||||
import { visitUrl } from '~/lib/utils/url_utility';
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
import { updateOutdatedUrl } from '~/runner/runner_search_utils';
|
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';
|
import AdminRunnersApp from './admin_runners_app.vue';
|
||||||
|
|
||||||
Vue.use(GlToast);
|
Vue.use(GlToast);
|
||||||
|
@ -27,8 +28,10 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
|
||||||
|
|
||||||
const { runnerInstallHelpPage, registrationToken } = el.dataset;
|
const { runnerInstallHelpPage, registrationToken } = el.dataset;
|
||||||
|
|
||||||
|
const { cacheConfig, typeDefs, localMutations } = createLocalState();
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
const apolloProvider = new VueApollo({
|
||||||
defaultClient: createDefaultClient(),
|
defaultClient: createDefaultClient({}, { cacheConfig, typeDefs }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
|
@ -36,6 +39,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
|
||||||
apolloProvider,
|
apolloProvider,
|
||||||
provide: {
|
provide: {
|
||||||
runnerInstallHelpPage,
|
runnerInstallHelpPage,
|
||||||
|
localMutations,
|
||||||
},
|
},
|
||||||
render(h) {
|
render(h) {
|
||||||
return h(AdminRunnersApp, {
|
return h(AdminRunnersApp, {
|
||||||
|
|
|
@ -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>
|
|
@ -4,11 +4,22 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/toolt
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { __, s__ } from '~/locale';
|
import { __, s__ } from '~/locale';
|
||||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
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 { formatJobCount, tableField } from '../utils';
|
||||||
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
|
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
|
||||||
import RunnerStatusCell from './cells/runner_status_cell.vue';
|
import RunnerStatusCell from './cells/runner_status_cell.vue';
|
||||||
import RunnerTags from './runner_tags.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 {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlTableLite,
|
GlTableLite,
|
||||||
|
@ -22,7 +33,20 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
|
apollo: {
|
||||||
|
checkedRunnerIds: {
|
||||||
|
query: checkedRunnerIdsQuery,
|
||||||
|
skip() {
|
||||||
|
return !this.checkable;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
|
checkable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -33,6 +57,10 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['checked'],
|
||||||
|
data() {
|
||||||
|
return { checkedRunnerIds: [] };
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tableClass() {
|
tableClass() {
|
||||||
// <gl-table-lite> does not provide a busy state, add
|
// <gl-table-lite> does not provide a busy state, add
|
||||||
|
@ -42,6 +70,18 @@ export default {
|
||||||
'gl-opacity-6': this.loading,
|
'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: {
|
methods: {
|
||||||
formatJobCount(jobCount) {
|
formatJobCount(jobCount) {
|
||||||
|
@ -55,16 +95,16 @@ export default {
|
||||||
}
|
}
|
||||||
return {};
|
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>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -73,13 +113,29 @@ export default {
|
||||||
:aria-busy="loading"
|
:aria-busy="loading"
|
||||||
:class="tableClass"
|
:class="tableClass"
|
||||||
:items="runners"
|
:items="runners"
|
||||||
:fields="$options.fields"
|
:fields="fields"
|
||||||
:tbody-tr-attr="runnerTrAttr"
|
:tbody-tr-attr="runnerTrAttr"
|
||||||
data-testid="runner-list"
|
data-testid="runner-list"
|
||||||
stacked="md"
|
stacked="md"
|
||||||
primary-key="id"
|
primary-key="id"
|
||||||
fixed
|
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 }">
|
<template #cell(status)="{ item }">
|
||||||
<runner-status-cell :runner="item" />
|
<runner-status-cell :runner="item" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
query getCheckedRunnerIds {
|
||||||
|
checkedRunnerIds @client
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
extend type Query {
|
||||||
|
checkedRunnerIds: [ID!]!
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ export const formatJobCount = (jobCount) => {
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @returns Field object to add to GlTable fields
|
* @returns Field object to add to GlTable fields
|
||||||
*/
|
*/
|
||||||
export const tableField = ({ key, label = '', thClasses = [] }) => {
|
export const tableField = ({ key, label = '', thClasses = [], ...options }) => {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
label,
|
label,
|
||||||
|
@ -32,6 +32,7 @@ export const tableField = ({ key, label = '', thClasses = [] }) => {
|
||||||
tdAttr: {
|
tdAttr: {
|
||||||
'data-testid': `td-${key}`,
|
'data-testid': `td-${key}`,
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { __, s__ } from '~/locale';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.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 AutoDevOpsAlert from './auto_dev_ops_alert.vue';
|
||||||
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
|
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
|
||||||
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
|
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
|
||||||
|
@ -52,7 +51,7 @@ export default {
|
||||||
TrainingProviderList,
|
TrainingProviderList,
|
||||||
},
|
},
|
||||||
mixins: [glFeatureFlagsMixin()],
|
mixins: [glFeatureFlagsMixin()],
|
||||||
inject: ['projectFullPath'],
|
inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
|
||||||
props: {
|
props: {
|
||||||
augmentedSecurityFeatures: {
|
augmentedSecurityFeatures: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -127,9 +126,6 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
|
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
|
||||||
securityTraininDocLink: helpPagePath('user/application_security/vulnerabilities/index', {
|
|
||||||
anchor: 'enable-security-training-for-vulnerabilities',
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -268,7 +264,7 @@ export default {
|
||||||
{{ $options.i18n.securityTrainingDescription }}
|
{{ $options.i18n.securityTrainingDescription }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<gl-link :href="$options.securityTraininDocLink">{{
|
<gl-link :href="vulnerabilityTrainingDocsPath">{{
|
||||||
$options.i18n.securityTrainingDoc
|
$options.i18n.securityTrainingDoc
|
||||||
}}</gl-link>
|
}}</gl-link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -25,6 +25,7 @@ export const initSecurityConfiguration = (el) => {
|
||||||
gitlabCiHistoryPath,
|
gitlabCiHistoryPath,
|
||||||
autoDevopsHelpPagePath,
|
autoDevopsHelpPagePath,
|
||||||
autoDevopsPath,
|
autoDevopsPath,
|
||||||
|
vulnerabilityTrainingDocsPath,
|
||||||
} = el.dataset;
|
} = el.dataset;
|
||||||
|
|
||||||
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
|
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
|
||||||
|
@ -41,6 +42,7 @@ export const initSecurityConfiguration = (el) => {
|
||||||
upgradePath,
|
upgradePath,
|
||||||
autoDevopsHelpPagePath,
|
autoDevopsHelpPagePath,
|
||||||
autoDevopsPath,
|
autoDevopsPath,
|
||||||
|
vulnerabilityTrainingDocsPath,
|
||||||
},
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement(SecurityConfigurationApp, {
|
return createElement(SecurityConfigurationApp, {
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<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">
|
<gl-popover :target="() => $refs.popoverTrigger.$el" v-bind="options">
|
||||||
<template v-if="options.title" #title>
|
<template v-if="options.title" #title>
|
||||||
<span v-safe-html="options.title"></span>
|
<span v-safe-html="options.title"></span>
|
||||||
|
|
|
@ -4,6 +4,9 @@ class Admin::RunnersController < Admin::ApplicationController
|
||||||
include RunnerSetupScripts
|
include RunnerSetupScripts
|
||||||
|
|
||||||
before_action :runner, except: [:index, :tag_list, :runner_setup_scripts]
|
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
|
feature_category :runner
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,10 @@ module Projects
|
||||||
def security_upgrade_path
|
def security_upgrade_path
|
||||||
"https://#{ApplicationHelper.promo_host}/pricing/"
|
"https://#{ApplicationHelper.promo_host}/pricing/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vulnerability_training_docs_path
|
||||||
|
help_page_path('user/application_security/vulnerabilities/index', anchor: 'enable-security-training-for-vulnerabilities')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,3 +31,5 @@ module Users
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Users::GroupCalloutsHelper.prepend_mod
|
||||||
|
|
|
@ -14,7 +14,8 @@ module Users
|
||||||
storage_enforcement_banner_first_enforcement_threshold: 3,
|
storage_enforcement_banner_first_enforcement_threshold: 3,
|
||||||
storage_enforcement_banner_second_enforcement_threshold: 4,
|
storage_enforcement_banner_second_enforcement_threshold: 4,
|
||||||
storage_enforcement_banner_third_enforcement_threshold: 5,
|
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
|
validates :group, presence: true
|
||||||
|
|
|
@ -4,7 +4,9 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
|
||||||
presents ::Issue, as: :issue
|
presents ::Issue, as: :issue
|
||||||
|
|
||||||
def issue_path
|
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
|
end
|
||||||
|
|
||||||
delegator_override :subscribed?
|
delegator_override :subscribed?
|
||||||
|
@ -15,6 +17,18 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
|
||||||
def project_emails_disabled?
|
def project_emails_disabled?
|
||||||
issue.project.emails_disabled?
|
issue.project.emails_disabled?
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
IssuePresenter.prepend_mod_with('IssuePresenter')
|
IssuePresenter.prepend_mod_with('IssuePresenter')
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
- add_page_specific_style 'page_bundles/members'
|
- add_page_specific_style 'page_bundles/members'
|
||||||
- page_title _('Group members')
|
- page_title _('Group members')
|
||||||
|
|
||||||
|
= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
|
||||||
|
|
||||||
.row.gl-mt-3
|
.row.gl-mt-3
|
||||||
.col-lg-12
|
.col-lg-12
|
||||||
.gl-display-flex.gl-flex-wrap
|
.gl-display-flex.gl-flex-wrap
|
||||||
|
|
|
@ -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/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/qrtly_reconciliation_alert', group: @group
|
||||||
|
= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
|
||||||
|
|
||||||
- if show_invite_banner?(@group)
|
- if show_invite_banner?(@group)
|
||||||
= content_for :group_invite_members_banner do
|
= content_for :group_invite_members_banner do
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
= dispensable_render_if_exists "shared/namespace_user_cap_reached_alert"
|
= dispensable_render_if_exists "shared/namespace_user_cap_reached_alert"
|
||||||
= dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
|
= dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
|
||||||
= yield :page_level_alert
|
= yield :page_level_alert
|
||||||
|
= yield :user_over_limit_free_plan_alert
|
||||||
= yield :group_invite_members_banner
|
= yield :group_invite_members_banner
|
||||||
- unless @hide_breadcrumbs
|
- unless @hide_breadcrumbs
|
||||||
= render "layouts/nav/breadcrumbs"
|
= render "layouts/nav/breadcrumbs"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
- escaped_default_branch_name = default_branch_name.shellescape
|
- escaped_default_branch_name = default_branch_name.shellescape
|
||||||
- @skip_current_level_breadcrumb = true
|
- @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 partial: 'flash_messages', locals: { project: @project }
|
||||||
|
|
||||||
= render "home_panel"
|
= render "home_panel"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
- page_title _('No repository')
|
- page_title _('No repository')
|
||||||
- @skip_current_level_breadcrumb = true
|
- @skip_current_level_breadcrumb = true
|
||||||
|
|
||||||
|
= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
|
||||||
|
|
||||||
%h2.gl-display-flex
|
%h2.gl-display-flex
|
||||||
.gl-display-flex.gl-align-items-center.gl-justify-content-center
|
.gl-display-flex.gl-align-items-center.gl-justify-content-center
|
||||||
= sprite_icon('warning-solid', size: 24, css_class: 'gl-mr-2')
|
= sprite_icon('warning-solid', size: 24, css_class: 'gl-mr-2')
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
- add_page_specific_style 'page_bundles/members'
|
- add_page_specific_style 'page_bundles/members'
|
||||||
- page_title _("Members")
|
- page_title _("Members")
|
||||||
|
|
||||||
|
= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
|
||||||
|
|
||||||
.row.gl-mt-3
|
.row.gl-mt-3
|
||||||
.col-lg-12
|
.col-lg-12
|
||||||
- if can_invite_members_for_project?(@project)
|
- if can_invite_members_for_project?(@project)
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
- @content_class = "limit-container-width" unless fluid_layout
|
- @content_class = "limit-container-width" unless fluid_layout
|
||||||
|
|
||||||
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
|
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
|
||||||
|
vulnerability_training_docs_path: vulnerability_training_docs_path,
|
||||||
upgrade_path: security_upgrade_path,
|
upgrade_path: security_upgrade_path,
|
||||||
project_full_path: @project.full_path } }
|
project_full_path: @project.full_path } }
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
= content_for :meta_tags do
|
= content_for :meta_tags do
|
||||||
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
|
= 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 partial: 'flash_messages', locals: { project: @project }
|
||||||
|
|
||||||
= render "projects/last_push"
|
= render "projects/last_push"
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -14,3 +14,4 @@ swap:
|
||||||
needs? to: "Rewrite the sentence, or use 'must', instead of"
|
needs? to: "Rewrite the sentence, or use 'must', instead of"
|
||||||
note that: "Be concise: rewrite the sentence to not use"
|
note that: "Be concise: rewrite the sentence to not use"
|
||||||
please: "Remove this word from the sentence: "
|
please: "Remove this word from the sentence: "
|
||||||
|
respectively: "Rewrite the sentence to be more precise, instead of using "
|
||||||
|
|
|
@ -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. |
|
| [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` | |
|
| [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). |
|
| [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
|
## 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
|
assets, and Yarn to manage JavaScript dependencies. The current minimum
|
||||||
requirements for these are:
|
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)
|
- `yarn` = v1.22.x (Yarn 2 is not supported yet)
|
||||||
|
|
||||||
In many distributions,
|
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:
|
we need to install through the following commands:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# install node v14.x
|
# install node v16.x
|
||||||
curl --location "https://deb.nodesource.com/setup_14.x" | sudo bash -
|
curl --location "https://deb.nodesource.com/setup_16.x" | sudo bash -
|
||||||
sudo apt-get install -y nodejs
|
sudo apt-get install -y nodejs
|
||||||
|
|
||||||
npm install --global yarn
|
npm install --global yarn
|
||||||
|
|
Before Width: | Height: | Size: 43 KiB |
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
GitLab provides some dashboards out-of-the-box for any project with
|
||||||
[Prometheus available](../../../user/project/integrations/prometheus.md). You can
|
[Prometheus available](../../../user/project/integrations/prometheus.md). You can
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
GitLab provides a template to make it easier for you to create templates for
|
||||||
[custom dashboards](index.md). Templates provide helpful guidance and
|
[custom dashboards](index.md). Templates provide helpful guidance and
|
||||||
|
|
|
@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
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.
|
includes a few key metrics, but you can also define your own custom dashboards.
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
The below panel types are supported in monitoring dashboards.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
You can configure your [Monitoring dashboard](../index.md) to
|
||||||
display the time zone of your choice, and the links of your choice.
|
display the time zone of your choice, and the links of your choice.
|
||||||
|
|
|
@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
Templating variables can be used to make your metrics dashboard more versatile.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
## Query variables
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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:
|
Dashboards have several components:
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,12 @@ For removal reviewers (Technical Writers only):
|
||||||
https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc
|
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
|
## 14.9
|
||||||
|
|
||||||
### Integrated error tracking disabled by default
|
### Integrated error tracking disabled by default
|
||||||
|
|
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 18 KiB |
|
@ -422,7 +422,7 @@ The following table lists group permissions available for each role:
|
||||||
| View 2FA status of members | | | | | ✓ |
|
| View 2FA status of members | | | | | ✓ |
|
||||||
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (4) |
|
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (4) |
|
||||||
| View [Usage Quotas](usage_quotas.md) Page | | | | ✓ | ✓ (4) |
|
| View [Usage Quotas](usage_quotas.md) Page | | | | ✓ | ✓ (4) |
|
||||||
| Manage runners | | | | | ✓ |
|
| Manage group runners | | | | | ✓ |
|
||||||
|
|
||||||
<!-- markdownlint-disable MD029 -->
|
<!-- markdownlint-disable MD029 -->
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 27 KiB |
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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
|
GitLab supports automatically detecting and monitoring AWS resources, starting
|
||||||
with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/) (ELB).
|
with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/) (ELB).
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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/).
|
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/).
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
GitLab has support for automatically detecting and monitoring Kubernetes metrics.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346541)
|
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:
|
NOTE:
|
||||||
[NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
|
[NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
|
||||||
|
|
|
@ -112,7 +112,13 @@ This example shows reviewers and approval rules in a merge request sidebar:
|
||||||
|
|
||||||
### Request a new review
|
### 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),
|
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:
|
the author of the merge request can request a new review from the reviewer:
|
||||||
|
|
|
@ -15153,6 +15153,9 @@ msgstr ""
|
||||||
msgid "Explore groups"
|
msgid "Explore groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Explore paid plans"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Explore projects"
|
msgid "Explore projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -16258,6 +16261,9 @@ msgstr ""
|
||||||
msgid "From %{providerTitle}"
|
msgid "From %{providerTitle}"
|
||||||
msgstr ""
|
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"
|
msgid "From issue creation until deploy to production"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -22896,6 +22902,9 @@ msgstr ""
|
||||||
msgid "Manage labels"
|
msgid "Manage labels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Manage members"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Manage milestones"
|
msgid "Manage milestones"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32184,6 +32193,16 @@ msgstr ""
|
||||||
msgid "Runners|%{percentage} spot."
|
msgid "Runners|%{percentage} spot."
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32229,9 +32248,15 @@ msgstr ""
|
||||||
msgid "Runners|Change to project runner"
|
msgid "Runners|Change to project runner"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Checkbox"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Choose your preferred GitLab Runner"
|
msgid "Runners|Choose your preferred GitLab Runner"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Clear selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Command to register runner"
|
msgid "Runners|Command to register runner"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32244,12 +32269,20 @@ msgstr ""
|
||||||
msgid "Runners|Copy registration token"
|
msgid "Runners|Copy registration token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Delete %d runner"
|
||||||
|
msgid_plural "Runners|Delete %d runners"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "Runners|Delete runner"
|
msgid "Runners|Delete runner"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Delete runner %{name}?"
|
msgid "Runners|Delete runner %{name}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Delete selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Runners|Deploy GitLab Runner in AWS"
|
msgid "Runners|Deploy GitLab Runner in AWS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -32340,6 +32373,11 @@ msgstr ""
|
||||||
msgid "Runners|Paused"
|
msgid "Runners|Paused"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Runners|Permanently delete %d runner"
|
||||||
|
msgid_plural "Runners|Permanently delete %d runners"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "Runners|Platform"
|
msgid "Runners|Platform"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -43243,6 +43281,9 @@ msgstr ""
|
||||||
msgid "YouTube URL or ID"
|
msgid "YouTube URL or ID"
|
||||||
msgstr ""
|
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}."
|
msgid "Your %{group} membership will now expire in %{days}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -75,13 +75,16 @@ module QA
|
||||||
# Download files from GCS bucket by environment name
|
# Download files from GCS bucket by environment name
|
||||||
# Delete the files afterward
|
# Delete the files afterward
|
||||||
def download(ci_project_name)
|
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
|
arr << obj.name
|
||||||
end
|
end
|
||||||
|
|
||||||
return puts "\nNothing to download!" if files_list.empty?
|
return puts "\nNothing to download!" if files_list.blank?
|
||||||
|
|
||||||
FileUtils.mkdir_p('tmp/')
|
FileUtils.mkdir_p('tmp/')
|
||||||
|
|
||||||
files_list.each do |file_name|
|
files_list.each do |file_name|
|
||||||
local_path = "tmp/#{file_name.split('/').last}"
|
local_path = "tmp/#{file_name.split('/').last}"
|
||||||
Runtime::Logger.info("Downloading #{file_name} to #{local_path}")
|
Runtime::Logger.info("Downloading #{file_name} to #{local_path}")
|
||||||
|
|
|
@ -58,6 +58,11 @@ FactoryBot.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :task do
|
||||||
|
issue_type { :task }
|
||||||
|
association :work_item_type, :default, :task
|
||||||
|
end
|
||||||
|
|
||||||
factory :incident do
|
factory :incident do
|
||||||
issue_type { :incident }
|
issue_type { :incident }
|
||||||
association :work_item_type, :default, :incident
|
association :work_item_type, :default, :incident
|
||||||
|
|
|
@ -37,5 +37,10 @@ FactoryBot.define do
|
||||||
base_type { WorkItems::Type.base_types[:requirement] }
|
base_type { WorkItems::Type.base_types[:requirement] }
|
||||||
icon_name { 'issue-type-requirements' }
|
icon_name { 'issue-type-requirements' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :task do
|
||||||
|
base_type { WorkItems::Type.base_types[:task] }
|
||||||
|
icon_name { 'issue-type-task' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -135,7 +135,7 @@ describe('Grouped code quality reports app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not render a help icon', () => {
|
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', () => {
|
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', () => {
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { createAlert } from '~/flash';
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { updateHistory } from '~/lib/utils/url_utility';
|
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 AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
|
||||||
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
|
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
|
||||||
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.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';
|
import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data';
|
||||||
|
|
||||||
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
|
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
|
||||||
|
const mockRunners = runnersData.data.runners.nodes;
|
||||||
|
|
||||||
jest.mock('~/flash');
|
jest.mock('~/flash');
|
||||||
jest.mock('~/runner/sentry_utils');
|
jest.mock('~/runner/sentry_utils');
|
||||||
|
@ -58,6 +60,8 @@ describe('AdminRunnersApp', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let mockRunnersQuery;
|
let mockRunnersQuery;
|
||||||
let mockRunnersCountQuery;
|
let mockRunnersCountQuery;
|
||||||
|
let cacheConfig;
|
||||||
|
let localMutations;
|
||||||
|
|
||||||
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
|
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
|
||||||
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
|
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
|
||||||
|
@ -69,18 +73,30 @@ describe('AdminRunnersApp', () => {
|
||||||
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
|
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
|
||||||
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
|
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
|
||||||
|
|
||||||
const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
|
const createComponent = ({
|
||||||
|
props = {},
|
||||||
|
mountFn = shallowMountExtended,
|
||||||
|
provide,
|
||||||
|
...options
|
||||||
|
} = {}) => {
|
||||||
|
({ cacheConfig, localMutations } = createLocalState());
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
[adminRunnersQuery, mockRunnersQuery],
|
[adminRunnersQuery, mockRunnersQuery],
|
||||||
[adminRunnersCountQuery, mockRunnersCountQuery],
|
[adminRunnersCountQuery, mockRunnersCountQuery],
|
||||||
];
|
];
|
||||||
|
|
||||||
wrapper = mountFn(AdminRunnersApp, {
|
wrapper = mountFn(AdminRunnersApp, {
|
||||||
apolloProvider: createMockApollo(handlers),
|
apolloProvider: createMockApollo(handlers, {}, cacheConfig),
|
||||||
propsData: {
|
propsData: {
|
||||||
registrationToken: mockRegistrationToken,
|
registrationToken: mockRegistrationToken,
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
|
provide: {
|
||||||
|
localMutations,
|
||||||
|
...provide,
|
||||||
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,7 +189,7 @@ describe('AdminRunnersApp', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the runners list', () => {
|
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 () => {
|
it('runner item links to the runner admin page', async () => {
|
||||||
|
@ -181,7 +197,7 @@ describe('AdminRunnersApp', () => {
|
||||||
|
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
const { id, shortSha } = runnersData.data.runners.nodes[0];
|
const { id, shortSha } = mockRunners[0];
|
||||||
const numericId = getIdFromGraphQLId(id);
|
const numericId = getIdFromGraphQLId(id);
|
||||||
|
|
||||||
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
|
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 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({
|
expect(runnerActions.props()).toEqual({
|
||||||
runner,
|
runner,
|
||||||
|
@ -232,8 +248,7 @@ describe('AdminRunnersApp', () => {
|
||||||
describe('Single runner row', () => {
|
describe('Single runner row', () => {
|
||||||
let showToast;
|
let showToast;
|
||||||
|
|
||||||
const mockRunner = runnersData.data.runners.nodes[0];
|
const { id: graphqlId, shortSha } = mockRunners[0];
|
||||||
const { id: graphqlId, shortSha } = mockRunner;
|
|
||||||
const id = getIdFromGraphQLId(graphqlId);
|
const id = getIdFromGraphQLId(graphqlId);
|
||||||
const COUNT_QUERIES = 7; // Smart queries that display a filtered count of runners
|
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
|
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);
|
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', () => {
|
describe('when no runners are found', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockRunnersQuery = jest.fn().mockResolvedValue({
|
mockRunnersQuery = jest.fn().mockResolvedValue({
|
||||||
|
|
|
@ -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>`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -55,7 +55,7 @@ describe('RunnerList', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Sets runner id as a row key', () => {
|
it('Sets runner id as a row key', () => {
|
||||||
createComponent({});
|
createComponent();
|
||||||
|
|
||||||
expect(findTable().attributes('primary-key')).toBe('id');
|
expect(findTable().attributes('primary-key')).toBe('id');
|
||||||
});
|
});
|
||||||
|
@ -90,6 +90,35 @@ describe('RunnerList', () => {
|
||||||
expect(findCell({ fieldKey: 'actions' }).exists()).toBe(true);
|
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', () => {
|
describe('Scoped cell slots', () => {
|
||||||
it('Render #runner-name slot in "summary" cell', () => {
|
it('Render #runner-name slot in "summary" cell', () => {
|
||||||
createComponent(
|
createComponent(
|
||||||
|
|
|
@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -44,6 +44,10 @@ describe('~/runner/utils', () => {
|
||||||
thClass: expect.arrayContaining(mockClasses),
|
thClass: expect.arrayContaining(mockClasses),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('a field with custom options', () => {
|
||||||
|
expect(tableField({ foo: 'bar' })).toMatchObject({ foo: 'bar' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getPaginationVariables', () => {
|
describe('getPaginationVariables', () => {
|
||||||
|
|
|
@ -33,6 +33,7 @@ const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
|
||||||
const autoDevopsPath = '/autoDevopsPath';
|
const autoDevopsPath = '/autoDevopsPath';
|
||||||
const gitlabCiHistoryPath = 'test/historyPath';
|
const gitlabCiHistoryPath = 'test/historyPath';
|
||||||
const projectFullPath = 'namespace/project';
|
const projectFullPath = 'namespace/project';
|
||||||
|
const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
|
||||||
|
|
||||||
useLocalStorageSpy();
|
useLocalStorageSpy();
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ describe('App component', () => {
|
||||||
autoDevopsHelpPagePath,
|
autoDevopsHelpPagePath,
|
||||||
autoDevopsPath,
|
autoDevopsPath,
|
||||||
projectFullPath,
|
projectFullPath,
|
||||||
|
vulnerabilityTrainingDocsPath,
|
||||||
glFeatures: {
|
glFeatures: {
|
||||||
secureVulnerabilityTraining,
|
secureVulnerabilityTraining,
|
||||||
},
|
},
|
||||||
|
@ -462,9 +464,7 @@ describe('App component', () => {
|
||||||
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
|
const trainingLink = findVulnerabilityManagementTab().findComponent(GlLink);
|
||||||
|
|
||||||
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
|
expect(trainingLink.text()).toBe('Learn more about vulnerability training');
|
||||||
expect(trainingLink.attributes('href')).toBe(
|
expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
|
||||||
'/help/user/application_security/vulnerabilities/index#enable-security-training-for-vulnerabilities',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe('HelpPopover', () => {
|
||||||
|
|
||||||
it('renders a link button with an icon question', () => {
|
it('renders a link button with an icon question', () => {
|
||||||
expect(findQuestionButton().props()).toMatchObject({
|
expect(findQuestionButton().props()).toMatchObject({
|
||||||
icon: 'question',
|
icon: 'question-o',
|
||||||
variant: 'link',
|
variant: 'link',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,4 +10,10 @@ RSpec.describe Projects::Security::ConfigurationHelper do
|
||||||
|
|
||||||
it { is_expected.to eq("https://#{ApplicationHelper.promo_host}/pricing/") }
|
it { is_expected.to eq("https://#{ApplicationHelper.promo_host}/pricing/") }
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -5,19 +5,42 @@ require 'spec_helper'
|
||||||
RSpec.describe IssuePresenter do
|
RSpec.describe IssuePresenter do
|
||||||
include Gitlab::Routing.url_helpers
|
include Gitlab::Routing.url_helpers
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let(:group) { create(:group) }
|
let_it_be(:group) { create(:group) }
|
||||||
let(:project) { create(:project, group: group) }
|
let_it_be(:project) { create(:project, group: group) }
|
||||||
let(:issue) { create(:issue, project: project) }
|
let_it_be(:issue) { create(:issue, project: project) }
|
||||||
let(:presenter) { described_class.new(issue, current_user: user) }
|
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)
|
group.add_developer(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#web_url' do
|
describe '#web_url' do
|
||||||
it 'returns correct path' 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,7 +52,7 @@ RSpec.describe IssuePresenter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns subscribed' do
|
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)
|
is_expected.to be(true)
|
||||||
end
|
end
|
||||||
|
@ -37,7 +60,27 @@ RSpec.describe IssuePresenter do
|
||||||
|
|
||||||
describe '#issue_path' do
|
describe '#issue_path' do
|
||||||
it 'returns correct 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|