diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 12:45:46 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-18 12:45:46 +0300 |
commit | a7b3560714b4d9cc4ab32dffcd1f74a284b93580 (patch) | |
tree | 7452bd5c3545c2fa67a28aa013835fb4fa071baf /app/assets/javascripts/security_configuration | |
parent | ee9173579ae56a3dbfe5afe9f9410c65bb327ca7 (diff) |
Add latest changes from gitlab-org/gitlab@14-8-stable-eev14.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/security_configuration')
11 files changed, 141 insertions, 147 deletions
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue index d228f77f27d..c48c9067250 100644 --- a/app/assets/javascripts/security_configuration/components/app.vue +++ b/app/assets/javascripts/security_configuration/components/app.vue @@ -50,7 +50,7 @@ export default { TrainingProviderList, }, mixins: [glFeatureFlagsMixin()], - inject: ['projectPath'], + inject: ['projectFullPath'], props: { augmentedSecurityFeatures: { type: Array, @@ -107,14 +107,14 @@ export default { shouldShowAutoDevopsEnabledAlert() { return ( this.autoDevopsEnabled && - !this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectPath) + !this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath) ); }, }, methods: { dismissAutoDevopsEnabledAlert() { const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects); - dismissedProjects.add(this.projectPath); + dismissedProjects.add(this.projectFullPath); this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects); }, onError(message) { diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index 034dba29196..81d222438e3 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -123,7 +123,7 @@ export const COVERAGE_FUZZING_CONFIG_HELP_PATH = helpPagePath( export const CORPUS_MANAGEMENT_NAME = __('Corpus Management'); export const CORPUS_MANAGEMENT_DESCRIPTION = s__( - 'SecurityConfiguration|Manage corpus files used as mutation sources in coverage fuzzing.', + 'SecurityConfiguration|Manage corpus files used as seed inputs with coverage-guided fuzzing.', ); export const CORPUS_MANAGEMENT_CONFIG_TEXT = s__('SecurityConfiguration|Manage corpus'); @@ -159,15 +159,6 @@ export const securityFeatures = [ helpPath: SAST_HELP_PATH, configurationHelpPath: SAST_CONFIG_HELP_PATH, type: REPORT_TYPE_SAST, - // This field is currently hardcoded because SAST is always available. - // It will eventually come from the Backend, the progress is tracked in - // https://gitlab.com/gitlab-org/gitlab/-/issues/331622 - available: true, - - // This field is currently hardcoded because SAST can always be enabled via MR - // It will eventually come from the Backend, the progress is tracked in - // https://gitlab.com/gitlab-org/gitlab/-/issues/331621 - canEnableByMergeRequest: true, }, { name: SAST_IAC_NAME, @@ -176,15 +167,6 @@ export const securityFeatures = [ helpPath: SAST_IAC_HELP_PATH, configurationHelpPath: SAST_IAC_CONFIG_HELP_PATH, type: REPORT_TYPE_SAST_IAC, - - // This field is currently hardcoded because SAST IaC is always available. - // It will eventually come from the Backend, the progress is tracked in - // https://gitlab.com/gitlab-org/gitlab/-/issues/331622 - available: true, - - // This field will eventually come from the backend, the progress is - // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/331621 - canEnableByMergeRequest: true, }, { name: DAST_NAME, @@ -206,10 +188,6 @@ export const securityFeatures = [ helpPath: DEPENDENCY_SCANNING_HELP_PATH, configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH, type: REPORT_TYPE_DEPENDENCY_SCANNING, - - // This field will eventually come from the backend, the progress is - // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/331621 - canEnableByMergeRequest: true, }, { name: CONTAINER_SCANNING_NAME, @@ -231,16 +209,6 @@ export const securityFeatures = [ helpPath: SECRET_DETECTION_HELP_PATH, configurationHelpPath: SECRET_DETECTION_CONFIG_HELP_PATH, type: REPORT_TYPE_SECRET_DETECTION, - - // This field is currently hardcoded because Secret Detection is always - // available. It will eventually come from the Backend, the progress is - // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/333113 - available: true, - - // This field is currently hardcoded because SAST can always be enabled via MR - // It will eventually come from the Backend, the progress is tracked in - // https://gitlab.com/gitlab-org/gitlab/-/issues/331621 - canEnableByMergeRequest: true, }, { name: API_FUZZING_NAME, diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index 33d72b54f86..1c37d8008de 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -24,9 +24,6 @@ export default { enabled() { return this.available && this.feature.configured; }, - hasStatus() { - return !this.available || typeof this.feature.configured === 'boolean'; - }, shortName() { return this.feature.shortName ?? this.feature.name; }, @@ -93,19 +90,17 @@ export default { data-testid="feature-status" :data-qa-selector="`${feature.type}_status`" > - <template v-if="hasStatus"> - <template v-if="enabled"> - <gl-icon name="check-circle-filled" /> - <span class="gl-text-green-700">{{ $options.i18n.enabled }}</span> - </template> + <template v-if="enabled"> + <gl-icon name="check-circle-filled" /> + <span class="gl-text-green-700">{{ $options.i18n.enabled }}</span> + </template> - <template v-else-if="available"> - {{ $options.i18n.notEnabled }} - </template> + <template v-else-if="available"> + {{ $options.i18n.notEnabled }} + </template> - <template v-else> - {{ $options.i18n.availableWith }} - </template> + <template v-else> + {{ $options.i18n.availableWith }} </template> </div> </div> diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue index ca4596e16b3..539e2bff17c 100644 --- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue +++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue @@ -1,6 +1,13 @@ <script> import { GlAlert, GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; +import Tracking from '~/tracking'; import { __ } from '~/locale'; +import { + TRACK_TOGGLE_TRAINING_PROVIDER_ACTION, + TRACK_TOGGLE_TRAINING_PROVIDER_LABEL, +} from '~/security_configuration/constants'; +import dismissUserCalloutMutation from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql'; import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql'; import configureSecurityTrainingProvidersMutation from '../graphql/configure_security_training_providers.mutation.graphql'; @@ -21,10 +28,19 @@ export default { GlLink, GlSkeletonLoader, }, - inject: ['projectPath'], + mixins: [Tracking.mixin()], + inject: ['projectFullPath'], apollo: { securityTrainingProviders: { query: securityTrainingProvidersQuery, + variables() { + return { + fullPath: this.projectFullPath, + }; + }, + update({ project }) { + return project?.securityTrainingProviders; + }, error() { this.errorMessage = this.$options.i18n.providerQueryErrorMessage; }, @@ -33,8 +49,9 @@ export default { data() { return { errorMessage: '', - toggleLoading: false, + providerLoadingId: null, securityTrainingProviders: [], + hasTouchedConfiguration: false, }; }, computed: { @@ -42,33 +59,59 @@ export default { return this.$apollo.queries.securityTrainingProviders.loading; }, }, + created() { + const unwatchConfigChance = this.$watch('hasTouchedConfiguration', () => { + this.dismissFeaturePromotionCallout(); + unwatchConfigChance(); + }); + }, methods: { - toggleProvider(selectedProviderId) { - const toggledProviders = this.securityTrainingProviders.map((provider) => ({ - ...provider, - ...(provider.id === selectedProviderId && { isEnabled: !provider.isEnabled }), - })); + async dismissFeaturePromotionCallout() { + try { + const { + data: { + userCalloutCreate: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: dismissUserCalloutMutation, + variables: { + input: { + featureName: 'security_training_feature_promotion', + }, + }, + }); - const enabledProviderIds = toggledProviders - .filter(({ isEnabled }) => isEnabled) - .map(({ id }) => id); + // handle errors reported from the backend + if (errors?.length > 0) { + throw new Error(errors[0]); + } + } catch (e) { + Sentry.captureException(e); + } + }, + toggleProvider(provider) { + const { isEnabled } = provider; + const toggledIsEnabled = !isEnabled; - this.storeEnabledProviders(toggledProviders, enabledProviderIds); + this.trackProviderToggle(provider.id, toggledIsEnabled); + this.storeProvider({ ...provider, isEnabled: toggledIsEnabled }); }, - async storeEnabledProviders(toggledProviders, enabledProviderIds) { - this.toggleLoading = true; + async storeProvider({ id, isEnabled, isPrimary }) { + this.providerLoadingId = id; try { const { data: { - configureSecurityTrainingProviders: { errors = [] }, + securityTrainingUpdate: { errors = [] }, }, } = await this.$apollo.mutate({ mutation: configureSecurityTrainingProvidersMutation, variables: { input: { - enabledProviders: enabledProviderIds, - fullPath: this.projectPath, + projectPath: this.projectFullPath, + providerId: id, + isEnabled, + isPrimary, }, }, }); @@ -77,12 +120,23 @@ export default { // throwing an error here means we can handle scenarios within the `catch` block below throw new Error(); } + + this.hasTouchedConfiguration = true; } catch { this.errorMessage = this.$options.i18n.configMutationErrorMessage; } finally { - this.toggleLoading = false; + this.providerLoadingId = null; } }, + trackProviderToggle(providerId, providerIsEnabled) { + this.track(TRACK_TOGGLE_TRAINING_PROVIDER_ACTION, { + label: TRACK_TOGGLE_TRAINING_PROVIDER_LABEL, + property: providerId, + extra: { + providerIsEnabled, + }, + }); + }, }, i18n, }; @@ -104,25 +158,21 @@ export default { </gl-skeleton-loader> </div> <ul v-else class="gl-list-style-none gl-m-0 gl-p-0"> - <li - v-for="{ id, isEnabled, name, description, url } in securityTrainingProviders" - :key="id" - class="gl-mb-6" - > + <li v-for="provider in securityTrainingProviders" :key="provider.id" class="gl-mb-6"> <gl-card> <div class="gl-display-flex"> <gl-toggle - :value="isEnabled" + :value="provider.isEnabled" :label="__('Training mode')" label-position="hidden" - :is-loading="toggleLoading" - @change="toggleProvider(id)" + :is-loading="providerLoadingId === provider.id" + @change="toggleProvider(provider)" /> <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">{{ provider.name }}</h3> <p> - {{ description }} - <gl-link :href="url" target="_blank">{{ __('Learn more.') }}</gl-link> + {{ provider.description }} + <gl-link :href="provider.url" target="_blank">{{ __('Learn more.') }}</gl-link> </p> </div> </div> diff --git a/app/assets/javascripts/security_configuration/components/upgrade_banner.vue b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue index 79e6b9d7a23..891d7bf2eb0 100644 --- a/app/assets/javascripts/security_configuration/components/upgrade_banner.vue +++ b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue @@ -1,11 +1,16 @@ <script> import { GlBanner } from '@gitlab/ui'; import { s__ } from '~/locale'; +import Tracking from '~/tracking'; + +export const SECURITY_UPGRADE_BANNER = 'security_upgrade_banner'; +export const UPGRADE_OR_FREE_TRIAL = 'upgrade_or_free_trial'; export default { components: { GlBanner, }, + mixins: [Tracking.mixin({ property: SECURITY_UPGRADE_BANNER })], inject: ['upgradePath'], i18n: { title: s__('SecurityConfiguration|Secure your project'), @@ -22,6 +27,17 @@ export default { ], buttonText: s__('SecurityConfiguration|Upgrade or start a free trial'), }, + mounted() { + this.track('render', { label: SECURITY_UPGRADE_BANNER }); + }, + methods: { + bannerClosed() { + this.track('dismiss_banner', { label: SECURITY_UPGRADE_BANNER }); + }, + bannerButtonClicked() { + this.track('click_button', { label: UPGRADE_OR_FREE_TRIAL }); + }, + }, }; </script> @@ -31,6 +47,8 @@ export default { :button-text="$options.i18n.buttonText" :button-link="upgradePath" variant="introduction" + @close="bannerClosed" + @primary="bannerButtonClicked" v-on="$listeners" > <p>{{ $options.i18n.bodyStart }}</p> diff --git a/app/assets/javascripts/security_configuration/constants.js b/app/assets/javascripts/security_configuration/constants.js new file mode 100644 index 00000000000..dc76436e91d --- /dev/null +++ b/app/assets/javascripts/security_configuration/constants.js @@ -0,0 +1,2 @@ +export const TRACK_TOGGLE_TRAINING_PROVIDER_ACTION = 'toggle_security_training_provider'; +export const TRACK_TOGGLE_TRAINING_PROVIDER_LABEL = 'update_security_training_provider'; diff --git a/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql b/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql index 660e0fadafb..3528bfaf7b8 100644 --- a/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql +++ b/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql @@ -1,9 +1,10 @@ -mutation configureSecurityTrainingProviders($input: configureSecurityTrainingProvidersInput!) { - configureSecurityTrainingProviders(input: $input) @client { +mutation updateSecurityTraining($input: SecurityTrainingUpdateInput!) { + securityTrainingUpdate(input: $input) { errors - securityTrainingProviders { + training { id isEnabled + isPrimary } } } diff --git a/app/assets/javascripts/security_configuration/graphql/security_training_providers.query.graphql b/app/assets/javascripts/security_configuration/graphql/security_training_providers.query.graphql index e0c5715ba8e..2baeda318f3 100644 --- a/app/assets/javascripts/security_configuration/graphql/security_training_providers.query.graphql +++ b/app/assets/javascripts/security_configuration/graphql/security_training_providers.query.graphql @@ -1,9 +1,13 @@ -query Query { - securityTrainingProviders @client { - name +query getSecurityTrainingProviders($fullPath: ID!) { + project(fullPath: $fullPath) { id - description - isEnabled - url + securityTrainingProviders { + name + id + description + isPrimary + isEnabled + url + } } } diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js index 24c0585e077..8416692dd27 100644 --- a/app/assets/javascripts/security_configuration/index.js +++ b/app/assets/javascripts/security_configuration/index.js @@ -5,7 +5,6 @@ import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils'; import SecurityConfigurationApp from './components/app.vue'; import { securityFeatures, complianceFeatures } from './components/constants'; import { augmentFeatures } from './utils'; -import tempResolvers from './resolver'; export const initSecurityConfiguration = (el) => { if (!el) { @@ -15,11 +14,11 @@ export const initSecurityConfiguration = (el) => { Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(tempResolvers), + defaultClient: createDefaultClient(), }); const { - projectPath, + projectFullPath, upgradePath, features, latestPipelinePath, @@ -38,7 +37,7 @@ export const initSecurityConfiguration = (el) => { el, apolloProvider, provide: { - projectPath, + projectFullPath, upgradePath, autoDevopsHelpPagePath, autoDevopsPath, diff --git a/app/assets/javascripts/security_configuration/resolver.js b/app/assets/javascripts/security_configuration/resolver.js deleted file mode 100644 index 93175d4a3d1..00000000000 --- a/app/assets/javascripts/security_configuration/resolver.js +++ /dev/null @@ -1,56 +0,0 @@ -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, - }; - }, - }, -}; diff --git a/app/assets/javascripts/security_configuration/utils.js b/app/assets/javascripts/security_configuration/utils.js index 47231497b8f..173560f8370 100644 --- a/app/assets/javascripts/security_configuration/utils.js +++ b/app/assets/javascripts/security_configuration/utils.js @@ -1,6 +1,19 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants'; +/** + * This function takes in 3 arrays of objects, securityFeatures, complianceFeatures and features. + * securityFeatures and complianceFeatures are static arrays living in the constants. + * features is dynamic and coming from the backend. + * This function builds a superset of those arrays. + * It looks for matching keys within the dynamic and the static arrays + * and will enrich the objects with the available static data. + * @param [{}] securityFeatures + * @param [{}] complianceFeatures + * @param [{}] features + * @returns {Object} Object with enriched features from constants divided into Security and Compliance Features + */ + export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => { const featuresByType = features.reduce((acc, feature) => { acc[feature.type] = convertObjectPropsToCamelCase(feature, { deep: true }); |