Commit 2fd4e9c4 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '347414-coonnect-mutation-sec-training-config' into 'master'

Connect mutation for security training configuration

See merge request gitlab-org/gitlab!76570
parents 7e084da1 6e440f27
<script> <script>
import { GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui'; import { GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui';
import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql'; import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql';
import configureSecurityTrainingProvidersMutation from '../graphql/configure_security_training_providers.mutation.graphql';
export default { export default {
components: { components: {
...@@ -9,6 +10,7 @@ export default { ...@@ -9,6 +10,7 @@ export default {
GlLink, GlLink,
GlSkeletonLoader, GlSkeletonLoader,
}, },
inject: ['projectPath'],
apollo: { apollo: {
securityTrainingProviders: { securityTrainingProviders: {
query: securityTrainingProvidersQuery, query: securityTrainingProvidersQuery,
...@@ -16,6 +18,7 @@ export default { ...@@ -16,6 +18,7 @@ export default {
}, },
data() { data() {
return { return {
toggleLoading: false,
securityTrainingProviders: [], securityTrainingProviders: [],
}; };
}, },
...@@ -24,6 +27,37 @@ export default { ...@@ -24,6 +27,37 @@ export default {
return this.$apollo.queries.securityTrainingProviders.loading; return this.$apollo.queries.securityTrainingProviders.loading;
}, },
}, },
methods: {
toggleProvider(selectedProviderId) {
const toggledProviders = this.securityTrainingProviders.map((provider) => ({
...provider,
...(provider.id === selectedProviderId && { isEnabled: !provider.isEnabled }),
}));
this.storeEnabledProviders(toggledProviders);
},
storeEnabledProviders(toggledProviders) {
const enabledProviderIds = toggledProviders
.filter(({ isEnabled }) => isEnabled)
.map(({ id }) => id);
this.toggleLoading = true;
return this.$apollo
.mutate({
mutation: configureSecurityTrainingProvidersMutation,
variables: {
input: {
enabledProviders: enabledProviderIds,
fullPath: this.projectPath,
},
},
})
.then(() => {
this.toggleLoading = false;
});
},
},
}; };
</script> </script>
...@@ -46,7 +80,13 @@ export default { ...@@ -46,7 +80,13 @@ export default {
> >
<gl-card> <gl-card>
<div class="gl-display-flex"> <div class="gl-display-flex">
<gl-toggle :value="isEnabled" :label="__('Training mode')" label-position="hidden" /> <gl-toggle
:value="isEnabled"
:label="__('Training mode')"
label-position="hidden"
:is-loading="toggleLoading"
@change="toggleProvider(id)"
/>
<div class="gl-ml-5"> <div class="gl-ml-5">
<h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3> <h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3>
<p> <p>
......
mutation configureSecurityTrainingProviders($input: configureSecurityTrainingProvidersInput!) {
configureSecurityTrainingProviders(input: $input) @client {
securityTrainingProviders {
id
isEnabled
}
}
}
...@@ -2,38 +2,10 @@ import Vue from 'vue'; ...@@ -2,38 +2,10 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils'; import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
import { __ } from '~/locale';
import SecurityConfigurationApp from './components/app.vue'; import SecurityConfigurationApp from './components/app.vue';
import { securityFeatures, complianceFeatures } from './components/constants'; import { securityFeatures, complianceFeatures } from './components/constants';
import { augmentFeatures } from './utils'; import { augmentFeatures } from './utils';
import tempResolvers from './resolver';
// Note: this is behind a feature flag and only a placeholder
// until the actual GraphQL fields have been added
// https://gitlab.com/gitlab-org/gi tlab/-/issues/346480
export const tempResolvers = {
Query: {
securityTrainingProviders() {
return [
{
__typename: 'SecurityTrainingProvider',
id: 101,
name: __('Kontra'),
description: __('Interactive developer security education.'),
url: 'https://application.security/',
isEnabled: false,
},
{
__typename: 'SecurityTrainingProvider',
id: 102,
name: __('SecureCodeWarrior'),
description: __('Security training with guide and learning pathways.'),
url: 'https://www.securecodewarrior.com/',
isEnabled: true,
},
];
},
},
};
export const initSecurityConfiguration = (el) => { export const initSecurityConfiguration = (el) => {
if (!el) { if (!el) {
......
import produce from 'immer';
import { __ } from '~/locale';
import securityTrainingProvidersQuery from './graphql/security_training_providers.query.graphql';
// Note: this is behind a feature flag and only a placeholder
// until the actual GraphQL fields have been added
// https://gitlab.com/gitlab-org/gi tlab/-/issues/346480
export default {
Query: {
securityTrainingProviders() {
return [
{
__typename: 'SecurityTrainingProvider',
id: 101,
name: __('Kontra'),
description: __('Interactive developer security education.'),
url: 'https://application.security/',
isEnabled: false,
},
{
__typename: 'SecurityTrainingProvider',
id: 102,
name: __('SecureCodeWarrior'),
description: __('Security training with guide and learning pathways.'),
url: 'https://www.securecodewarrior.com/',
isEnabled: true,
},
];
},
},
Mutation: {
configureSecurityTrainingProviders: (
_,
{ input: { enabledProviders, primaryProvider } },
{ cache },
) => {
const sourceData = cache.readQuery({
query: securityTrainingProvidersQuery,
});
const data = produce(sourceData.securityTrainingProviders, (draftData) => {
/* eslint-disable no-param-reassign */
draftData.forEach((provider) => {
provider.isPrimary = provider.id === primaryProvider;
provider.isEnabled =
provider.id === primaryProvider || enabledProviders.includes(provider.id);
});
});
return {
__typename: 'configureSecurityTrainingProvidersPayload',
securityTrainingProviders: data,
};
},
},
};
...@@ -4,8 +4,14 @@ import Vue from 'vue'; ...@@ -4,8 +4,14 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue'; import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import configureSecurityTrainingProvidersMutation from '~/security_configuration/graphql/configure_security_training_providers.mutation.graphql';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { securityTrainingProviders, mockResolvers } from '../mock_data'; import {
securityTrainingProviders,
mockResolvers,
testProjectPath,
textProviderIds,
} from '../mock_data';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -18,6 +24,9 @@ describe('TrainingProviderList component', () => { ...@@ -18,6 +24,9 @@ describe('TrainingProviderList component', () => {
mockApollo = createMockApollo([], mockResolvers); mockApollo = createMockApollo([], mockResolvers);
wrapper = shallowMount(TrainingProviderList, { wrapper = shallowMount(TrainingProviderList, {
provide: {
projectPath: testProjectPath,
},
apolloProvider: mockApollo, apolloProvider: mockApollo,
}); });
}; };
...@@ -85,4 +94,36 @@ describe('TrainingProviderList component', () => { ...@@ -85,4 +94,36 @@ describe('TrainingProviderList component', () => {
}); });
}); });
}); });
describe('success mutation', () => {
const firstToggle = () => findToggles().at(0);
beforeEach(async () => {
jest.spyOn(mockApollo.defaultClient, 'mutate');
await waitForQueryToBeLoaded();
firstToggle().vm.$emit('change');
});
it('calls mutation when toggle is changed', () => {
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith(
expect.objectContaining({
mutation: configureSecurityTrainingProvidersMutation,
variables: { input: { enabledProviders: textProviderIds, fullPath: testProjectPath } },
}),
);
});
it.each`
loading | wait | desc
${true} | ${false} | ${'enables loading of GlToggle when mutation is called'}
${false} | ${true} | ${'disables loading of GlToggle when mutation is complete'}
`('$desc', async ({ loading, wait }) => {
if (wait) {
await waitForPromises();
}
expect(firstToggle().props('isLoading')).toBe(loading);
});
});
}); });
export const testProjectPath = 'foo/bar';
export const textProviderIds = [101, 102];
export const securityTrainingProviders = [ export const securityTrainingProviders = [
{ {
id: 101, id: textProviderIds[0],
name: 'Kontra', name: 'Kontra',
description: 'Interactive developer security education.', description: 'Interactive developer security education.',
url: 'https://application.security/', url: 'https://application.security/',
isEnabled: false, isEnabled: false,
}, },
{ {
id: 102, id: textProviderIds[1],
name: 'SecureCodeWarrior', name: 'SecureCodeWarrior',
description: 'Security training with guide and learning pathways.', description: 'Security training with guide and learning pathways.',
url: 'https://www.securecodewarrior.com/', url: 'https://www.securecodewarrior.com/',
......
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