Commit 62235853 authored by Payton Burdette's avatar Payton Burdette Committed by Nicolò Maria Mezzopera

Introduce keep latest artifact setting

Build CI/CD project setting
that allows users to adjust
preference on keeping the lastest
artifact of the pipeline.
parent a47d17f6
mutation updateKeepLatestArtifactProjectSetting($fullPath: ID!, $keepLatestArtifact: Boolean!) {
ciCdSettingsUpdate(input: { fullPath: $fullPath, keepLatestArtifact: $keepLatestArtifact }) {
errors
}
}
query getKeepLatestArtifactProjectSetting($fullPath: ID!) {
project(fullPath: $fullPath) {
ciCdSettings {
keepLatestArtifact
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default (containerId = 'js-artifacts-settings-app') => {
const containerEl = document.getElementById(containerId);
if (!containerEl) {
return false;
}
const { fullPath, helpPagePath } = containerEl.dataset;
return new Vue({
el: containerEl,
apolloProvider,
provide: {
fullPath,
helpPagePath,
},
render(createElement) {
return createElement(KeepLatestArtifactCheckbox);
},
});
};
<script>
import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
const FETCH_ERROR = __('There was a problem fetching the keep latest artifact setting.');
const UPDATE_ERROR = __('There was a problem updating the keep latest artifact setting.');
export default {
components: {
GlAlert,
GlFormCheckbox,
GlLink,
},
inject: {
fullPath: {
default: '',
},
helpPagePath: {
default: '',
},
},
apollo: {
keepLatestArtifact: {
query: GetKeepLatestArtifactProjectSetting,
variables() {
return {
fullPath: this.fullPath,
};
},
update(data) {
return data.project?.ciCdSettings?.keepLatestArtifact;
},
error() {
this.reportError(FETCH_ERROR);
},
},
},
data() {
return {
keepLatestArtifact: true,
errorMessage: '',
isAlertDismissed: false,
};
},
computed: {
shouldShowAlert() {
return this.errorMessage && !this.isAlertDismissed;
},
},
methods: {
reportError(error) {
this.errorMessage = error;
this.isAlertDismissed = false;
},
async updateSetting(checked) {
try {
const { data } = await this.$apollo.mutate({
mutation: UpdateKeepLatestArtifactProjectSetting,
variables: {
fullPath: this.fullPath,
keepLatestArtifact: checked,
},
});
if (data.ciCdSettingsUpdate.errors.length) {
this.reportError(UPDATE_ERROR);
}
} catch (error) {
this.reportError(UPDATE_ERROR);
}
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="shouldShowAlert"
class="gl-mb-5"
variant="danger"
@dismiss="isAlertDismissed = true"
>{{ errorMessage }}</gl-alert
>
<gl-form-checkbox v-model="keepLatestArtifact" @change="updateSetting"
><b class="gl-mr-3">{{ __('Keep artifacts from most recent successful jobs') }}</b>
<gl-link :href="helpPagePath">{{ __('More information') }}</gl-link></gl-form-checkbox
>
<p>
{{
__(
'The latest artifacts created by jobs in the most recent successful pipeline will be stored.',
)
}}
</p>
</div>
</template>
...@@ -5,6 +5,7 @@ import initVariableList from '~/ci_variable_list'; ...@@ -5,6 +5,7 @@ import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze'; import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers'; import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle'; import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import initArtifactsSettings from '~/artifacts_settings';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels // Initialize expandable settings panels
...@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => {
initDeployFreeze(); initDeployFreeze();
initSettingsPipelinesTriggers(); initSettingsPipelinesTriggers();
initArtifactsSettings();
if (gon?.features?.vueifySharedRunnersToggle) { if (gon?.features?.vueifySharedRunnersToggle) {
initSharedRunnersToggle(); initSharedRunnersToggle();
......
...@@ -45,6 +45,17 @@ ...@@ -45,6 +45,17 @@
.settings-content .settings-content
= render 'projects/runners/index' = render 'projects/runners/index'
%section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _("Artifacts")
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _("A job artifact is an archive of files and directories saved by a job when it finishes.")
.settings-content
#js-artifacts-settings-app{ data: { full_path: @project.full_path, help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'keep-artifacts-from-most-recent-successful-jobs') } }
%section.qa-variables-settings.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } } %section.qa-variables-settings.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } }
.settings-header .settings-header
= render 'ci/variables/header', expanded: expanded = render 'ci/variables/header', expanded: expanded
......
---
title: UI to opt out of keeping the artifacts from the last job at project level.
merge_request: 49500
author:
type: added
...@@ -464,6 +464,23 @@ To retrieve a job artifact from a different project, you might need to use a ...@@ -464,6 +464,23 @@ To retrieve a job artifact from a different project, you might need to use a
private token to [authenticate and download](../../api/job_artifacts.md#get-job-artifacts) private token to [authenticate and download](../../api/job_artifacts.md#get-job-artifacts)
the artifact. the artifact.
## Keep artifacts from most recent successful jobs
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16267) in GitLab 13.0.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/229936) in GitLab 13.4.
> - [Made optional with a CI/CD setting](https://gitlab.com/gitlab-org/gitlab/-/issues/241026) in GitLab 13.8.
By default, the latest artifacts from the most recent successful jobs are never deleted.
If a job is configured with [`expire_in`](../yaml/README.md#artifactsexpire_in),
its artifacts only expire if a more recent artifact exists.
Keeping the latest artifacts can use a large amount of storage space in projects
with a lot of jobs or large artifacts. If the latest artifacts are not needed in
a project, you can disable this behavior to save space:
1. Navigate to **Settings > CI/CD > Artifacts**.
1. Uncheck **Keep artifacts from most recent successful jobs**.
## Troubleshooting ## Troubleshooting
### Error message `No files to upload` ### Error message `No files to upload`
......
...@@ -3363,7 +3363,7 @@ job: ...@@ -3363,7 +3363,7 @@ job:
The latest artifacts for refs are locked against deletion, and kept regardless of The latest artifacts for refs are locked against deletion, and kept regardless of
the expiry time. [Introduced in](https://gitlab.com/gitlab-org/gitlab/-/issues/16267) the expiry time. [Introduced in](https://gitlab.com/gitlab-org/gitlab/-/issues/16267)
GitLab 13.0 behind a disabled feature flag, and [made the default behavior](https://gitlab.com/gitlab-org/gitlab/-/issues/229936) GitLab 13.0 behind a disabled feature flag, and [made the default behavior](https://gitlab.com/gitlab-org/gitlab/-/issues/229936)
in GitLab 13.4. in GitLab 13.4. In [GitLab 13.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/241026), you can [disable this behavior in the CI/CD settings](../pipelines/job_artifacts.md#keep-artifacts-from-most-recent-successful-jobs).
#### `artifacts:reports` #### `artifacts:reports`
......
...@@ -1265,6 +1265,9 @@ msgstr "" ...@@ -1265,6 +1265,9 @@ msgstr ""
msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects." msgid "A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects."
msgstr "" msgstr ""
msgid "A job artifact is an archive of files and directories saved by a job when it finishes."
msgstr ""
msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies." msgid "A limit of %{ci_project_subscriptions_limit} subscriptions to or from a project applies."
msgstr "" msgstr ""
...@@ -16175,6 +16178,9 @@ msgstr "" ...@@ -16175,6 +16178,9 @@ msgstr ""
msgid "K8s pod health" msgid "K8s pod health"
msgstr "" msgstr ""
msgid "Keep artifacts from most recent successful jobs"
msgstr ""
msgid "Keep divergent refs" msgid "Keep divergent refs"
msgstr "" msgstr ""
...@@ -28070,6 +28076,9 @@ msgstr "" ...@@ -28070,6 +28076,9 @@ msgstr ""
msgid "The issue was successfully promoted to an epic. Redirecting to epic..." msgid "The issue was successfully promoted to an epic. Redirecting to epic..."
msgstr "" msgstr ""
msgid "The latest artifacts created by jobs in the most recent successful pipeline will be stored."
msgstr ""
msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc." msgid "The license key is invalid. Make sure it is exactly as you received it from GitLab Inc."
msgstr "" msgstr ""
...@@ -28406,6 +28415,9 @@ msgstr "" ...@@ -28406,6 +28415,9 @@ msgstr ""
msgid "There was a problem fetching project users." msgid "There was a problem fetching project users."
msgstr "" msgstr ""
msgid "There was a problem fetching the keep latest artifact setting."
msgstr ""
msgid "There was a problem fetching users." msgid "There was a problem fetching users."
msgstr "" msgstr ""
...@@ -28418,6 +28430,9 @@ msgstr "" ...@@ -28418,6 +28430,9 @@ msgstr ""
msgid "There was a problem sending the confirmation email" msgid "There was a problem sending the confirmation email"
msgstr "" msgstr ""
msgid "There was a problem updating the keep latest artifact setting."
msgstr ""
msgid "There was an error %{message} todo." msgid "There was an error %{message} todo."
msgstr "" msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Keep latest artifact checkbox sets correct setting value in checkbox with query result 1`] = `
<div>
<!---->
<gl-form-checkbox-stub
checked="true"
>
<b
class="gl-mr-3"
>
Keep artifacts from most recent successful jobs
</b>
<gl-link-stub
href="/help/ci/pipelines/job_artifacts"
>
More information
</gl-link-stub>
</gl-form-checkbox-stub>
<p>
The latest artifacts created by jobs in the most recent successful pipeline will be stored.
</p>
</div>
`;
import { GlFormCheckbox, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
import UpdateKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
describe('Keep latest artifact checkbox', () => {
let wrapper;
const mutate = jest.fn().mockResolvedValue();
const fullPath = 'gitlab-org/gitlab';
const helpPagePath = '/help/ci/pipelines/job_artifacts';
const findCheckbox = () => wrapper.find(GlFormCheckbox);
const findHelpLink = () => wrapper.find(GlLink);
const createComponent = () => {
wrapper = shallowMount(KeepLatestArtifactCheckbox, {
provide: {
fullPath,
helpPagePath,
},
mocks: {
$apollo: {
mutate,
},
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('displays the checkbox and the help link', () => {
expect(findCheckbox().exists()).toBe(true);
expect(findHelpLink().exists()).toBe(true);
});
it('sets correct setting value in checkbox with query result', async () => {
await wrapper.setData({ keepLatestArtifact: true });
expect(wrapper.element).toMatchSnapshot();
});
it('calls mutation on artifact setting change with correct payload', () => {
findCheckbox().vm.$emit('change', false);
const expected = {
mutation: UpdateKeepLatestArtifactProjectSetting,
variables: {
fullPath,
keepLatestArtifact: false,
},
};
expect(mutate).toHaveBeenCalledWith(expected);
});
});
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