Commit 839729da authored by Craig Norris's avatar Craig Norris

Merge branch 'docs-aqualls-cleanup6' into 'master'

Reflow notes into body text

See merge request gitlab-org/gitlab!46843
parents 21820889 81854d99
...@@ -16,7 +16,7 @@ inherit_mode: ...@@ -16,7 +16,7 @@ inherit_mode:
- Include - Include
AllCops: AllCops:
TargetRubyVersion: 2.6 TargetRubyVersion: 2.7
TargetRailsVersion: 6.0 TargetRailsVersion: 6.0
Exclude: Exclude:
- 'vendor/**/*' - 'vendor/**/*'
......
5783f980c3c83022dd5a0173186fba4158948062 78487b6231f7f0b0ae2c6db34f1495adc47268e8
import Vue from 'vue'; // EE-specific feature. Find the implementation in the `ee/`-folder
import DevopsAdoptionApp from './components/devops_adoption_app.vue'; export default () => {};
export default () => {
const el = document.querySelector('.js-devops-adoption');
if (!el) return false;
const { emptyStateSvgPath } = el.dataset;
return new Vue({
el,
provide: {
emptyStateSvgPath,
},
render(h) {
return h(DevopsAdoptionApp);
},
});
};
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
export default {
components: {
GlLink,
GlSprintf,
},
props: {
message: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
},
};
</script>
<template>
<span class="gl-text-gray-500">
<gl-sprintf :message="message">
<template #link="{ content }">
<gl-link class="gl-display-inline-block" :href="link" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</template>
...@@ -81,7 +81,6 @@ export default { ...@@ -81,7 +81,6 @@ export default {
<div class="incident-management-list"> <div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5> <h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<gl-table <gl-table
:empty-text="$options.i18n.emptyState"
:items="integrations" :items="integrations"
:fields="$options.fields" :fields="$options.fields"
:busy="loading" :busy="loading"
...@@ -115,6 +114,14 @@ export default { ...@@ -115,6 +114,14 @@ export default {
<template #table-busy> <template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" /> <gl-loading-icon size="lg" color="dark" class="mt-3" />
</template> </template>
<template #empty>
<div
class="gl-border-t-solid gl-border-b-solid gl-border-1 gl-border gl-border-gray-100 mt-n3"
>
<p class="gl-text-gray-400 gl-py-3 gl-my-3">{{ $options.i18n.emptyState }}</p>
</div>
</template>
</gl-table> </gl-table>
</div> </div>
</template> </template>
...@@ -56,7 +56,7 @@ export default { ...@@ -56,7 +56,7 @@ export default {
data() { data() {
return { return {
loading: false, loading: false,
selectedIntegration: integrationTypes[1].value, selectedIntegration: integrationTypes[0].value,
options: integrationTypes, options: integrationTypes,
active: false, active: false,
authKey: '', authKey: '',
...@@ -88,34 +88,34 @@ export default { ...@@ -88,34 +88,34 @@ export default {
]; ];
}, },
isPrometheus() { isPrometheus() {
return this.selectedIntegration === 'prometheus'; return this.selectedIntegration === 'PROMETHEUS';
}, },
isOpsgenie() { isOpsgenie() {
return this.selectedIntegration === 'opsgenie'; return this.selectedIntegration === 'OPSGENIE';
}, },
selectedIntegrationType() { selectedIntegrationType() {
switch (this.selectedIntegration) { switch (this.selectedIntegration) {
case 'generic': { case 'HTTP': {
return { return {
url: this.generic.url, url: this.generic.url,
authKey: this.generic.authorizationKey, authKey: this.generic.authKey,
activated: this.generic.activated, active: this.generic.active,
resetKey: this.resetKey.bind(this), resetKey: this.resetKey.bind(this),
}; };
} }
case 'prometheus': { case 'PROMETHEUS': {
return { return {
url: this.prometheus.prometheusUrl, url: this.prometheus.url,
authKey: this.prometheus.authorizationKey, authKey: this.prometheus.authKey,
activated: this.prometheus.activated, active: this.prometheus.active,
resetKey: this.resetKey.bind(this, 'prometheus'), resetKey: this.resetKey.bind(this, 'PROMETHEUS'),
targetUrl: this.prometheus.prometheusApiUrl, targetUrl: this.prometheus.prometheusApiUrl,
}; };
} }
case 'opsgenie': { case 'OPSGENIE': {
return { return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl, targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
activated: this.opsgenie.activated, active: this.opsgenie.active,
}; };
} }
default: { default: {
...@@ -161,16 +161,12 @@ export default { ...@@ -161,16 +161,12 @@ export default {
}, },
}, },
mounted() { mounted() {
if ( if (this.prometheus.active || this.generic.active || !this.opsgenie.opsgenieMvcIsAvailable) {
this.prometheus.activated ||
this.generic.activated ||
!this.opsgenie.opsgenieMvcIsAvailable
) {
this.removeOpsGenieOption(); this.removeOpsGenieOption();
} else if (this.opsgenie.activated) { } else if (this.opsgenie.active) {
this.setOpsgenieAsDefault(); this.setOpsgenieAsDefault();
} }
this.active = this.selectedIntegrationType.activated; this.active = this.selectedIntegrationType.active;
this.authKey = this.selectedIntegrationType.authKey ?? ''; this.authKey = this.selectedIntegrationType.authKey ?? '';
}, },
methods: { methods: {
...@@ -183,19 +179,19 @@ export default { ...@@ -183,19 +179,19 @@ export default {
}, },
setOpsgenieAsDefault() { setOpsgenieAsDefault() {
this.options = this.options.map(el => { this.options = this.options.map(el => {
if (el.value !== 'opsgenie') { if (el.value !== 'OPSGENIE') {
return { ...el, disabled: true }; return { ...el, disabled: true };
} }
return { ...el, disabled: false }; return { ...el, disabled: false };
}); });
this.selectedIntegration = this.options.find(({ value }) => value === 'opsgenie').value; this.selectedIntegration = this.options.find(({ value }) => value === 'OPSGENIE').value;
if (this.targetUrl === null) { if (this.targetUrl === null) {
this.targetUrl = this.selectedIntegrationType.targetUrl; this.targetUrl = this.selectedIntegrationType.targetUrl;
} }
}, },
removeOpsGenieOption() { removeOpsGenieOption() {
this.options = this.options.map(el => { this.options = this.options.map(el => {
if (el.value !== 'opsgenie') { if (el.value !== 'OPSGENIE') {
return { ...el, disabled: false }; return { ...el, disabled: false };
} }
return { ...el, disabled: true }; return { ...el, disabled: true };
...@@ -204,7 +200,7 @@ export default { ...@@ -204,7 +200,7 @@ export default {
resetFormValues() { resetFormValues() {
this.testAlert.json = null; this.testAlert.json = null;
this.targetUrl = this.selectedIntegrationType.targetUrl; this.targetUrl = this.selectedIntegrationType.targetUrl;
this.active = this.selectedIntegrationType.activated; this.active = this.selectedIntegrationType.active;
}, },
dismissFeedback() { dismissFeedback() {
this.serverError = null; this.serverError = null;
...@@ -212,7 +208,7 @@ export default { ...@@ -212,7 +208,7 @@ export default {
this.isFeedbackDismissed = false; this.isFeedbackDismissed = false;
}, },
resetKey(key) { resetKey(key) {
const fn = key === 'prometheus' ? this.resetPrometheusKey() : this.resetGenericKey(); const fn = key === 'PROMETHEUS' ? this.resetPrometheusKey() : this.resetGenericKey();
return fn return fn
.then(({ data: { token } }) => { .then(({ data: { token } }) => {
...@@ -242,9 +238,10 @@ export default { ...@@ -242,9 +238,10 @@ export default {
}, },
toggleActivated(value) { toggleActivated(value) {
this.loading = true; this.loading = true;
const path = this.isOpsgenie ? this.opsgenie.formPath : this.generic.formPath;
return service return service
.updateGenericActive({ .updateGenericActive({
endpoint: this[this.selectedIntegration].formPath, endpoint: path,
params: this.isOpsgenie params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } } ? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } }, : { service: { active: value } },
...@@ -345,7 +342,7 @@ export default { ...@@ -345,7 +342,7 @@ export default {
if (this.canSaveForm) { if (this.canSaveForm) {
this.canSaveForm = false; this.canSaveForm = false;
this.active = this.selectedIntegrationType.activated; this.active = this.selectedIntegrationType.active;
} }
}, },
}, },
...@@ -402,9 +399,9 @@ export default { ...@@ -402,9 +399,9 @@ export default {
</gl-sprintf> </gl-sprintf>
</span> </span>
</gl-form-group> </gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="activated"> <gl-form-group :label="$options.i18n.activeLabel" label-for="active">
<toggle-button <toggle-button
id="activated" id="active"
:disabled-input="loading" :disabled-input="loading"
:is-loading="loading" :is-loading="loading"
:value="active" :value="active"
......
<script> <script>
import produce from 'immer';
import { s__ } from '~/locale'; 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 { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql'; import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue'; import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue'; import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue'; import SettingsFormNew from './alerts_settings_form_new.vue';
import { typeSet } from '../constants';
export default { export default {
typeSet,
i18n: {
changesSaved: s__(
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
),
},
components: { components: {
IntegrationsList, IntegrationsList,
SettingsFormOld, SettingsFormOld,
...@@ -49,6 +60,7 @@ export default { ...@@ -49,6 +60,7 @@ export default {
data() { data() {
return { return {
errored: false, errored: false,
isUpdating: false,
integrations: {}, integrations: {},
}; };
}, },
...@@ -61,16 +73,85 @@ export default { ...@@ -61,16 +73,85 @@ export default {
{ {
name: s__('AlertSettings|HTTP endpoint'), name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'), type: s__('AlertsIntegrations|HTTP endpoint'),
active: this.generic.activated, active: this.generic.active,
}, },
{ {
name: s__('AlertSettings|External Prometheus'), name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'), type: s__('AlertsIntegrations|Prometheus'),
active: this.prometheus.activated, active: this.prometheus.active,
}, },
]; ];
}, },
}, },
methods: {
onCreateNewIntegration({ type, variables }) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation:
type === this.$options.typeSet.http
? createHttpIntegrationMutation
: createPrometheusIntegrationMutation,
variables: {
...variables,
projectPath: this.projectPath,
},
update: this.updateIntegrations,
})
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) {
return createFlash({ message: error });
}
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(err => {
this.errored = true;
createFlash({ message: err });
})
.finally(() => {
this.isUpdating = false;
});
},
updateIntegrations(
store,
{
data: { httpIntegrationCreate, prometheusIntegrationCreate },
},
) {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
];
});
store.writeQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
data,
});
},
},
}; };
</script> </script>
...@@ -80,7 +161,11 @@ export default { ...@@ -80,7 +161,11 @@ export default {
:integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld" :integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
:loading="loading" :loading="loading"
/> />
<settings-form-new v-if="glFeatures.httpIntegrationsList" /> <settings-form-new
v-if="glFeatures.httpIntegrationsList"
:loading="loading"
@on-create-new-integration="onCreateNewIntegration"
/>
<settings-form-old v-else /> <settings-form-old v-else />
</div> </div>
</template> </template>
import { s__ } from '~/locale'; import { s__ } from '~/locale';
// TODO: Remove this as part of the form old removal
export const i18n = { export const i18n = {
usageSection: s__( usageSection: s__(
'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.', 'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
...@@ -39,13 +40,23 @@ export const i18n = { ...@@ -39,13 +40,23 @@ export const i18n = {
integration: s__('AlertSettings|Integration'), integration: s__('AlertSettings|Integration'),
}; };
// TODO: Delete as part of old form removal in 13.6
export const integrationTypes = [ export const integrationTypes = [
{ value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
{ value: 'OPSGENIE', text: s__('AlertSettings|Opsgenie') },
];
export const integrationTypesNew = [
{ value: '', text: s__('AlertSettings|Select integration type') }, { value: '', text: s__('AlertSettings|Select integration type') },
{ value: 'generic', text: s__('AlertSettings|HTTP Endpoint') }, ...integrationTypes,
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
]; ];
export const typeSet = {
http: 'HTTP',
prometheus: 'PROMETHEUS',
};
export const JSON_VALIDATE_DELAY = 250; export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/'; export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
......
#import "../fragments/integration_item.fragment.graphql"
mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
errors
integration {
...IntegrationItem
}
}
}
#import "../fragments/integration_item.fragment.graphql"
mutation createPrometheusIntegration($projectPath: ID!, $apiUrl: String!, $active: Boolean!) {
prometheusIntegrationCreate(
input: { projectPath: $projectPath, apiUrl: $apiUrl, active: $active }
) {
errors
integration {
...IntegrationItem
}
}
}
...@@ -48,9 +48,9 @@ export default el => { ...@@ -48,9 +48,9 @@ export default el => {
el, el,
provide: { provide: {
prometheus: { prometheus: {
activated: parseBoolean(prometheusActivated), active: parseBoolean(prometheusActivated),
prometheusUrl, url: prometheusUrl,
authorizationKey: prometheusAuthorizationKey, authKey: prometheusAuthorizationKey,
prometheusFormPath, prometheusFormPath,
prometheusResetKeyPath, prometheusResetKeyPath,
prometheusApiUrl, prometheusApiUrl,
...@@ -58,14 +58,14 @@ export default el => { ...@@ -58,14 +58,14 @@ export default el => {
generic: { generic: {
alertsSetupUrl, alertsSetupUrl,
alertsUsageUrl, alertsUsageUrl,
activated: parseBoolean(activatedStr), active: parseBoolean(activatedStr),
formPath, formPath,
authorizationKey, authKey: authorizationKey,
url, url,
}, },
opsgenie: { opsgenie: {
formPath: opsgenieMvcFormPath, formPath: opsgenieMvcFormPath,
activated: parseBoolean(opsgenieMvcEnabled), active: parseBoolean(opsgenieMvcEnabled),
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable), opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
}, },
......
...@@ -70,6 +70,7 @@ const Api = { ...@@ -70,6 +70,7 @@ const Api = {
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists', featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid', featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
billableGroupMembersPath: '/api/:version/groups/:id/billable_members', billableGroupMembersPath: '/api/:version/groups/:id/billable_members',
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
group(groupId, callback = () => {}) { group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
...@@ -106,6 +107,11 @@ const Api = { ...@@ -106,6 +107,11 @@ const Api = {
return axios.delete(url); return axios.delete(url);
}, },
containerRegistryDetails(registryId, options = {}) {
const url = Api.buildUrl(this.containerRegistryDetailsPath).replace(':id', registryId);
return axios.get(url, options);
},
groupMembers(id, options) { groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
......
...@@ -12,11 +12,19 @@ import { getLocationHash } from '../lib/utils/url_utility'; ...@@ -12,11 +12,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
$(() => { $(() => {
function toggleContainer(container, toggleState) { function toggleContainer(container, toggleState) {
const $container = $(container); const $container = $(container);
const isExpanded = $container.data('is-expanded');
$container const $collapseIcon = $container.find('.js-sidebar-collapse');
.find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down') const $expandIcon = $container.find('.js-sidebar-expand');
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); if (isExpanded && !toggleState) {
$container.data('is-expanded', false);
$collapseIcon.addClass('hidden');
$expandIcon.removeClass('hidden');
} else {
$container.data('is-expanded', true);
$expandIcon.addClass('hidden');
$collapseIcon.removeClass('hidden');
}
$container.find('.js-toggle-content').toggle(toggleState); $container.find('.js-toggle-content').toggle(toggleState);
} }
......
...@@ -626,7 +626,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d ...@@ -626,7 +626,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d
.then(({ data }) => { .then(({ data }) => {
const lines = data.map((line, index) => const lines = data.map((line, index) =>
prepareLineForRenamedFile({ prepareLineForRenamedFile({
diffViewType: state.diffViewType, diffViewType: window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType,
line, line,
diffFile, diffFile,
index, index,
...@@ -638,6 +638,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d ...@@ -638,6 +638,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d
viewer: { viewer: {
...diffFile.alternate_viewer, ...diffFile.alternate_viewer,
automaticallyCollapsed: false, automaticallyCollapsed: false,
manuallyCollapsed: false,
}, },
}); });
commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: diffFile.file_path, lines }); commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: diffFile.file_path, lines });
......
...@@ -378,8 +378,13 @@ export default { ...@@ -378,8 +378,13 @@ export default {
}, },
[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) { [types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
const file = state.diffFiles.find(f => f.file_path === filePath); const file = state.diffFiles.find(f => f.file_path === filePath);
const currentDiffLinesKey = let currentDiffLinesKey;
state.diffViewType === 'inline' ? 'highlighted_diff_lines' : 'parallel_diff_lines';
if (window.gon?.features?.unifiedDiffLines || state.diffViewType === 'inline') {
currentDiffLinesKey = 'highlighted_diff_lines';
} else {
currentDiffLinesKey = 'parallel_diff_lines';
}
file[currentDiffLinesKey] = lines; file[currentDiffLinesKey] = lines;
}, },
......
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
rolloutUserListLabel: s__('FeatureFlag|User List'), rolloutUserListLabel: s__('FeatureFlag|User List'),
rolloutUserListDescription: s__('FeatureFlag|Select a user list'), rolloutUserListDescription: s__('FeatureFlag|Select a user list'),
rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'), rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'),
defaultDropdownText: s__('FeatureFlags|Select a user list'), defaultDropdownText: s__('FeatureFlags|No user list selected'),
}, },
computed: { computed: {
...mapGetters(['hasUserLists', 'isLoading', 'hasError', 'userListOptions']), ...mapGetters(['hasUserLists', 'isLoading', 'hasError', 'userListOptions']),
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
return this.strategy?.userList?.id ?? ''; return this.strategy?.userList?.id ?? '';
}, },
dropdownText() { dropdownText() {
return this.strategy?.userList?.name ?? this.$options.defaultDropdownText; return this.strategy?.userList?.name ?? this.$options.translations.defaultDropdownText;
}, },
}, },
mounted() { mounted() {
......
...@@ -116,7 +116,7 @@ export default { ...@@ -116,7 +116,7 @@ export default {
<gl-dropdown <gl-dropdown
v-if="displayFilters" v-if="displayFilters"
id="discussion-filter-dropdown" id="discussion-filter-dropdown"
class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container qa-discussion-filter" class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container"
data-qa-selector="discussion_filter_dropdown" data-qa-selector="discussion_filter_dropdown"
:text="currentFilter.title" :text="currentFilter.title"
> >
......
...@@ -65,8 +65,8 @@ export default { ...@@ -65,8 +65,8 @@ export default {
}; };
}, },
computed: { computed: {
toggleChevronClass() { toggleChevronIconName() {
return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down'; return this.expanded ? 'chevron-up' : 'chevron-down';
}, },
noteTimestampLink() { noteTimestampLink() {
return this.noteId ? `#note_${this.noteId}` : undefined; return this.noteId ? `#note_${this.noteId}` : undefined;
...@@ -133,7 +133,7 @@ export default { ...@@ -133,7 +133,7 @@ export default {
type="button" type="button"
@click="handleToggle" @click="handleToggle"
> >
<i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i> <gl-icon ref="chevronIcon" :name="toggleChevronIconName" aria-hidden="true" />
{{ __('Toggle thread') }} {{ __('Toggle thread') }}
</button> </button>
</div> </div>
......
import initDevopAdoption from 'ee_else_ce/admin/dev_ops_report/devops_adoption';
import initDevOpsScoreEmptyState from '~/admin/dev_ops_report/devops_score_empty_state'; import initDevOpsScoreEmptyState from '~/admin/dev_ops_report/devops_score_empty_state';
import initDevopAdoption from '~/admin/dev_ops_report/devops_adoption';
initDevOpsScoreEmptyState(); initDevOpsScoreEmptyState();
initDevopAdoption(); initDevopAdoption();
...@@ -15,6 +15,10 @@ export const DELETE_TAGS_SUCCESS_MESSAGE = s__( ...@@ -15,6 +15,10 @@ export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tags successfully marked for deletion.', 'ContainerRegistry|Tags successfully marked for deletion.',
); );
export const FETCH_IMAGE_DETAILS_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while fetching the image details.',
);
export const TAGS_LIST_TITLE = s__('ContainerRegistry|Image tags'); export const TAGS_LIST_TITLE = s__('ContainerRegistry|Image tags');
export const DIGEST_LABEL = s__('ContainerRegistry|Digest: %{imageId}'); export const DIGEST_LABEL = s__('ContainerRegistry|Digest: %{imageId}');
export const CREATED_AT_LABEL = s__('ContainerRegistry|Published %{timeInfo}'); export const CREATED_AT_LABEL = s__('ContainerRegistry|Published %{timeInfo}');
......
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import Api from '~/api';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { import {
FETCH_IMAGES_LIST_ERROR_MESSAGE, FETCH_IMAGES_LIST_ERROR_MESSAGE,
DEFAULT_PAGE, DEFAULT_PAGE,
DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE, FETCH_TAGS_LIST_ERROR_MESSAGE,
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
} from '../constants/index'; } from '../constants/index';
import { decodeAndParse } from '../utils'; import { decodeAndParse } from '../utils';
...@@ -61,6 +63,19 @@ export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params ...@@ -61,6 +63,19 @@ export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params
}); });
}; };
export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
commit(types.SET_MAIN_LOADING, true);
return Api.containerRegistryDetails(id)
.then(({ data }) => {
commit(types.SET_IMAGE_DETAILS, data);
dispatch('requestTagsList');
})
.catch(() => {
createFlash(FETCH_IMAGE_DETAILS_ERROR_MESSAGE);
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => { export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => {
commit(types.SET_MAIN_LOADING, true); commit(types.SET_MAIN_LOADING, true);
return axios return axios
......
...@@ -7,3 +7,4 @@ export const SET_MAIN_LOADING = 'SET_MAIN_LOADING'; ...@@ -7,3 +7,4 @@ export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION'; export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS'; export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP'; export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP';
export const SET_IMAGE_DETAILS = 'SET_IMAGE_DETAILS';
...@@ -47,4 +47,8 @@ export default { ...@@ -47,4 +47,8 @@ export default {
const normalizedHeaders = normalizeHeaders(headers); const normalizedHeaders = normalizeHeaders(headers);
state.tagsPagination = parseIntPagination(normalizedHeaders); state.tagsPagination = parseIntPagination(normalizedHeaders);
}, },
[types.SET_IMAGE_DETAILS](state, details) {
state.imageDetails = details;
},
}; };
...@@ -3,6 +3,7 @@ export default () => ({ ...@@ -3,6 +3,7 @@ export default () => ({
showGarbageCollectionTip: false, showGarbageCollectionTip: false,
config: {}, config: {},
images: [], images: [],
imageDetails: {},
tags: [], tags: [],
pagination: {}, pagination: {},
tagsPagination: {}, tagsPagination: {},
......
export const decodeAndParse = param => JSON.parse(window.atob(param)); export const decodeAndParse = param => JSON.parse(window.atob(param));
// eslint-disable-next-line @gitlab/require-i18n-strings
export const pathGenerator = (imageDetails, ending = 'tags?format=json') => {
// this method is a temporary workaround, to be removed with graphql implementation
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
const basePath = imageDetails.path.replace(`/${imageDetails.name}`, '');
return `/${basePath}/registry/repository/${imageDetails.id}/${ending}`;
};
...@@ -109,10 +109,6 @@ ...@@ -109,10 +109,6 @@
content: '\f0da'; content: '\f0da';
} }
.fa-chevron-up::before {
content: '\f077';
}
.fa-exclamation-circle::before { .fa-exclamation-circle::before {
content: '\f06a'; content: '\f06a';
} }
......
...@@ -51,7 +51,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -51,7 +51,7 @@ class Projects::IssuesController < Projects::ApplicationController
real_time_feature_flag = :real_time_issue_sidebar real_time_feature_flag = :real_time_issue_sidebar
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project) real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project)
gon.push({ features: { real_time_feature_flag.to_s.camelize(:lower) => real_time_enabled } }, true) push_to_gon_features(real_time_feature_flag, real_time_enabled)
record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b) record_experiment_user(:invite_members_version_b)
......
...@@ -32,7 +32,7 @@ module FinderWithCrossProjectAccess ...@@ -32,7 +32,7 @@ module FinderWithCrossProjectAccess
end end
override :execute override :execute
def execute(*args) def execute(*args, **kwargs)
check = Gitlab::CrossProjectAccess.find_check(self) check = Gitlab::CrossProjectAccess.find_check(self)
original = -> { super } original = -> { super }
......
...@@ -30,7 +30,7 @@ module OperationsHelper ...@@ -30,7 +30,7 @@ module OperationsHelper
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'), 'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_usage_url' => project_alert_management_index_path(@project), 'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s, 'disabled' => disabled.to_s,
'project_path' => project_path(@project) 'project_path' => @project.full_path
} }
end end
......
- breadcrumb_title _("Dashboard") - breadcrumb_title _("Dashboard")
- page_title _("Dashboard") - page_title _("Dashboard")
- billable_users_url = help_page_path('subscriptions/self_managed/index', anchor: 'billable-users')
- billable_users_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer nofollow">'.html_safe % { url: billable_users_url }
- if @notices - if @notices
- @notices.each do |notice| - @notices.each do |notice|
...@@ -22,10 +24,20 @@ ...@@ -22,10 +24,20 @@
= link_to(s_('AdminArea|New project'), new_project_path, class: "btn gl-button btn-success gl-w-full") = link_to(s_('AdminArea|New project'), new_project_path, class: "btn gl-button btn-success gl-w-full")
.col-sm-4 .col-sm-4
.info-well.dark-well .info-well.dark-well
.well-segment.well-centered .well-segment.well-centered.gl-text-center
= link_to admin_users_path do = link_to admin_users_path do
%h3.text-center %h3.gl-display-inline-block.gl-mb-0
= s_('AdminArea|Users: %{number_of_users}') % { number_of_users: approximate_count_with_delimiters(@counts, User) } = s_('AdminArea|Users: %{number_of_users}') % { number_of_users: approximate_count_with_delimiters(@counts, User) }
%span.gl-outline-0.gl-ml-2{ href: "#", tabindex: "0", data: { container: "body",
toggle: "popover",
placement: "top",
html: "true",
trigger: "focus",
content: s_("AdminArea|All users created in the instance, including users who are not %{billable_users_link_start}billable users%{billable_users_link_end}.").html_safe % { billable_users_link_start: billable_users_link_start, billable_users_link_end: '</a>'.html_safe },
} }
= sprite_icon('question', size: 16, css_class: 'gl-text-gray-700 gl-mb-1')
%hr %hr
.btn-group.d-flex{ role: 'group' } .btn-group.d-flex{ role: 'group' }
= link_to s_('AdminArea|New user'), new_admin_user_path, class: "btn gl-button btn-success gl-w-full" = link_to s_('AdminArea|New user'), new_admin_user_path, class: "btn gl-button btn-success gl-w-full"
......
...@@ -4,17 +4,7 @@ ...@@ -4,17 +4,7 @@
.container .container
.gl-mt-3 .gl-mt-3
- if Feature.enabled?(:devops_adoption) - if Feature.enabled?(:devops_adoption)
%h2 = render_if_exists 'admin/dev_ops_report/devops_tabs'
= _('DevOps Report')
%ul.nav-links.nav-tabs.nav.js-devops-tabs{ role: 'tablist' }
= render 'tab', active: true, title: _('DevOps Score'), target: '#devops_score_pane'
= render 'tab', active: false, title: _('Adoption'), target: '#devops_adoption_pane'
.tab-content
.tab-pane.active#devops_score_pane
= render 'report'
.tab-pane#devops_adoption_pane
.js-devops-adoption{ data: { empty_state_svg_path: image_path('illustrations/monitoring/getting_started.svg') } }
- else - else
= render 'report' = render 'report'
...@@ -2,17 +2,15 @@ ...@@ -2,17 +2,15 @@
%li.note.note-discussion.timeline-entry.unstyled-comments %li.note.note-discussion.timeline-entry.unstyled-comments
.timeline-entry-inner .timeline-entry-inner
.timeline-content .timeline-content
.discussion.js-toggle-container{ data: { discussion_id: discussion.id } } .discussion.js-toggle-container{ data: { discussion_id: discussion.id, is_expanded: expanded.to_s } }
.discussion-header .discussion-header
.timeline-icon .timeline-icon
= link_to user_path(discussion.author) do = link_to user_path(discussion.author) do
= image_tag avatar_icon_for_user(discussion.author), class: "avatar s40" = image_tag avatar_icon_for_user(discussion.author), class: "avatar s40"
.discussion-actions .discussion-actions
%button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) } %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
- if expanded = sprite_icon('chevron-up', css_class: "js-sidebar-collapse #{'hidden' unless expanded}")
= icon("chevron-up") = sprite_icon('chevron-down', css_class: "js-sidebar-expand #{'hidden' if expanded}")
- else
= icon("chevron-down")
= _('Toggle thread') = _('Toggle thread')
= link_to_member(@project, discussion.author, avatar: false) = link_to_member(@project, discussion.author, avatar: false)
......
---
title: Add EC2 to AutoDevOps template
merge_request: 45651
author:
type: changed
---
title: Replace fa-chevron-up with GitLab SVG icon
merge_request: 46118
author:
type: changed
---
title: Remove all records from `security_findings` table
merge_request: 44312
author:
type: fixed
---
title: Remove feedback alert from on-demand scans form
merge_request: 45217
author:
type: changed
---
title: Fix logging handling for API integer params
merge_request: 46551
author:
type: fixed
---
title: Assign new incoming diff lines for renamed files to the correct view type
merge_request: 46823
author:
type: fixed
---
title: Fix example responses for Group Issue Board creation API in the docs
merge_request: 46760
author: Takuya Noguchi
type: fixed
---
title: Fix compliance framework database migration on CE instances
merge_request: 46761
author:
type: fixed
---
title: Show "No user list selected" in feature flags
merge_request: 46790
author:
type: fixed
---
title: Add total projects imported usage ping
merge_request: 46541
author:
type: added
---
title: Fix 'File name too long' error happening during Project Export when exporting
project uploads
merge_request: 46674
author:
type: fixed
---
name: incident_sla
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648
rollout_issue_url:
group: group::health
type: licensed
default_enabled: true
...@@ -14,10 +14,8 @@ if Gitlab.ee? && Gitlab.dev_or_test_env? ...@@ -14,10 +14,8 @@ if Gitlab.ee? && Gitlab.dev_or_test_env?
# being unique to licensed names. These feature flags should be reworked to # being unique to licensed names. These feature flags should be reworked to
# be "development" with explicit check # be "development" with explicit check
IGNORED_FEATURE_FLAGS = %i[ IGNORED_FEATURE_FLAGS = %i[
ci_secrets_management
feature_flags_related_issues feature_flags_related_issues
group_wikis group_wikis
incident_sla
swimlanes swimlanes
minimal_access_role minimal_access_role
].to_set ].to_set
......
...@@ -11,7 +11,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r ...@@ -11,7 +11,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r
env: req.env['rack.attack.match_type'], env: req.env['rack.attack.match_type'],
remote_ip: req.ip, remote_ip: req.ip,
request_method: req.request_method, request_method: req.request_method,
path: req.fullpath path: req.fullpath,
matched: req.env['rack.attack.matched']
} }
throttles_with_user_information = [ throttles_with_user_information = [
...@@ -25,9 +26,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r ...@@ -25,9 +26,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r
user_id = req.env['rack.attack.match_discriminator'] user_id = req.env['rack.attack.match_discriminator']
user = User.find_by(id: user_id) user = User.find_by(id: user_id)
rack_attack_info[:throttle_type] = req.env['rack.attack.matched']
rack_attack_info[:user_id] = user_id rack_attack_info[:user_id] = user_id
rack_attack_info[:username] = user.username unless user.nil? rack_attack_info['meta.user'] = user.username unless user.nil?
end end
Gitlab::AuthLogger.error(rack_attack_info) Gitlab::AuthLogger.error(rack_attack_info)
......
...@@ -52,8 +52,6 @@ class MigrateComplianceFrameworkEnumToDatabaseFrameworkRecord < ActiveRecord::Mi ...@@ -52,8 +52,6 @@ class MigrateComplianceFrameworkEnumToDatabaseFrameworkRecord < ActiveRecord::Mi
end end
def up def up
return unless Gitlab.ee?
TmpComplianceFramework.reset_column_information TmpComplianceFramework.reset_column_information
TmpProjectSettings.reset_column_information TmpProjectSettings.reset_column_information
......
# frozen_string_literal: true
class TruncateSecurityFindingsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
return unless Gitlab.dev_env_or_com?
with_lock_retries do
connection.execute('TRUNCATE security_findings RESTART IDENTITY')
end
end
def down
# no-op
end
end
55ffd18d5f55ee0fd51a31d50cf2d51595740c72ca23d5134d93e2da3fc186ff
\ No newline at end of file
...@@ -631,7 +631,7 @@ GET /projects/:id/repository/commits/:sha/statuses ...@@ -631,7 +631,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `sha` | string | yes | The commit SHA | `sha` | string | yes | The commit SHA
| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch | `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test` | `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
| `name` | string | no | Filter by [job name](../ci/yaml/README.md#introduction), e.g., `bundler:audit` | `name` | string | no | Filter by [job name](../ci/yaml/README.md#job-keywords), e.g., `bundler:audit`
| `all` | boolean | no | Return all statuses, not only the latest ones | `all` | boolean | no | Return all statuses, not only the latest ones
```shell ```shell
......
...@@ -1439,6 +1439,11 @@ type BoardEpic implements CurrentUserTodos & Noteable { ...@@ -1439,6 +1439,11 @@ type BoardEpic implements CurrentUserTodos & Noteable {
""" """
iids: [ID!] iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
""" """
Filter epics by labels Filter epics by labels
""" """
...@@ -6592,6 +6597,11 @@ type Epic implements CurrentUserTodos & Noteable { ...@@ -6592,6 +6597,11 @@ type Epic implements CurrentUserTodos & Noteable {
""" """
iids: [ID!] iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
""" """
Filter epics by labels Filter epics by labels
""" """
...@@ -8166,6 +8176,11 @@ type Group { ...@@ -8166,6 +8176,11 @@ type Group {
""" """
iids: [ID!] iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
""" """
Filter epics by labels Filter epics by labels
""" """
...@@ -8249,6 +8264,11 @@ type Group { ...@@ -8249,6 +8264,11 @@ type Group {
""" """
iids: [ID!] iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
""" """
Filter epics by labels Filter epics by labels
""" """
......
...@@ -3867,6 +3867,16 @@ ...@@ -3867,6 +3867,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -18288,6 +18298,16 @@ ...@@ -18288,6 +18298,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -22575,6 +22595,16 @@ ...@@ -22575,6 +22595,16 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
} }
], ],
"type": { "type": {
...@@ -22725,6 +22755,16 @@ ...@@ -22725,6 +22755,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -265,44 +265,17 @@ Example response: ...@@ -265,44 +265,17 @@ Example response:
{ {
"id": 1, "id": 1,
"name": "newboard", "name": "newboard",
"project": null,
"lists" : [],
"group": { "group": {
"id": 5, "id": 5,
"name": "Documentcloud", "name": "Documentcloud",
"web_url": "http://example.com/groups/documentcloud" "web_url": "http://example.com/groups/documentcloud"
}, },
"milestone": { "milestone": null,
"id": 12 "assignee" : null,
"title": "10.0" "labels" : [],
}, "weight" : null
"lists" : [
{
"id" : 1,
"label" : {
"name" : "Testing",
"color" : "#F0AD4E",
"description" : null
},
"position" : 1
},
{
"id" : 2,
"label" : {
"name" : "Ready",
"color" : "#FF0000",
"description" : null
},
"position" : 2
},
{
"id" : 3,
"label" : {
"name" : "Production",
"color" : "#FF5F00",
"description" : null
},
"position" : 3
}
]
} }
``` ```
......
...@@ -282,6 +282,32 @@ When running your project pipeline at this point: ...@@ -282,6 +282,32 @@ When running your project pipeline at this point:
on the related JSON object's content. The deployment job finishes whenever the deployment to EC2 on the related JSON object's content. The deployment job finishes whenever the deployment to EC2
is done or has failed. is done or has failed.
#### Custom build job for Auto DevOps
To leverage [Auto DevOps](../../topics/autodevops/index.md) for your project when deploying to
AWS EC2, you must specify a job for the `build` stage.
To do so, you must reference the `Auto-DevOps.gitlab-ci.yml` template and include a job named
`build_artifact` in your `.gitlab-ci.yml` file. For example:
```yaml
# .gitlab-ci.yml
include:
- template: Auto-DevOps.gitlab-ci.yml
variables:
- AUTO_DEVOPS_PLATFORM_TARGET: EC2
build_artifact:
stage: build
script:
- <your build script goes here>
artifacts:
paths:
- <built artifact>
```
### Deploy to Amazon EKS ### Deploy to Amazon EKS
- [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/) - [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)
......
...@@ -34,8 +34,7 @@ currently being deployed or has been deployed on your servers. ...@@ -34,8 +34,7 @@ currently being deployed or has been deployed on your servers.
It's important to know that: It's important to know that:
- Environments are like tags for your CI jobs, describing where code gets deployed. - Environments are like tags for your CI jobs, describing where code gets deployed.
- Deployments are created when [jobs](../yaml/README.md#introduction) deploy versions of code to environments, - Deployments are created when [GitLab CI/CD](../yaml/README.md) is used to deploy versions of code to environments.
so every environment can have one or more deployments.
GitLab: GitLab:
......
...@@ -140,7 +140,7 @@ new browser window interacting with your app as you specified. ...@@ -140,7 +140,7 @@ new browser window interacting with your app as you specified.
Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we
need to do for this: need to do for this:
1. Set up [CI/CD jobs](../../yaml/README.md#introduction) that actually have a browser available. 1. Set up [CI/CD jobs](../../yaml/README.md) that actually have a browser available.
1. Update our WebdriverIO configuration to use those browsers to visit the review apps. 1. Update our WebdriverIO configuration to use those browsers to visit the review apps.
For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages) For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
......
...@@ -17,6 +17,14 @@ Out-of-the-box management systems can decrease hours spent on maintaining toolch ...@@ -17,6 +17,14 @@ Out-of-the-box management systems can decrease hours spent on maintaining toolch
Watch our ["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/) Watch our ["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development. webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
> For some additional information about GitLab CI/CD:
>
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Watch the [CI/CD Ease of configuration](https://www.youtube.com/embed/opdLqwz6tcE) video.
> - Watch the [Making the case for CI/CD in your organization](https://about.gitlab.com/compare/github-actions-alternative/)
> webcast to learn the benefits of CI/CD and how to measure the results of CI/CD automation.
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn how [Verizon reduced rebuilds](https://about.gitlab.com/blog/2019/02/14/verizon-customer-story/)
> from 30 days to under 8 hours with GitLab.
## Introduction to CI/CD methodologies ## Introduction to CI/CD methodologies
The continuous methodologies of software development are based on The continuous methodologies of software development are based on
......
...@@ -27,7 +27,7 @@ CircleCI's `config.yml` configuration file defines scripts, jobs, and workflows ...@@ -27,7 +27,7 @@ CircleCI's `config.yml` configuration file defines scripts, jobs, and workflows
### Jobs ### Jobs
In CircleCI, jobs are a collection of steps to perform a specific task. In GitLab, [jobs](../yaml/README.md#introduction) are also a fundamental element in the configuration file. The `checkout` keyword is not necessary in GitLab CI/CD as the repository is automatically fetched. In CircleCI, jobs are a collection of steps to perform a specific task. In GitLab, [jobs](../pipelines/index.md#about-jobs) are also a fundamental element in the configuration file. The `checkout` keyword is not necessary in GitLab CI/CD as the repository is automatically fetched.
CircleCI example job definition: CircleCI example job definition:
......
...@@ -68,7 +68,7 @@ Pipelines can be configured in many different ways: ...@@ -68,7 +68,7 @@ Pipelines can be configured in many different ways:
Pipelines and their component jobs and stages are defined in the CI/CD pipeline configuration file for each project. Pipelines and their component jobs and stages are defined in the CI/CD pipeline configuration file for each project.
- Jobs are the [basic configuration](../yaml/README.md#introduction) component. - Jobs are the [basic configuration](#about-jobs) component.
- Stages are defined by using the [`stages`](../yaml/README.md#stages) keyword. - Stages are defined by using the [`stages`](../yaml/README.md#stages) keyword.
For a list of configuration options in the CI pipeline file, see the [GitLab CI/CD Pipeline Configuration Reference](../yaml/README.md). For a list of configuration options in the CI pipeline file, see the [GitLab CI/CD Pipeline Configuration Reference](../yaml/README.md).
...@@ -287,7 +287,36 @@ preserving deployment keys and other credentials from being unintentionally ...@@ -287,7 +287,36 @@ preserving deployment keys and other credentials from being unintentionally
accessed. In order to ensure that jobs intended to be executed on protected accessed. In order to ensure that jobs intended to be executed on protected
runners do not use regular runners, they must be tagged accordingly. runners do not use regular runners, they must be tagged accordingly.
## View jobs in a pipeline ## About jobs
Pipeline configuration begins with jobs. Jobs are the most fundamental element of a `.gitlab-ci.yml` file.
Jobs are:
- Defined with constraints stating under what conditions they should be executed.
- Top-level elements with an arbitrary name and must contain at least the [`script`](../yaml/README.md#script) clause.
- Not limited in how many can be defined.
For example:
```yaml
job1:
script: "execute-script-for-job1"
job2:
script: "execute-script-for-job2"
```
The above example is the simplest possible CI/CD configuration with two separate
jobs, where each of the jobs executes a different command.
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
Jobs are picked up by [runners](../runners/README.md) and executed within the
environment of the runner. What is important is that each job is run
independently from each other.
### View jobs in a pipeline
When you access a pipeline, you can see the related jobs for that pipeline. When you access a pipeline, you can see the related jobs for that pipeline.
......
...@@ -13,7 +13,12 @@ GitLab offers a [continuous integration](https://about.gitlab.com/stages-devops- ...@@ -13,7 +13,12 @@ GitLab offers a [continuous integration](https://about.gitlab.com/stages-devops-
- Add a [`.gitlab-ci.yml` file](#creating-a-gitlab-ciyml-file) to your repository's root directory. - Add a [`.gitlab-ci.yml` file](#creating-a-gitlab-ciyml-file) to your repository's root directory.
- Ensure your project is configured to use a [runner](#configuring-a-runner). - Ensure your project is configured to use a [runner](#configuring-a-runner).
The `.gitlab-ci.yml` file tells the runner what to do. A simple pipeline commonly has The `.gitlab-ci.yml` file defines the structure and order of the pipelines, and determines:
- What to execute using [GitLab Runner](https://docs.gitlab.com/runner/).
- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
A simple pipeline commonly has
three [stages](../yaml/README.md#stages): three [stages](../yaml/README.md#stages):
- `build` - `build`
......
...@@ -225,7 +225,7 @@ should disable **Pipelines must succeed** so you can accept merge requests. ...@@ -225,7 +225,7 @@ should disable **Pipelines must succeed** so you can accept merge requests.
Pipeline configuration warnings are shown when you: Pipeline configuration warnings are shown when you:
- [Validate configuration with the CI Lint tool](yaml/README.md#validate-the-gitlab-ciyml). - [Validate configuration with the CI Lint tool](yaml/README.md).
- [Manually run a pipeline](pipelines/index.md#run-a-pipeline-manually). - [Manually run a pipeline](pipelines/index.md#run-a-pipeline-manually).
### "Job may allow multiple pipelines to run for a single action" warning ### "Job may allow multiple pipelines to run for a single action" warning
......
...@@ -7,92 +7,16 @@ type: reference ...@@ -7,92 +7,16 @@ type: reference
# GitLab CI/CD pipeline configuration reference # GitLab CI/CD pipeline configuration reference
GitLab CI/CD [pipelines](../pipelines/index.md) are configured using a YAML file called `.gitlab-ci.yml` within each project. This document lists the configuration options for your GitLab `.gitlab-ci.yml` file.
The `.gitlab-ci.yml` file defines the structure and order of the pipelines and determines: - For a quick introduction to GitLab CI/CD, follow the [quick start guide](../quick_start/README.md).
- What to execute using [GitLab Runner](https://docs.gitlab.com/runner/).
- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
This topic covers CI/CD pipeline configuration. For other CI/CD configuration information, see:
- [GitLab CI/CD Variables](../variables/README.md), for configuring the environment the pipelines run in.
- [GitLab Runner advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html), for configuring GitLab Runner.
We have complete examples of configuring pipelines:
- For a quick introduction to GitLab CI/CD, follow our [quick start guide](../quick_start/README.md).
- For a collection of examples, see [GitLab CI/CD Examples](../examples/README.md). - For a collection of examples, see [GitLab CI/CD Examples](../examples/README.md).
- To see a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml). - To view a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml).
> For some additional information about GitLab CI/CD:
>
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Watch the [CI/CD Ease of configuration](https://www.youtube.com/embed/opdLqwz6tcE) video.
> - Watch the [Making the case for CI/CD in your organization](https://about.gitlab.com/compare/github-actions-alternative/)
> webcast to learn the benefits of CI/CD and how to measure the results of CI/CD automation.
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn how [Verizon reduced rebuilds](https://about.gitlab.com/blog/2019/02/14/verizon-customer-story/)
> from 30 days to under 8 hours with GitLab.
If you have a [mirrored repository that GitLab pulls from](../../user/project/repository/repository_mirroring.md#pulling-from-a-remote-repository),
you may need to enable pipeline triggering. Go to your project's **Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
## Introduction
Pipeline configuration begins with jobs. Jobs are the most fundamental element of a `.gitlab-ci.yml` file.
Jobs are: While you are authoring your `.gitlab-ci.yml` file, you can validate it
by using the [CI Lint](../lint.md) tool.
- Defined with constraints stating under what conditions they should be executed.
- Top-level elements with an arbitrary name and must contain at least the [`script`](#script) clause.
- Not limited in how many can be defined.
For example:
```yaml
job1:
script: "execute-script-for-job1"
job2:
script: "execute-script-for-job2"
```
The above example is the simplest possible CI/CD configuration with two separate
jobs, where each of the jobs executes a different command.
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
Jobs are picked up by [runners](../runners/README.md) and executed within the
environment of the runner. What is important is that each job is run
independently from each other.
### Validate the `.gitlab-ci.yml`
Each instance of GitLab CI/CD has an embedded debug tool called Lint, which validates the
content of your `.gitlab-ci.yml` files. You can find the Lint under the page `ci/lint` of your
project namespace. For example, `https://gitlab.example.com/gitlab-org/project-123/-/ci/lint`. project namespace. For example, `https://gitlab.example.com/gitlab-org/project-123/-/ci/lint`.
### Unavailable names for jobs
Each job must have a unique name, but there are a few **reserved `keywords` that
can't be used as job names**:
- `image`
- `services`
- `stages`
- `types`
- `before_script`
- `after_script`
- `variables`
- `cache`
- `include`
### Using reserved keywords
If you get validation error when using specific values (for example, `true` or `false`), try to:
- Quote them.
- Change them to a different form. For example, `/bin/true`.
## Job keywords ## Job keywords
A job is defined as a list of keywords that define the job's behavior. A job is defined as a list of keywords that define the job's behavior.
...@@ -130,10 +54,32 @@ The following table lists available keywords for jobs: ...@@ -130,10 +54,32 @@ The following table lists available keywords for jobs:
| [`variables`](#variables) | Define job variables on a job level. | | [`variables`](#variables) | Define job variables on a job level. |
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. | | [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
### Unavailable names for jobs
Each job must have a unique name, but there are a few **reserved `keywords` that
can't be used as job names**:
- `image`
- `services`
- `stages`
- `types`
- `before_script`
- `after_script`
- `variables`
- `cache`
- `include`
## Global keywords ## Global keywords
Some keywords must be defined at a global level, affecting all jobs in the pipeline. Some keywords must be defined at a global level, affecting all jobs in the pipeline.
### Using reserved keywords
If you get validation error when using specific values (for example, `true` or `false`), try to:
- Quote them.
- Change them to a different form. For example, `/bin/true`.
### Global defaults ### Global defaults
Some keywords can be set globally as the default for all jobs using the Some keywords can be set globally as the default for all jobs using the
......
...@@ -133,6 +133,7 @@ from: ...@@ -133,6 +133,7 @@ from:
- [Approval Rules](approval_rules.md) - [Approval Rules](approval_rules.md)
- [Feature categorization](feature_categorization/index.md) - [Feature categorization](feature_categorization/index.md)
- [Wikis development guide](wikis.md) - [Wikis development guide](wikis.md)
- [Newlines style guide](newlines_styleguide.md)
## Performance guides ## Performance guides
......
...@@ -132,8 +132,23 @@ Non-nullable fields should only be used when a field is required, very unlikely ...@@ -132,8 +132,23 @@ Non-nullable fields should only be used when a field is required, very unlikely
to become optional in the future, and very easy to calculate. An example would to become optional in the future, and very easy to calculate. An example would
be `id` fields. be `id` fields.
A non-nullable GraphQL schema field is an object type followed by the exclamation point (bang) `!`. Here's an example from the `gitlab_schema.graphql` file:
```graphql
id: ProjectID!
```
Here's an example of a non-nullable GraphQL array:
```graphql
errors: [String!]!
```
Further reading: Further reading:
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability).
- GraphQL documentation on [Object types and fields](https://graphql.org/learn/schema/#object-types-and-fields).
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability) - [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability)
- [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7) - [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7)
......
...@@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Security scanner integration # Security scanner integration
Integrating a security scanner into GitLab consists of providing end users Integrating a security scanner into GitLab consists of providing end users
with a [CI job definition](../../ci/yaml/README.md#introduction) with a [CI job definition](../../ci/yaml/README.md)
they can add to their CI configuration files to scan their GitLab projects. they can add to their CI configuration files to scan their GitLab projects.
This CI job should then output its results in a GitLab-specified format. These results are then This CI job should then output its results in a GitLab-specified format. These results are then
automatically presented in various places in GitLab, such as the Pipeline view, Merge Request automatically presented in various places in GitLab, such as the Pipeline view, Merge Request
......
...@@ -90,6 +90,8 @@ The GitLab installation consists of setting up the following components: ...@@ -90,6 +90,8 @@ The GitLab installation consists of setting up the following components:
## 1. Packages and dependencies ## 1. Packages and dependencies
### sudo
`sudo` is not installed on Debian by default. Make sure your system is `sudo` is not installed on Debian by default. Make sure your system is
up-to-date and install it. up-to-date and install it.
...@@ -110,6 +112,8 @@ sudo apt-get install -y vim ...@@ -110,6 +112,8 @@ sudo apt-get install -y vim
sudo update-alternatives --set editor /usr/bin/vim.basic sudo update-alternatives --set editor /usr/bin/vim.basic
``` ```
### Build dependencies
Install the required packages (needed to compile Ruby and native extensions to Ruby gems): Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
```shell ```shell
...@@ -129,6 +133,8 @@ If you want to use Kerberos for user authentication, install `libkrb5-dev` ...@@ -129,6 +133,8 @@ If you want to use Kerberos for user authentication, install `libkrb5-dev`
sudo apt-get install libkrb5-dev sudo apt-get install libkrb5-dev
``` ```
### Git
Make sure you have the right version of Git installed: Make sure you have the right version of Git installed:
```shell ```shell
...@@ -168,18 +174,9 @@ On Debian, use the following compilation instructions: ...@@ -168,18 +174,9 @@ On Debian, use the following compilation instructions:
```shell ```shell
# Install dependencies # Install dependencies
sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev libpcre2-dev build-essential
# Download and compile pcre2 from source
curl --silent --show-error --location https://ftp.pcre.org/pub/pcre/pcre2-10.33.tar.gz --output pcre2.tar.gz
tar -xzf pcre2.tar.gz
cd pcre2-10.33
chmod +x configure
./configure --prefix=/usr --enable-jit
make
sudo make install
# Download and compile from source # Download and compile Git from source
cd /tmp cd /tmp
curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz
echo 'fa08dc8424ef80c0f9bf307877f9e2e49f1a6049e873530d6747c2be770742ff git-2.29.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.29.0.tar.gz echo 'fa08dc8424ef80c0f9bf307877f9e2e49f1a6049e873530d6747c2be770742ff git-2.29.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.29.0.tar.gz
...@@ -193,6 +190,8 @@ sudo make prefix=/usr/local install ...@@ -193,6 +190,8 @@ sudo make prefix=/usr/local install
# When editing config/gitlab.yml later, change the git -> bin_path to /usr/local/bin/git # When editing config/gitlab.yml later, change the git -> bin_path to /usr/local/bin/git
``` ```
### GraphicsMagick
For the [Custom Favicon](../user/admin_area/appearance.md#favicon) to work, GraphicsMagick For the [Custom Favicon](../user/admin_area/appearance.md#favicon) to work, GraphicsMagick
needs to be installed. needs to be installed.
...@@ -200,6 +199,8 @@ needs to be installed. ...@@ -200,6 +199,8 @@ needs to be installed.
sudo apt-get install -y graphicsmagick sudo apt-get install -y graphicsmagick
``` ```
### Mail server
In order to receive mail notifications, make sure to install a mail server. In order to receive mail notifications, make sure to install a mail server.
By default, Debian is shipped with `exim4` but this By default, Debian is shipped with `exim4` but this
[has problems](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/12754) while [has problems](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/12754) while
...@@ -212,6 +213,8 @@ sudo apt-get install -y postfix ...@@ -212,6 +213,8 @@ sudo apt-get install -y postfix
Then select 'Internet Site' and press enter to confirm the hostname. Then select 'Internet Site' and press enter to confirm the hostname.
### Exiftool
[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse#dependencies) [GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse#dependencies)
requires `exiftool` to remove EXIF data from uploaded images. requires `exiftool` to remove EXIF data from uploaded images.
......
...@@ -114,8 +114,11 @@ See the documentation on [File Locking](../../../user/project/file_lock.md). ...@@ -114,8 +114,11 @@ See the documentation on [File Locking](../../../user/project/file_lock.md).
## LFS objects in project archives ## LFS objects in project archives
> - Support for including Git LFS blobs inside [project source downloads](../../../user/project/repository/index.md) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15079) in GitLab 13.5. > - Support for including Git LFS blobs inside [project source downloads](../../../user/project/repository/index.md) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15079) in GitLab 13.5.
> - It's [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default. > - It was [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-lfs-objects-in-project-archives). **(CORE ONLY)** > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/268409) on GitLab 13.6.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-lfs-objects-in-project-archives).
CAUTION: **Warning:** CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details. This feature might not be available to you. Check the **version history** note above for details.
...@@ -139,10 +142,10 @@ Technical details about how this works can be found in the [development document ...@@ -139,10 +142,10 @@ Technical details about how this works can be found in the [development document
### Enable or disable LFS objects in project archives ### Enable or disable LFS objects in project archives
_LFS objects in project archives_ is under development and not ready for production use. It is _LFS objects in project archives_ is under development but ready for production use.
deployed behind a feature flag that is **disabled by default**. It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can enable it. can opt to disable it.
To enable it: To enable it:
......
...@@ -530,7 +530,10 @@ To remove a group and its contents: ...@@ -530,7 +530,10 @@ To remove a group and its contents:
This action either: This action either:
- Removes the group, and also queues a background job to delete all projects in that group. - Removes the group, and also queues a background job to delete all projects in that group.
- Since [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [Premium or Silver](https://about.gitlab.com/pricing/premium/) or higher tiers, marks a group for deletion. The deletion will happen 7 days later by default, but this can be changed in the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay). - Since [GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [Premium or Silver](https://about.gitlab.com/pricing/premium/) or higher tiers, this action adds a background job to mark a group for deletion. By default, the job schedules the deletion 7 days in the future. You can modify this waiting period through the [instance settings](../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay).
Since [GitLab 13.6](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion leaves or is otherwise removed from the group before the
actual deletion happens, the job is cancelled, and the group is no longer scheduled for deletion.
### Restore a group **(PREMIUM)** ### Restore a group **(PREMIUM)**
......
...@@ -158,9 +158,8 @@ When it succeeds, go to **Settings > Pages** to view the URL where your site ...@@ -158,9 +158,8 @@ When it succeeds, go to **Settings > Pages** to view the URL where your site
is now available. is now available.
If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file If you want to do more advanced tasks, you can update your `.gitlab-ci.yml` file
with [any of the available settings](../../../../ci/yaml/README.md). See with [any of the available settings](../../../../ci/yaml/README.md). You can validate
[Validate the `.gitlab-ci.yml`](../../../../ci/yaml/README.md#validate-the-gitlab-ciyml) your `.gitlab-ci.yml` file with the [CI Lint](../../../../ci/lint.md) tool that's included with GitLab.
for instructions on validating your YAML file with the Lint tool included with GitLab.
After successful execution of this `pages` job, a special `pages:deploy` job appears in the After successful execution of this `pages` job, a special `pages:deploy` job appears in the
pipeline view. It prepares the content of the website for GitLab Pages daemon. GitLab executes it in pipeline view. It prepares the content of the website for GitLab Pages daemon. GitLab executes it in
......
import Vue from 'vue';
import DevopsAdoptionApp from './components/devops_adoption_app.vue';
export default () => {
const el = document.querySelector('.js-devops-adoption');
if (!el) return false;
const { emptyStateSvgPath } = el.dataset;
return new Vue({
el,
provide: {
emptyStateSvgPath,
},
render(h) {
return h(DevopsAdoptionApp);
},
});
};
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { GlCard, GlEmptyState, GlSkeletonLoader, GlTable } from '@gitlab/ui'; import { GlCard, GlEmptyState, GlLink, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import SelectProjectsDropdown from './select_projects_dropdown.vue'; import SelectProjectsDropdown from './select_projects_dropdown.vue';
...@@ -12,6 +13,7 @@ export default { ...@@ -12,6 +13,7 @@ export default {
components: { components: {
GlCard, GlCard,
GlEmptyState, GlEmptyState,
GlLink,
GlSkeletonLoader, GlSkeletonLoader,
GlTable, GlTable,
SelectProjectsDropdown, SelectProjectsDropdown,
...@@ -31,12 +33,16 @@ export default { ...@@ -31,12 +33,16 @@ export default {
// fetch the same data more than once // fetch the same data more than once
this.allCoverageData = [ this.allCoverageData = [
...this.allCoverageData, ...this.allCoverageData,
...data.projects.nodes.map(project => ({ // Remove the projects that don't have any code coverage
...project, ...data.projects.nodes
// if a project has no code coverage, set to default values .filter(({ codeCoverageSummary }) => Boolean(codeCoverageSummary))
codeCoverageSummary: .map(project => ({
project.codeCoverageSummary || this.$options.noCoverageDefaultSummary, ...project,
})), codeCoveragePath: joinPaths(
gon.relative_url_root || '',
`/${project.fullPath}/-/graphs/${project.repository.rootRef}/charts`,
),
})),
]; ];
}, },
error() { error() {
...@@ -76,6 +82,17 @@ export default { ...@@ -76,6 +82,17 @@ export default {
selectedCoverageData() { selectedCoverageData() {
return this.allCoverageData.filter(({ id }) => this.projectIds[id]); return this.allCoverageData.filter(({ id }) => this.projectIds[id]);
}, },
sortedCoverageData() {
// Sort the table by most recently updated coverage report
return [...this.selectedCoverageData].sort((a, b) => {
if (a.codeCoverageSummary.lastUpdatedAt > b.codeCoverageSummary.lastUpdatedAt) {
return -1;
} else if (a.codeCoverageSummary.lastUpdatedAt < b.codeCoverageSummary.lastUpdatedAt) {
return 1;
}
return 0;
});
},
}, },
methods: { methods: {
handleError() { handleError() {
...@@ -127,11 +144,6 @@ export default { ...@@ -127,11 +144,6 @@ export default {
'RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data.', 'RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data.',
), ),
}, },
noCoverageDefaultSummary: {
averageCoverage: 0,
coverageCount: 0,
lastUpdatedAt: '', // empty string will default to "just now" in table
},
LOADING_STATE: { LOADING_STATE: {
rows: 4, rows: 4,
height: 10, height: 10,
...@@ -183,7 +195,7 @@ export default { ...@@ -183,7 +195,7 @@ export default {
data-testid="test-coverage-data-table" data-testid="test-coverage-data-table"
thead-class="thead-white" thead-class="thead-white"
:fields="$options.tableFields" :fields="$options.tableFields"
:items="selectedCoverageData" :items="sortedCoverageData"
> >
<template #head(project)="data"> <template #head(project)="data">
<div>{{ data.label }}</div> <div>{{ data.label }}</div>
...@@ -199,7 +211,9 @@ export default { ...@@ -199,7 +211,9 @@ export default {
</template> </template>
<template #cell(project)="{ item }"> <template #cell(project)="{ item }">
<div :data-testid="`${item.id}-name`">{{ item.name }}</div> <gl-link target="_blank" :href="item.codeCoveragePath" :data-testid="`${item.id}-name`">
{{ item.name }}
</gl-link>
</template> </template>
<template #cell(averageCoverage)="{ item }"> <template #cell(averageCoverage)="{ item }">
<div :data-testid="`${item.id}-average`"> <div :data-testid="`${item.id}-average`">
......
query getProjectsTestCoverage($projectIds: [ID!]) { query getProjectsTestCoverage($projectIds: [ID!]) {
projects(ids: $projectIds) { projects(ids: $projectIds) {
nodes { nodes {
fullPath
id id
name name
repository {
rootRef
}
codeCoverageSummary { codeCoverageSummary {
averageCoverage averageCoverage
coverageCount coverageCount
......
...@@ -21,7 +21,6 @@ import { ...@@ -21,7 +21,6 @@ import {
SITE_PROFILES_QUERY, SITE_PROFILES_QUERY,
} from '../settings'; } from '../settings';
import dastOnDemandScanCreateMutation from '../graphql/dast_on_demand_scan_create.mutation.graphql'; import dastOnDemandScanCreateMutation from '../graphql/dast_on_demand_scan_create.mutation.graphql';
import DismissibleFeedbackAlert from '~/vue_shared/components/dismissible_feedback_alert.vue';
import OnDemandScansScannerProfileSelector from './profile_selector/scanner_profile_selector.vue'; import OnDemandScansScannerProfileSelector from './profile_selector/scanner_profile_selector.vue';
import OnDemandScansSiteProfileSelector from './profile_selector/site_profile_selector.vue'; import OnDemandScansSiteProfileSelector from './profile_selector/site_profile_selector.vue';
...@@ -53,7 +52,6 @@ export default { ...@@ -53,7 +52,6 @@ export default {
GlLink, GlLink,
GlSkeletonLoader, GlSkeletonLoader,
GlSprintf, GlSprintf,
DismissibleFeedbackAlert,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -162,15 +160,6 @@ export default { ...@@ -162,15 +160,6 @@ export default {
<template> <template>
<gl-form @submit.prevent="onSubmit"> <gl-form @submit.prevent="onSubmit">
<!--
This is a temporary change to solicit feedback from users
and shall be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/255889
-->
<dismissible-feedback-alert
feature-name="on-demand DAST scans"
feedback-link="https://gitlab.com/gitlab-org/gitlab/-/issues/249684"
/>
<header class="gl-mb-6"> <header class="gl-mb-6">
<h2>{{ s__('OnDemandScans|New on-demand DAST scan') }}</h2> <h2>{{ s__('OnDemandScans|New on-demand DAST scan') }}</h2>
<p> <p>
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
class Groups::UsageQuotasController < Groups::ApplicationController class Groups::UsageQuotasController < Groups::ApplicationController
before_action :authorize_admin_group! before_action :authorize_admin_group!
before_action :verify_usage_quotas_enabled! before_action :verify_usage_quotas_enabled!
before_action do before_action :push_additional_repo_storage_by_namespace_feature, only: :index
push_frontend_feature_flag(:additional_repo_storage_by_namespace, @group)
end
layout 'group_settings' layout 'group_settings'
...@@ -21,4 +19,8 @@ class Groups::UsageQuotasController < Groups::ApplicationController ...@@ -21,4 +19,8 @@ class Groups::UsageQuotasController < Groups::ApplicationController
render_404 unless License.feature_available?(:usage_quotas) render_404 unless License.feature_available?(:usage_quotas)
render_404 if @group.has_parent? render_404 if @group.has_parent?
end end
def push_additional_repo_storage_by_namespace_feature
push_to_gon_features(:additional_repo_storage_by_namespace, @group.additional_repo_storage_by_namespace_enabled?)
end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Profiles::UsageQuotasController < Profiles::ApplicationController class Profiles::UsageQuotasController < Profiles::ApplicationController
before_action do before_action :push_additional_repo_storage_by_namespace_feature, only: :index
push_frontend_feature_flag(:additional_repo_storage_by_namespace, @group)
end
feature_category :purchase feature_category :purchase
...@@ -11,4 +9,10 @@ class Profiles::UsageQuotasController < Profiles::ApplicationController ...@@ -11,4 +9,10 @@ class Profiles::UsageQuotasController < Profiles::ApplicationController
@namespace = current_user.namespace @namespace = current_user.namespace
@projects = @namespace.projects.with_shared_runners_limit_enabled.page(params[:page]) @projects = @namespace.projects.with_shared_runners_limit_enabled.page(params[:page])
end end
private
def push_additional_repo_storage_by_namespace_feature
push_to_gon_features(:additional_repo_storage_by_namespace, current_user.namespace.additional_repo_storage_by_namespace_enabled?)
end
end end
...@@ -41,6 +41,11 @@ module Resolvers ...@@ -41,6 +41,11 @@ module Resolvers
required: false, required: false,
description: 'Filter epics by iid for autocomplete' description: 'Filter epics by iid for autocomplete'
argument :include_descendant_groups, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Include epics from descendant groups',
default_value: true
type Types::EpicType, null: true type Types::EpicType, null: true
def ready?(**args) def ready?(**args)
......
...@@ -13,6 +13,7 @@ module EE ...@@ -13,6 +13,7 @@ module EE
end end
cleanup_group_identity(member) cleanup_group_identity(member)
cleanup_group_deletion_schedule(member) if member.source&.is_a?(Group)
end end
private private
...@@ -40,6 +41,14 @@ module EE ...@@ -40,6 +41,14 @@ module EE
saml_provider.identities.for_user(member.user).delete_all saml_provider.identities.for_user(member.user).delete_all
end end
def cleanup_group_deletion_schedule(member)
deletion_schedule = member.source&.deletion_schedule
return unless deletion_schedule
deletion_schedule.destroy if deletion_schedule.deleting_user == member.user
end
end end
end end
end end
...@@ -19,6 +19,8 @@ module Security ...@@ -19,6 +19,8 @@ module Security
end end
def execute def execute
return security_scan unless Feature.enabled?(:store_security_findings, project)
StoreFindingsMetadataService.execute(security_scan, security_report) StoreFindingsMetadataService.execute(security_scan, security_report)
deduplicate_findings? ? update_deduplicated_findings : register_finding_keys deduplicate_findings? ? update_deduplicated_findings : register_finding_keys
......
- billable_users_url = help_page_path('subscriptions/self_managed/index', anchor: 'billable-users')
- billable_users_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer nofollow">'.html_safe % { url: billable_users_url }
= s_('AdminArea|Billable users') = s_('AdminArea|Billable users')
%span.gl-outline-0.gl-ml-2{ href: "#", tabindex: "0", role: "button", data: { container: "body",
toggle: "popover",
placement: "top",
html: "true",
trigger: "focus",
content: s_("AdminArea|%{billable_users_link_start}Learn more%{billable_users_link_end} about what defines a billable user").html_safe % { billable_users_link_start: billable_users_link_start, billable_users_link_end: '</a>'.html_safe } } }
= sprite_icon('question', size: 16, css_class: 'gl-text-gray-700')
%h2
= _('DevOps Report')
%ul.nav-links.nav-tabs.nav.js-devops-tabs{ role: 'tablist' }
= render 'tab', active: true, title: _('DevOps Score'), target: '#devops_score_pane'
= render 'tab', active: false, title: _('Adoption'), target: '#devops_adoption_pane'
.tab-content
.tab-pane.active#devops_score_pane
= render_ce 'admin/dev_ops_report/report'
.tab-pane#devops_adoption_pane
.js-devops-adoption{ data: { empty_state_svg_path: image_path('illustrations/monitoring/getting_started.svg') } }
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
- true_up_url = 'https://about.gitlab.com/license-faq/' - true_up_url = 'https://about.gitlab.com/license-faq/'
- true_up_link_start = '<a href="%{url}">'.html_safe % { url: true_up_url } - true_up_link_start = '<a href="%{url}">'.html_safe % { url: true_up_url }
- billable_users_url = help_page_path('subscriptions/self_managed/index', anchor: 'billable-users')
- billable_users_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer nofollow">'.html_safe % { url: billable_users_url }
- link_end = '</a>'.html_safe - link_end = '</a>'.html_safe
.license-panel.gl-mt-5 .license-panel.gl-mt-5
...@@ -57,7 +59,7 @@ ...@@ -57,7 +59,7 @@
= number_with_delimiter current_active_user_count = number_with_delimiter current_active_user_count
%hr %hr
%p %p
= _('This is the number of currently active users on your installation, and this is the minimum number you need to purchase when you renew your license.') = _('This is the number of %{billable_users_link_start}billable users%{link_end} on your installation, and this is the minimum number you need to purchase when you renew your license.').html_safe % { billable_users_link_start: billable_users_link_start, link_end: link_end }
.d-flex.gl-pb-5 .d-flex.gl-pb-5
.col-sm-6.d-flex.pl-0 .col-sm-6.d-flex.pl-0
.info-well.dark-well.flex-fill.gl-mb-0 .info-well.dark-well.flex-fill.gl-mb-0
......
...@@ -17,10 +17,10 @@ ...@@ -17,10 +17,10 @@
= f.hidden_field :data, value: license_key = f.hidden_field :data, value: license_key
%a#hide-license.hide-license{ href: '#hide-license' } %a#hide-license.hide-license{ href: '#hide-license' }
Show license key Show license key
= icon('chevron-down') = sprite_icon('chevron-down')
%a#show-license.show-license{ href: '#show-license' } %a#show-license.show-license{ href: '#show-license' }
Hide license key Hide license key
= icon('chevron-up') = sprite_icon('chevron-up')
.card.trial-license-preview.gl-mt-5 .card.trial-license-preview.gl-mt-5
= license_key = license_key
.modal-footer.form-actions .modal-footer.form-actions
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
= s_('Promotions|This feature is locked.') = s_('Promotions|This feature is locked.')
%a.btn-link.js-toggle-button.js-weight-sidebar-callout{ href: '#', data: { track_event: 'click_callout' }.merge(tracking_options) } %a.btn-link.js-toggle-button.js-weight-sidebar-callout{ href: '#', data: { track_event: 'click_callout' }.merge(tracking_options) }
= s_('Promotions|Learn more') = s_('Promotions|Learn more')
= icon('chevron-down') = sprite_icon('chevron-up', css_class: 'js-sidebar-collapse hidden')
= sprite_icon('chevron-down', css_class: 'js-sidebar-expand')
.js-toggle-content{ style:'display: none' } .js-toggle-content{ style:'display: none' }
%div %div
%h4 %h4
......
---
title: Add text/tips to clarify various user counts in self-managed admin panels
merge_request: 44986
author:
type: other
---
title: Unschedule group deletion on deleting member removal
merge_request: 45715
author:
type: changed
---
title: Add `include_descendant_groups` option to Epics GraphQL API
merge_request: 46711
author:
type: changed
---
name: store_security_findings
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44312
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276011
milestone: '13.6'
type: development
group: group::threat insights
default_enabled: false
---
name: ci_secrets_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42055
rollout_issue_url:
group: group::release management
type: licensed
default_enabled: true
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::UsageQuotasController do
let_it_be(:group) { create(:group, :private) }
let_it_be(:user) { create(:user) }
before do
sign_in(user)
group.add_owner(user)
end
describe 'Pushing the `additionalRepoStorageByNamespace` feature flag to the frontend' do
context 'when both flags are true' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: true, namespace_storage_limit: true)
end
it 'is disabled' do
get :index, params: { group_id: group }
expect(Gon.features).to include('additionalRepoStorageByNamespace' => false)
end
end
context 'when `namespace_storage_limit` flag is false' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: true, namespace_storage_limit: false)
end
it 'is enabled' do
get :index, params: { group_id: group }
expect(Gon.features).to include('additionalRepoStorageByNamespace' => true)
end
end
context 'when both flags are false' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: false, namespace_storage_limit: false)
end
it 'is disabled' do
get :index, params: { group_id: group }
expect(Gon.features).to include('additionalRepoStorageByNamespace' => false)
end
end
end
end
...@@ -16,4 +16,42 @@ RSpec.describe Profiles::UsageQuotasController do ...@@ -16,4 +16,42 @@ RSpec.describe Profiles::UsageQuotasController do
expect(subject).to render_template(:index) expect(subject).to render_template(:index)
end end
end end
describe 'Pushing the `additionalRepoStorageByNamespace` feature flag to the frontend' do
context 'when both flags are true' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: true, namespace_storage_limit: true)
end
it 'is disabled' do
get :index
expect(Gon.features).to include('additionalRepoStorageByNamespace' => false)
end
end
context 'when `namespace_storage_limit` flag is false' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: true, namespace_storage_limit: false)
end
it 'is enabled' do
get :index
expect(Gon.features).to include('additionalRepoStorageByNamespace' => true)
end
end
context 'when both flags are false' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: false, namespace_storage_limit: false)
end
it 'is disabled' do
get :index
expect(Gon.features).to include('additionalRepoStorageByNamespace' => false)
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'DevOps Report page', :js do
tabs_selector = '.js-devops-tabs'
tab_item_selector = '.js-devops-tab-item'
active_tab_selector = '.nav-link.active'
before do
sign_in(create(:admin))
end
context 'with devops_adoption feature flag disabled' do
before do
stub_feature_flags(devops_adoption: false)
end
it 'does not show the tabbed layout' do
visit admin_dev_ops_report_path
expect(page).not_to have_selector tabs_selector
end
end
context 'with devops_adoption feature flag enabled' do
it 'shows the tabbed layout' do
visit admin_dev_ops_report_path
expect(page).to have_selector tabs_selector
end
it 'shows the correct tabs' do
visit admin_dev_ops_report_path
within tabs_selector do
expect(page.all(:css, tab_item_selector).length).to be(2)
expect(page).to have_text 'DevOps Score Adoption'
end
end
it 'defaults to the DevOps Score tab' do
visit admin_dev_ops_report_path
within tabs_selector do
expect(page).to have_selector active_tab_selector, text: 'DevOps Score'
end
end
it 'displays the Adoption tab content when selected' do
visit admin_dev_ops_report_path
click_link 'Adoption'
within tabs_selector do
expect(page).to have_selector active_tab_selector, text: 'Adoption'
end
end
context 'the devops score tab' do
it 'has dismissable intro callout' do
visit admin_dev_ops_report_path
expect(page).to have_content 'Introducing Your DevOps Report'
find('.js-close-callout').click
expect(page).not_to have_content 'Introducing Your DevOps Report'
end
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
end
it 'shows empty state' do
visit admin_dev_ops_report_path
expect(page).to have_selector(".js-empty-state")
end
it 'hides the intro callout' do
visit admin_dev_ops_report_path
expect(page).not_to have_content 'Introducing Your DevOps Report'
end
end
context 'when there is no data to display' do
it 'shows empty state' do
stub_application_setting(usage_ping_enabled: true)
visit admin_dev_ops_report_path
expect(page).to have_content('Data is still calculating')
end
end
context 'when there is data to display' do
it 'shows numbers for each metric' do
stub_application_setting(usage_ping_enabled: true)
create(:dev_ops_report_metric)
visit admin_dev_ops_report_path
expect(page).to have_content(
'Issues created per active user 1.2 You 9.3 Lead 13.3%'
)
end
end
end
end
end
...@@ -9,35 +9,12 @@ RSpec.describe 'Groups > Usage Quotas' do ...@@ -9,35 +9,12 @@ RSpec.describe 'Groups > Usage Quotas' do
let(:gitlab_dot_com) { true } let(:gitlab_dot_com) { true }
before do before do
stub_feature_flags(additional_repo_storage_by_namespace: true)
allow(Gitlab).to receive(:com?).and_return(gitlab_dot_com) allow(Gitlab).to receive(:com?).and_return(gitlab_dot_com)
group.add_owner(user) group.add_owner(user)
sign_in(user) sign_in(user)
end end
it 'pushes frontend feature flags' do
visit visit_pipeline_quota_page
expect(page).to have_pushed_frontend_feature_flags(
additionalRepoStorageByNamespace: true
)
end
context 'when `additional_repo_storage_by_namespace` is disabled for a group' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: false, thing: group)
end
it 'pushes disabled feature flag to the frontend' do
visit visit_pipeline_quota_page
expect(page).to have_pushed_frontend_feature_flags(
additionalRepoStorageByNamespace: false
)
end
end
shared_examples 'linked in group settings dropdown' do shared_examples 'linked in group settings dropdown' do
it 'is linked within the group settings dropdown' do it 'is linked within the group settings dropdown' do
visit edit_group_path(group) visit edit_group_path(group)
......
...@@ -12,32 +12,9 @@ RSpec.describe 'Profile > Usage Quota' do ...@@ -12,32 +12,9 @@ RSpec.describe 'Profile > Usage Quota' do
let_it_be(:other_project) { create(:project, namespace: namespace, shared_runners_enabled: false) } let_it_be(:other_project) { create(:project, namespace: namespace, shared_runners_enabled: false) }
before do before do
stub_feature_flags(additional_repo_storage_by_namespace: true)
gitlab_sign_in(user) gitlab_sign_in(user)
end end
it 'pushes frontend feature flags' do
visit profile_usage_quotas_path
expect(page).to have_pushed_frontend_feature_flags(
additionalRepoStorageByNamespace: true
)
end
context 'when `additional_repo_storage_by_namespace` is disabled for a namespace' do
before do
stub_feature_flags(additional_repo_storage_by_namespace: false, thing: namespace)
end
it 'pushes disabled feature flag to the frontend' do
visit profile_usage_quotas_path
expect(page).to have_pushed_frontend_feature_flags(
additionalRepoStorageByNamespace: false
)
end
end
it 'is linked within the profile page' do it 'is linked within the profile page' do
visit profile_path visit profile_path
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import DevopsAdoptionApp from '~/admin/dev_ops_report/components/devops_adoption_app.vue'; import DevopsAdoptionApp from 'ee/admin/dev_ops_report/components/devops_adoption_app.vue';
import DevopsAdoptionEmptyState from '~/admin/dev_ops_report/components/devops_adoption_empty_state.vue'; import DevopsAdoptionEmptyState from 'ee/admin/dev_ops_report/components/devops_adoption_empty_state.vue';
describe('DevopsAdoptionApp', () => { describe('DevopsAdoptionApp', () => {
let wrapper; let wrapper;
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlEmptyState, GlButton } from '@gitlab/ui'; import { GlEmptyState, GlButton } from '@gitlab/ui';
import DevopsAdoptionEmptyState from '~/admin/dev_ops_report/components/devops_adoption_empty_state.vue'; import DevopsAdoptionEmptyState from 'ee/admin/dev_ops_report/components/devops_adoption_empty_state.vue';
import { DEVOPS_ADOPTION_STRINGS } from '~/admin/dev_ops_report/constants'; import { DEVOPS_ADOPTION_STRINGS } from 'ee/admin/dev_ops_report/constants';
const emptyStateSvgPath = 'illustrations/monitoring/getting_started.svg'; const emptyStateSvgPath = 'illustrations/monitoring/getting_started.svg';
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment