diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue
index 6ad02fedefa48981a5e82f06aee7eea0e44065c9..b927483ade5c1f6df51e7e1444dca95eb5f9cf9c 100644
--- a/app/assets/javascripts/token_access/components/inbound_token_access.vue
+++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue
@@ -2,8 +2,9 @@
import {
GlAlert,
GlButton,
- GlLink,
+ GlCollapsibleListbox,
GlIcon,
+ GlLink,
GlLoadingIcon,
GlSprintf,
GlTooltipDirective,
@@ -22,6 +23,10 @@ import inboundUpdateCIJobTokenScopeMutation from '../graphql/mutations/inbound_u
import inboundGetCIJobTokenScopeQuery from '../graphql/queries/inbound_get_ci_job_token_scope.query.graphql';
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '../graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
import getCiJobTokenScopeAllowlistQuery from '../graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
+import {
+ JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT,
+ JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG,
+} from '../constants';
import TokenAccessTable from './token_access_table.vue';
import NamespaceForm from './namespace_form.vue';
@@ -38,6 +43,7 @@ export default {
settingDisabledMessage: s__(
'CICD|Access unrestricted, so users with sufficient permissions in this project can authenticate with a job token generated in any other project.',
),
+ add: __('Add'),
addGroupOrProject: __('Add group or project'),
projectsFetchError: __('There was a problem fetching the projects'),
scopeFetchError: __('There was a problem fetching the job token scope value'),
@@ -58,11 +64,22 @@ export default {
text: s__('CICD|Only this project and any groups and projects in the allowlist'),
},
],
+ crudFormActions: [
+ {
+ text: __('Group or project'),
+ value: JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT,
+ },
+ {
+ text: __('All projects in authentication log'),
+ value: JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG,
+ },
+ ],
components: {
GlAlert,
GlButton,
- GlLink,
+ GlCollapsibleListbox,
GlIcon,
+ GlLink,
GlLoadingIcon,
GlSprintf,
CrudComponent,
@@ -132,6 +149,7 @@ export default {
projectName: '',
namespaceToEdit: null,
namespaceToRemove: null,
+ selectedAction: null,
};
},
computed: {
@@ -147,6 +165,9 @@ export default {
const { groups, projects } = this.groupsAndProjectsWithAccess;
return [...groups, ...projects];
},
+ canAutopopulateAuthLog() {
+ return this.glFeatures.authenticationLogsMigrationForAllowlist;
+ },
groupCount() {
return this.groupsAndProjectsWithAccess.groups.length;
},
@@ -169,6 +190,10 @@ export default {
},
},
methods: {
+ hideSelectedAction() {
+ this.namespaceToEdit = null;
+ this.selectedAction = null;
+ },
mapAllowlistNodes(list) {
// The defaultPermissions and jobTokenPolicies are separate fields from the target (the group or project in the
// allowlist). Combine them into a single object.
@@ -235,6 +260,11 @@ export default {
refetchGroupsAndProjects() {
this.$apollo.queries.groupsAndProjectsWithAccess.refetch();
},
+ selectAction(action, showFormFn) {
+ // TODO: render autopopulate modal when selected
+ this.selectedAction = action;
+ showFormFn();
+ },
showNamespaceForm(namespace, showFormFn) {
this.namespaceToEdit = namespace;
showFormFn();
@@ -289,10 +319,20 @@ export default {
+
+
+
diff --git a/app/assets/javascripts/token_access/constants.js b/app/assets/javascripts/token_access/constants.js
index 46f0e443ac43d0a44126e041cfcd11c1723a3a8a..80c013b2ed4c08bc386057ab5c0dadb4cb29a3dc 100644
--- a/app/assets/javascripts/token_access/constants.js
+++ b/app/assets/javascripts/token_access/constants.js
@@ -145,3 +145,6 @@ export const JOB_TOKEN_POLICIES = keyBy(
POLICIES_BY_RESOURCE.flatMap(({ policies }) => policies),
({ value }) => value,
);
+
+export const JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT = 'JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT';
+export const JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG = 'JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG';
diff --git a/app/assets/javascripts/vue_shared/components/crud_component.vue b/app/assets/javascripts/vue_shared/components/crud_component.vue
index aa97eca61418373bd196a3759447ea589abe1a95..671f4f253623a9cc8dc99d97b43a06f4d43b3b49 100644
--- a/app/assets/javascripts/vue_shared/components/crud_component.vue
+++ b/app/assets/javascripts/vue_shared/components/crud_component.vue
@@ -217,7 +217,7 @@ export default {
-
+
{
const failureHandler = jest.fn().mockRejectedValue(error);
const mockToastShow = jest.fn();
+ const findFormSelector = () => wrapper.findByTestId('form-selector');
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findToggleFormBtn = () => wrapper.findByTestId('crud-form-toggle');
@@ -72,13 +74,18 @@ describe('TokenAccess component', () => {
const createComponent = (
requestHandlers,
- { addPoliciesToCiJobToken = false, enforceAllowlist = false, stubs = {} } = {},
+ {
+ addPoliciesToCiJobToken = false,
+ authenticationLogsMigrationForAllowlist = false,
+ enforceAllowlist = false,
+ stubs = {},
+ } = {},
) => {
wrapper = shallowMountExtended(InboundTokenAccess, {
provide: {
fullPath: projectPath,
enforceAllowlist,
- glFeatures: { addPoliciesToCiJobToken },
+ glFeatures: { addPoliciesToCiJobToken, authenticationLogsMigrationForAllowlist },
},
apolloProvider: createMockApollo(requestHandlers),
mocks: {
@@ -349,6 +356,53 @@ describe('TokenAccess component', () => {
});
});
+ describe('when authenticationLogsMigrationForAllowlist feature flag is disabled', () => {
+ beforeEach(() =>
+ createComponent(
+ [
+ [
+ inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
+ inboundGroupsAndProjectsWithScopeResponseHandler,
+ ],
+ ],
+ { authenticationLogsMigrationForAllowlist: false, stubs: { CrudComponent } },
+ ),
+ );
+
+ it('renders toggle form button and hides actions dropdown', () => {
+ expect(findToggleFormBtn().exists()).toBe(true);
+ expect(findFormSelector().exists()).toBe(false);
+ });
+ });
+
+ describe('when authenticationLogsMigrationForAllowlist feature flag is enabled', () => {
+ beforeEach(() =>
+ createComponent(
+ [
+ [
+ inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
+ inboundGroupsAndProjectsWithScopeResponseHandler,
+ ],
+ ],
+ { authenticationLogsMigrationForAllowlist: true, stubs: { CrudComponent } },
+ ),
+ );
+
+ it('toggle form button is replaced by actions dropdown', () => {
+ expect(findToggleFormBtn().exists()).toBe(false);
+ expect(findFormSelector().exists()).toBe(true);
+ });
+
+ it('Add group or project option renders the namespace form', async () => {
+ expect(findNamespaceForm().exists()).toBe(false);
+
+ findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT);
+ await nextTick();
+
+ expect(findNamespaceForm().exists()).toBe(true);
+ });
+ });
+
describe.each`
type | mutation | handler
${'Group'} | ${inboundRemoveGroupCIJobTokenScopeMutation} | ${inboundRemoveGroupSuccessHandler}
diff --git a/spec/frontend/vue_shared/components/crud_component_spec.js b/spec/frontend/vue_shared/components/crud_component_spec.js
index d7b17bcc77a57beb9ec9f2b9f454dc7e13fe1855..01bc9914852aab9b634a739a0e4fabef2f463230 100644
--- a/spec/frontend/vue_shared/components/crud_component_spec.js
+++ b/spec/frontend/vue_shared/components/crud_component_spec.js
@@ -241,4 +241,15 @@ describe('CRUD Component', () => {
);
});
});
+
+ describe('actions slot', () => {
+ it('passes the showForm function to the actions slot', () => {
+ const actionsSlot = jest.fn();
+ createComponent({}, { actions: actionsSlot });
+
+ expect(actionsSlot).toHaveBeenCalledWith(
+ expect.objectContaining({ showForm: wrapper.vm.showForm }),
+ );
+ });
+ });
});