Skip to content
代码片段 群组 项目
未验证 提交 effaf0d5 编辑于 作者: Max Fan's avatar Max Fan 提交者: GitLab
浏览文件

Merge branch...

Merge branch 'add-ci-cd-project-settings-ui-for-pipeline-variables-minimum-override-role' into 'master' 

UI for `pipeline_variables_minimum_role` project setting

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172304



Merged-by: default avatarMax Fan <mfan@gitlab.com>
Approved-by: default avatarAmmar Alakkad <aalakkad@gitlab.com>
Approved-by: default avatarSunjung Park <spark@gitlab.com>
Approved-by: default avatarKushal Pandya <kushal@gitlab.com>
Approved-by: default avatarJose Ivan Vargas <jvargas@gitlab.com>
Approved-by: default avatarMax Fan <mfan@gitlab.com>
Reviewed-by: default avatarKushal Pandya <kushal@gitlab.com>
Reviewed-by: default avatarFabio Pitino <fpitino@gitlab.com>
Co-authored-by: default avatarBriley Sandlin <bsandlin@gitlab.com>
Co-authored-by: default avatarHordur Freyr Yngvason <hfyngvason@gitlab.com>
No related branches found
No related tags found
无相关合并请求
显示 449 个添加0 个删除
mutation updatePipelineVariablesMinimumOverrideRoleProjectSetting(
$fullPath: ID!
$pipelineVariablesMinimumOverrideRole: String!
) {
projectCiCdSettingsUpdate(
input: {
fullPath: $fullPath
pipelineVariablesMinimumOverrideRole: $pipelineVariablesMinimumOverrideRole
}
) {
errors
}
}
query getPipelineVariablesMinimumOverrideRoleProjectSetting($fullPath: ID!) {
project(fullPath: $fullPath) {
id
ciCdSettings {
pipelineVariablesMinimumOverrideRole
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import PipelinesMinimumOverrideRole from '~/ci/pipeline_variables_minimum_override_role/pipeline_variables_minimum_override_role.vue';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default (containerId = 'js-ci-variables-minimum-override-role-app') => {
const containerEl = document.getElementById(containerId);
if (!containerEl) {
return false;
}
const { fullPath } = containerEl.dataset;
return new Vue({
el: containerEl,
name: 'PipelineVariablesMinimumOverrideRoleRoot',
apolloProvider,
provide: {
fullPath,
},
render(createElement) {
return createElement(PipelinesMinimumOverrideRole);
},
});
};
<script>
import { GlAlert, GlButton, GlFormGroup, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { reportToSentry } from '~/ci/utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import updatePipelineVariablesMinimumOverrideRoleMutation from './graphql/mutations/update_pipeline_variables_minimum_override_role_project_setting.mutation.graphql';
import getPipelineVariablesMinimumOverrideRoleQuery from './graphql/queries/get_pipeline_variables_minimum_override_role_project_setting.query.graphql';
export const MINIMUM_ROLE_DEVELOPER = 'developer';
export const MINIMUM_ROLE_MAINTAINER = 'maintainer';
export const MINIMUM_ROLE_NO_ONE = 'no_one_allowed';
export const MINIMUM_ROLE_OWNER = 'owner';
export default {
name: 'PipelineVariablesMinimumOverrideRole',
helpPath: helpPagePath('ci/variables/_index', {
anchor: 'set-a-minimum-role-for-pipeline-variables',
}),
ROLE_OPTIONS: [
{
text: __('No one allowed'),
value: MINIMUM_ROLE_NO_ONE,
help: s__('CiVariables|Pipeline variables cannot be used.'),
},
{
text: __('Owner'),
value: MINIMUM_ROLE_OWNER,
},
{
text: __('Maintainer'),
value: MINIMUM_ROLE_MAINTAINER,
},
{
text: __('Developer'),
value: MINIMUM_ROLE_DEVELOPER,
},
],
components: {
GlAlert,
GlButton,
GlFormGroup,
GlFormRadio,
GlFormRadioGroup,
GlLink,
},
inject: ['fullPath'],
apollo: {
minimumOverrideRole: {
query: getPipelineVariablesMinimumOverrideRoleQuery,
variables() {
return {
fullPath: this.fullPath,
};
},
update({ project }) {
return (
project?.ciCdSettings?.pipelineVariablesMinimumOverrideRole || MINIMUM_ROLE_DEVELOPER
);
},
error() {
this.reportError(__('There was a problem fetching the latest minimum override role.'));
},
},
},
data() {
return {
errorMessage: '',
isAlertDismissed: false,
isSubmitting: false,
minimumOverrideRole: null,
};
},
computed: {
shouldShowAlert() {
return this.errorMessage && !this.isAlertDismissed;
},
},
methods: {
reportError(error) {
this.errorMessage = error;
this.isAlertDismissed = false;
reportToSentry(this.$options.name, error);
},
async updateSetting() {
this.isSubmitting = true;
try {
const {
data: {
projectCiCdSettingsUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: updatePipelineVariablesMinimumOverrideRoleMutation,
variables: {
fullPath: this.fullPath,
pipelineVariablesMinimumOverrideRole: this.minimumOverrideRole,
},
});
if (errors.length) {
this.reportError(errors.join(', '));
} else {
this.isAlertDismissed = true;
this.$toast.show(
s__('CiVariables|Pipeline variable minimum override role successfully updated.'),
);
}
} catch {
this.reportError(__('There was a problem updating the minimum override setting.'));
}
this.isSubmitting = false;
},
},
};
</script>
<template>
<div class="gl-mb-5">
<gl-alert
v-if="shouldShowAlert"
class="gl-mb-5"
variant="danger"
@dismiss="isAlertDismissed = true"
>{{ errorMessage }}</gl-alert
>
<gl-form-group :label="s__('CiVariables|Minimum role to use pipeline variables')">
<template #label-description>
<span>{{
s__(
'CiVariables|Select the minimum role that is allowed to run a new pipeline with pipeline variables.',
)
}}</span>
<gl-link :href="$options.helpPath" target="_blank">{{
s__('CiVariables|What are pipeline variables?')
}}</gl-link>
</template>
<gl-form-radio-group v-model="minimumOverrideRole">
<gl-form-radio v-for="role in $options.ROLE_OPTIONS" :key="role.value" :value="role.value">
{{ role.text }}
<template v-if="role.help" #help>{{ role.help }}</template>
</gl-form-radio>
</gl-form-radio-group>
<gl-button
category="primary"
variant="confirm"
class="gl-mt-3"
:loading="isSubmitting"
:aria-label="__('Save changes')"
@click="updateSetting"
>{{ __('Save changes') }}
</gl-button>
</gl-form-group>
</div>
</template>
import initArtifactsSettings from '~/artifacts_settings'; import initArtifactsSettings from '~/artifacts_settings';
import initVariablesMinimumOverrideRole from '~/ci/pipeline_variables_minimum_override_role';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers'; import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initVariableList from '~/ci/ci_variable_list'; import initVariableList from '~/ci/ci_variable_list';
import initInheritedGroupCiVariables from '~/ci/inherited_ci_variables'; import initInheritedGroupCiVariables from '~/ci/inherited_ci_variables';
...@@ -34,6 +35,7 @@ initDeployTokens(); ...@@ -34,6 +35,7 @@ initDeployTokens();
initDeployFreeze(); initDeployFreeze();
initSettingsPipelinesTriggers(); initSettingsPipelinesTriggers();
initArtifactsSettings(); initArtifactsSettings();
initVariablesMinimumOverrideRole();
initProjectRunnersRegistrationDropdown(); initProjectRunnersRegistrationDropdown();
initSharedRunnersToggle(); initSharedRunnersToggle();
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
- is_group = !@group.nil? - is_group = !@group.nil?
- is_project = !@project.nil? - is_project = !@project.nil?
- if is_project
#js-ci-variables-minimum-override-role-app{ data: { full_path: @project.full_path } }
%h5
= _('Project variables')
= render 'ci/variables/attributes' = render 'ci/variables/attributes'
#js-ci-variables{ data: { endpoint: save_endpoint, #js-ci-variables{ data: { endpoint: save_endpoint,
......
...@@ -12478,9 +12478,18 @@ msgstr "" ...@@ -12478,9 +12478,18 @@ msgstr ""
msgid "CiVariables|Maximum number of variables reached." msgid "CiVariables|Maximum number of variables reached."
msgstr "" msgstr ""
   
msgid "CiVariables|Minimum role to use pipeline variables"
msgstr ""
msgid "CiVariables|No matching values" msgid "CiVariables|No matching values"
msgstr "" msgstr ""
   
msgid "CiVariables|Pipeline variable minimum override role successfully updated."
msgstr ""
msgid "CiVariables|Pipeline variables cannot be used."
msgstr ""
msgid "CiVariables|Protect variable" msgid "CiVariables|Protect variable"
msgstr "" msgstr ""
   
...@@ -12505,6 +12514,9 @@ msgstr "" ...@@ -12505,6 +12514,9 @@ msgstr ""
msgid "CiVariables|Search values" msgid "CiVariables|Search values"
msgstr "" msgstr ""
   
msgid "CiVariables|Select the minimum role that is allowed to run a new pipeline with pipeline variables."
msgstr ""
msgid "CiVariables|Set the visibility level for the variable's value. The %{linkStart}Masked and hidden%{linkEnd} option is only available for new variables. You cannot update an existing variable to be hidden." msgid "CiVariables|Set the visibility level for the variable's value. The %{linkStart}Masked and hidden%{linkEnd} option is only available for new variables. You cannot update an existing variable to be hidden."
msgstr "" msgstr ""
   
...@@ -12574,6 +12586,9 @@ msgstr "" ...@@ -12574,6 +12586,9 @@ msgstr ""
msgid "CiVariables|Visible" msgid "CiVariables|Visible"
msgstr "" msgstr ""
   
msgid "CiVariables|What are pipeline variables?"
msgstr ""
msgid "CiVariables|You can use CI/CD variables with the same name in different places, but the variables might overwrite each other. %{linkStart}What is the order of precedence for variables?%{linkEnd}" msgid "CiVariables|You can use CI/CD variables with the same name in different places, but the variables might overwrite each other. %{linkStart}What is the order of precedence for variables?%{linkEnd}"
msgstr "" msgstr ""
   
...@@ -38318,6 +38333,9 @@ msgstr "" ...@@ -38318,6 +38333,9 @@ msgstr ""
msgid "No more than %{max_work_items} work items can be updated at the same time" msgid "No more than %{max_work_items} work items can be updated at the same time"
msgstr "" msgstr ""
   
msgid "No one allowed"
msgstr ""
msgid "No open merge requests" msgid "No open merge requests"
msgstr "" msgstr ""
   
...@@ -44906,6 +44924,9 @@ msgstr "" ...@@ -44906,6 +44924,9 @@ msgstr ""
msgid "Project uploads" msgid "Project uploads"
msgstr "" msgstr ""
   
msgid "Project variables"
msgstr ""
msgid "Project visibility level is less restrictive than the group settings." msgid "Project visibility level is less restrictive than the group settings."
msgstr "" msgstr ""
   
...@@ -58591,6 +58612,9 @@ msgstr "" ...@@ -58591,6 +58612,9 @@ msgstr ""
msgid "There was a problem fetching the keep latest artifacts setting." msgid "There was a problem fetching the keep latest artifacts setting."
msgstr "" msgstr ""
   
msgid "There was a problem fetching the latest minimum override role."
msgstr ""
msgid "There was a problem fetching the pipeline summary." msgid "There was a problem fetching the pipeline summary."
msgstr "" msgstr ""
   
...@@ -58609,6 +58633,9 @@ msgstr "" ...@@ -58609,6 +58633,9 @@ msgstr ""
msgid "There was a problem updating the keep latest artifacts setting." msgid "There was a problem updating the keep latest artifacts setting."
msgstr "" msgstr ""
   
msgid "There was a problem updating the minimum override setting."
msgstr ""
msgid "There was an error fetching configuration for charts" msgid "There was an error fetching configuration for charts"
msgstr "" msgstr ""
   
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlButton, GlFormGroup, GlFormRadio, GlFormRadioGroup } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PipelineVariablesMinimumOverrideRole, {
MINIMUM_ROLE_MAINTAINER,
MINIMUM_ROLE_DEVELOPER,
} from '~/ci/pipeline_variables_minimum_override_role/pipeline_variables_minimum_override_role.vue';
import updatePipelineVariablesMinimumOverrideRoleProjectSetting from '~/ci/pipeline_variables_minimum_override_role/graphql/mutations/update_pipeline_variables_minimum_override_role_project_setting.mutation.graphql';
import getPipelineVariablesMinimumOverrideRoleProjectSetting from '~/ci/pipeline_variables_minimum_override_role/graphql/queries/get_pipeline_variables_minimum_override_role_project_setting.query.graphql';
Vue.use(VueApollo);
const $toast = {
show: jest.fn(),
};
const TEST_FULL_PATH = 'project/path';
describe('PipelineVariablesMinimumOverrideRole', () => {
let wrapper;
const defaultQueryResponse = jest.fn().mockResolvedValue({
data: {
project: {
id: '1',
ciCdSettings: {
pipelineVariablesMinimumOverrideRole: 'maintainer',
},
},
},
});
const defaultMutationResponse = jest.fn().mockResolvedValue({
data: {
projectCiCdSettingsUpdate: {
errors: [],
},
},
});
const createComponent = async ({
queryHandler = defaultQueryResponse,
mutationHandler = defaultMutationResponse,
} = {}) => {
const apolloProvider = createMockApollo([
[getPipelineVariablesMinimumOverrideRoleProjectSetting, queryHandler],
[updatePipelineVariablesMinimumOverrideRoleProjectSetting, mutationHandler],
]);
wrapper = shallowMount(PipelineVariablesMinimumOverrideRole, {
provide: {
fullPath: TEST_FULL_PATH,
},
mocks: {
$toast,
},
apolloProvider,
});
await waitForPromises();
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findRadioButtons = () => wrapper.findAllComponents(GlFormRadio);
const findSaveButton = () => wrapper.findComponent(GlButton);
const selectRadioOption = async (value) => {
findRadioGroup().vm.$emit('input', value);
const radioButton = findRadioButtons().wrappers.find(
(btn) => btn.attributes('value') === value,
);
radioButton.vm.$emit('change');
await waitForPromises();
};
afterEach(() => {
jest.clearAllMocks();
});
describe('on render', () => {
it('renders the form group with correct label', async () => {
await createComponent();
expect(findFormGroup().exists()).toBe(true);
expect(findFormGroup().attributes('label')).toBe('Minimum role to use pipeline variables');
});
it('renders all role options as radio buttons', async () => {
await createComponent();
expect(findRadioButtons()).toHaveLength(
PipelineVariablesMinimumOverrideRole.ROLE_OPTIONS.length,
);
PipelineVariablesMinimumOverrideRole.ROLE_OPTIONS.forEach((option, index) => {
expect(findRadioButtons().at(index).attributes('value')).toBe(option.value);
expect(findRadioButtons().at(index).text()).toContain(option.text);
});
});
it('has the correct help path', () => {
expect(PipelineVariablesMinimumOverrideRole.helpPath).toBe(
helpPagePath('ci/variables/_index', {
anchor: 'set-a-minimum-role-for-pipeline-variables',
}),
);
});
});
describe('GraphQL operations', () => {
describe('query', () => {
it('fetches initial role setting successfully', async () => {
await createComponent();
expect(defaultQueryResponse).toHaveBeenCalledWith({ fullPath: TEST_FULL_PATH });
expect(wrapper.vm.minimumOverrideRole).toBe('maintainer');
expect(findRadioGroup().attributes('checked')).toBe('maintainer');
});
it('sets default role to `developer` when query returns null', async () => {
const queryHandler = jest.fn().mockResolvedValue({
data: {
project: null,
},
});
await createComponent({ queryHandler });
expect(wrapper.vm.minimumOverrideRole).toBe(MINIMUM_ROLE_DEVELOPER);
});
it('shows error alert when query fails', async () => {
const queryHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
await createComponent({ queryHandler });
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(
'There was a problem fetching the latest minimum override role.',
);
});
});
describe('mutation', () => {
it('updates role setting successfully', async () => {
await createComponent();
await selectRadioOption(MINIMUM_ROLE_MAINTAINER);
findSaveButton().vm.$emit('click');
expect(defaultMutationResponse).toHaveBeenCalledWith({
fullPath: TEST_FULL_PATH,
pipelineVariablesMinimumOverrideRole: MINIMUM_ROLE_MAINTAINER,
});
});
it('displays a toast message on success', async () => {
await createComponent();
await selectRadioOption(MINIMUM_ROLE_MAINTAINER);
findSaveButton().vm.$emit('click');
await waitForPromises();
expect($toast.show).toHaveBeenCalledWith(
'Pipeline variable minimum override role successfully updated.',
);
});
it('shows error alert when mutation returns errors', async () => {
const mutationHandler = jest.fn().mockResolvedValue({
data: {
namespaceSettingsUpdate: {
errors: [{ message: 'Update failed' }],
},
},
});
await createComponent({ mutationHandler });
await selectRadioOption(MINIMUM_ROLE_MAINTAINER);
findSaveButton().vm.$emit('click');
await waitForPromises();
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(
'There was a problem updating the minimum override setting.',
);
});
it('shows error alert when mutation fails', async () => {
const mutationHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
await createComponent({ mutationHandler });
await selectRadioOption(MINIMUM_ROLE_MAINTAINER);
findSaveButton().vm.$emit('click');
await waitForPromises();
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(
'There was a problem updating the minimum override setting.',
);
});
});
});
});
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册