diff options
58 files changed, 808 insertions, 620 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 8c3df170f6d..6b0a7f31f1a 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -165,7 +165,7 @@ rspec-ee migration pg9: rspec-ee unit pg9: extends: .rspec-ee-base-pg9 - parallel: 5 + parallel: 10 rspec-ee integration pg9: extends: .rspec-ee-base-pg9 @@ -186,7 +186,7 @@ rspec-ee unit pg10: extends: - .rspec-ee-base-pg10 - .only-master - parallel: 5 + parallel: 10 rspec-ee integration pg10: extends: diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js index daf61e5d467..97296a40d6e 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js +++ b/app/assets/javascripts/diff_notes/models/discussion.js @@ -1,4 +1,4 @@ -/* eslint-disable camelcase, guard-for-in, no-restricted-syntax */ +/* eslint-disable guard-for-in, no-restricted-syntax */ /* global NoteModel */ import $ from 'jquery'; @@ -40,13 +40,13 @@ class DiscussionModel { return true; } - resolveAllNotes(resolved_by) { + resolveAllNotes(resolvedBy) { for (const noteId in this.notes) { const note = this.notes[noteId]; if (!note.resolved) { note.resolved = true; - note.resolved_by = resolved_by; + note.resolved_by = resolvedBy; } } } diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js index 69a972f644d..9bde18c4edf 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js +++ b/app/assets/javascripts/diff_notes/stores/comments.js @@ -1,4 +1,4 @@ -/* eslint-disable camelcase, no-restricted-syntax, guard-for-in */ +/* eslint-disable no-restricted-syntax, guard-for-in */ /* global DiscussionModel */ import Vue from 'vue'; @@ -26,11 +26,11 @@ window.CommentsStore = { discussion.createNote(noteObj); }, - update(discussionId, noteId, resolved, resolved_by) { + update(discussionId, noteId, resolved, resolvedBy) { const discussion = this.state[discussionId]; const note = discussion.getNote(noteId); note.resolved = resolved; - note.resolved_by = resolved_by; + note.resolved_by = resolvedBy; }, delete(discussionId, noteId) { const discussion = this.state[discussionId]; diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index cdf62259479..0a978ab5869 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -41,7 +41,7 @@ export default { <div class="environments-container"> <gl-loading-icon v-if="isLoading" - :size="3" + size="md" class="prepend-top-default" label="Loading environments" /> diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 30299ccc7bc..8abc927c500 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -170,7 +170,7 @@ export default { <template v-if="shouldRenderFolderContent(model)"> <div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`"> - <gl-loading-icon :size="2" class="prepend-top-16" /> + <gl-loading-icon size="md" class="prepend-top-16" /> </div> <template v-else> diff --git a/app/assets/javascripts/error_tracking_settings/utils.js b/app/assets/javascripts/error_tracking_settings/utils.js index 6613e04ee0e..450e8728121 100644 --- a/app/assets/javascripts/error_tracking_settings/utils.js +++ b/app/assets/javascripts/error_tracking_settings/utils.js @@ -13,6 +13,6 @@ export const transformFrontendSettings = ({ apiHost, enabled, token, selectedPro return { api_host: apiHost || null, enabled, token: token || null, project }; }; -export const getDisplayName = project => `${project.organizationName} | ${project.name}`; +export const getDisplayName = project => `${project.organizationName} | ${project.slug}`; export default () => {}; diff --git a/app/assets/javascripts/helpers/diffs_helper.js b/app/assets/javascripts/helpers/diffs_helper.js index 9695d01ad3d..d2b8cb11fe0 100644 --- a/app/assets/javascripts/helpers/diffs_helper.js +++ b/app/assets/javascripts/helpers/diffs_helper.js @@ -1,9 +1,9 @@ export function hasInlineLines(diffFile) { - return diffFile?.highlighted_diff_lines?.length > 0; /* eslint-disable-line camelcase */ + return diffFile?.highlighted_diff_lines?.length > 0; } export function hasParallelLines(diffFile) { - return diffFile?.parallel_diff_lines?.length > 0; /* eslint-disable-line camelcase */ + return diffFile?.parallel_diff_lines?.length > 0; } export function isSingleViewStyle(diffFile) { @@ -11,9 +11,5 @@ export function isSingleViewStyle(diffFile) { } export function hasDiff(diffFile) { - return ( - hasInlineLines(diffFile) || - hasParallelLines(diffFile) || - !diffFile?.blob?.readable_text /* eslint-disable-line camelcase */ - ); + return hasInlineLines(diffFile) || hasParallelLines(diffFile) || !diffFile?.blob?.readable_text; } diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index d94dccad962..62084892d13 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -133,9 +133,9 @@ export const loadBranch = ({ dispatch, getters }, { projectId, branchId }) => ref: branch.commit.id, }); }) - .catch(() => { + .catch(err => { dispatch('showBranchNotFoundError', branchId); - return Promise.reject(); + throw err; }); export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, basePath }) => { diff --git a/app/assets/javascripts/lib/utils/webpack.js b/app/assets/javascripts/lib/utils/webpack.js index 37b17f0fe23..390294afcb7 100644 --- a/app/assets/javascripts/lib/utils/webpack.js +++ b/app/assets/javascripts/lib/utils/webpack.js @@ -8,7 +8,7 @@ export function resetServiceWorkersPublicPath() { // see: https://webpack.js.org/guides/public-path/ const relativeRootPath = (gon && gon.relative_url_root) || ''; const webpackAssetPath = joinPaths(relativeRootPath, '/assets/webpack/'); - __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase + __webpack_public_path__ = webpackAssetPath; // eslint-disable-line babel/camelcase // monaco-editor-webpack-plugin currently (incorrectly) references the // public path as a property of `window`. Once this is fixed upstream we diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js index e7fcc183715..25c357b6073 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign, camelcase, no-nested-ternary, no-continue */ +/* eslint-disable no-param-reassign, babel/camelcase, no-nested-ternary, no-continue */ import $ from 'jquery'; import Vue from 'vue'; diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index c301c304409..3cc95168ba1 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, consistent-return, camelcase */ +/* eslint-disable func-names, consistent-return */ import $ from 'jquery'; import { __ } from '../locale'; @@ -270,14 +270,14 @@ export default class BranchGraph { stroke: 'none', }); - const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; - const avatar_box_y = y - 10; + const avatarBoxX = this.offsetX + this.unitSpace * this.mspace + 10; + const avatarBoxY = y - 10; - r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ + r.rect(avatarBoxX, avatarBoxY, 20, 20).attr({ stroke: this.colors[commit.space], 'stroke-width': 2, }); - r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); + r.image(commit.author.icon, avatarBoxX, avatarBoxY, 20, 20); return r .text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0]) .attr({ diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 5756532d18d..c01024fc2cd 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,4 +1,4 @@ -/* eslint-disable no-restricted-properties, camelcase, +/* eslint-disable no-restricted-properties, babel/camelcase, no-unused-expressions, default-case, consistent-return, no-alert, no-param-reassign, no-else-return, no-shadow, no-useless-escape, diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue index 28f4ef62242..2156c4469da 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -3,7 +3,7 @@ import { mapActions, mapState } from 'vuex'; import { GlAlert } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; -import { FETCH_SETTINGS_ERROR_MESSAGE } from '../constants'; +import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants'; import SettingsForm from './settings_form.vue'; diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue index 6a617f97271..ad2fdb4fd40 100644 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -1,32 +1,16 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; -import { - GlFormGroup, - GlToggle, - GlFormSelect, - GlFormTextarea, - GlButton, - GlCard, - GlLoadingIcon, -} from '@gitlab/ui'; -import { s__, __, sprintf } from '~/locale'; import Tracking from '~/tracking'; import { - NAME_REGEX_LENGTH, UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE, -} from '../constants'; +} from '../../shared/constants'; import { mapComputed } from '~/vuex_shared/bindings'; +import ExpirationPolicyForm from '../../shared/components/expiration_policy_form.vue'; export default { components: { - GlFormGroup, - GlToggle, - GlFormSelect, - GlFormTextarea, - GlButton, - GlCard, - GlLoadingIcon, + ExpirationPolicyForm, }, mixins: [Tracking.mixin()], labelsConfig: { @@ -43,59 +27,7 @@ export default { computed: { ...mapState(['formOptions', 'isLoading']), ...mapGetters({ isEdited: 'getIsEdited' }), - ...mapComputed( - [ - 'enabled', - { key: 'cadence', getter: 'getCadence' }, - { key: 'older_than', getter: 'getOlderThan' }, - { key: 'keep_n', getter: 'getKeepN' }, - 'name_regex', - ], - 'updateSettings', - 'settings', - ), - policyEnabledText() { - return this.enabled ? __('enabled') : __('disabled'); - }, - toggleDescriptionText() { - return sprintf( - s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'), - { - toggleStatus: `<strong>${this.policyEnabledText}</strong>`, - }, - false, - ); - }, - regexHelpText() { - return sprintf( - s__( - 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}', - ), - { - codeStart: '<code>', - codeEnd: '</code>', - }, - false, - ); - }, - nameRegexPlaceholder() { - return '.*'; - }, - nameRegexState() { - return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null; - }, - formIsInvalid() { - return this.nameRegexState === false; - }, - isFormElementDisabled() { - return !this.enabled || this.isLoading; - }, - isSubmitButtonDisabled() { - return this.formIsInvalid || this.isLoading; - }, - isCancelButtonDisabled() { - return !this.isEdited || this.isLoading; - }, + ...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'), }, methods: { ...mapActions(['resetSettings', 'saveSettings']), @@ -114,127 +46,12 @@ export default { </script> <template> - <form ref="form-element" @submit.prevent="submit" @reset.prevent="reset"> - <gl-card> - <template #header> - {{ s__('ContainerRegistry|Tag expiration policy') }} - </template> - <template> - <gl-form-group - id="expiration-policy-toggle-group" - :label-cols="$options.labelsConfig.cols" - :label-align="$options.labelsConfig.align" - label-for="expiration-policy-toggle" - :label="s__('ContainerRegistry|Expiration policy:')" - > - <div class="d-flex align-items-start"> - <gl-toggle id="expiration-policy-toggle" v-model="enabled" :disabled="isLoading" /> - <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> - </div> - </gl-form-group> - - <gl-form-group - id="expiration-policy-interval-group" - :label-cols="$options.labelsConfig.cols" - :label-align="$options.labelsConfig.align" - label-for="expiration-policy-interval" - :label="s__('ContainerRegistry|Expiration interval:')" - > - <gl-form-select - id="expiration-policy-interval" - v-model="older_than" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - id="expiration-policy-schedule-group" - :label-cols="$options.labelsConfig.cols" - :label-align="$options.labelsConfig.align" - label-for="expiration-policy-schedule" - :label="s__('ContainerRegistry|Expiration schedule:')" - > - <gl-form-select - id="expiration-policy-schedule" - v-model="cadence" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.cadence" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - id="expiration-policy-latest-group" - :label-cols="$options.labelsConfig.cols" - :label-align="$options.labelsConfig.align" - label-for="expiration-policy-latest" - :label="s__('ContainerRegistry|Number of tags to retain:')" - > - <gl-form-select - id="expiration-policy-latest" - v-model="keep_n" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.keepN" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - id="expiration-policy-name-matching-group" - :label-cols="$options.labelsConfig.cols" - :label-align="$options.labelsConfig.align" - label-for="expiration-policy-name-matching" - :label=" - s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:') - " - :state="nameRegexState" - :invalid-feedback=" - s__('ContainerRegistry|The value of this input should be less than 255 characters') - " - > - <gl-form-textarea - id="expiration-policy-name-matching" - v-model="name_regex" - :placeholder="nameRegexPlaceholder" - :state="nameRegexState" - :disabled="isFormElementDisabled" - trim - /> - <template #description> - <span ref="regex-description" v-html="regexHelpText"></span> - </template> - </gl-form-group> - </template> - <template #footer> - <div class="d-flex justify-content-end"> - <gl-button - ref="cancel-button" - type="reset" - :disabled="isCancelButtonDisabled" - class="mr-2 d-block" - > - {{ __('Cancel') }} - </gl-button> - <gl-button - ref="save-button" - type="submit" - :disabled="isSubmitButtonDisabled" - variant="success" - class="d-flex justify-content-center align-items-center js-no-auto-disable" - > - {{ __('Save expiration policy') }} - <gl-loading-icon v-if="isLoading" class="ml-2" /> - </gl-button> - </div> - </template> - </gl-card> - </form> + <expiration-policy-form + v-model="settings" + :form-options="formOptions" + :is-loading="isLoading" + :disable-cancel-button="!isEdited" + @submit="submit" + @reset="reset" + /> </template> diff --git a/app/assets/javascripts/registry/settings/store/getters.js b/app/assets/javascripts/registry/settings/store/getters.js index cd6392bd0cc..639becebeec 100644 --- a/app/assets/javascripts/registry/settings/store/getters.js +++ b/app/assets/javascripts/registry/settings/store/getters.js @@ -1,10 +1,21 @@ import { isEqual } from 'lodash'; -import { findDefaultOption } from '../utils'; +import { findDefaultOption } from '../../shared/utils'; export const getCadence = state => state.settings.cadence || findDefaultOption(state.formOptions.cadence); + export const getKeepN = state => state.settings.keep_n || findDefaultOption(state.formOptions.keepN); + export const getOlderThan = state => state.settings.older_than || findDefaultOption(state.formOptions.olderThan); + +export const getSettings = (state, getters) => ({ + enabled: state.settings.enabled, + cadence: getters.getCadence, + older_than: getters.getOlderThan, + keep_n: getters.getKeepN, + name_regex: state.settings.name_regex, +}); + export const getIsEdited = state => !isEqual(state.original, state.settings); diff --git a/app/assets/javascripts/registry/settings/store/mutations.js b/app/assets/javascripts/registry/settings/store/mutations.js index b773f2dd44c..f562137db1a 100644 --- a/app/assets/javascripts/registry/settings/store/mutations.js +++ b/app/assets/javascripts/registry/settings/store/mutations.js @@ -9,8 +9,8 @@ export default { olderThan: JSON.parse(initialState.olderThanOptions), }; }, - [types.UPDATE_SETTINGS](state, settings) { - state.settings = { ...state.settings, ...settings }; + [types.UPDATE_SETTINGS](state, data) { + state.settings = { ...state.settings, ...data.settings }; }, [types.SET_SETTINGS](state, settings) { state.settings = settings; diff --git a/app/assets/javascripts/registry/settings/utils.js b/app/assets/javascripts/registry/settings/utils.js deleted file mode 100644 index 75af401e96d..00000000000 --- a/app/assets/javascripts/registry/settings/utils.js +++ /dev/null @@ -1,6 +0,0 @@ -export const findDefaultOption = options => { - const item = options.find(o => o.default); - return item ? item.key : null; -}; - -export default () => {}; diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue new file mode 100644 index 00000000000..c044add3759 --- /dev/null +++ b/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue @@ -0,0 +1,247 @@ +<script> +import { uniqueId } from 'lodash'; +import { + GlFormGroup, + GlToggle, + GlFormSelect, + GlFormTextarea, + GlButton, + GlCard, + GlLoadingIcon, +} from '@gitlab/ui'; +import { s__, __, sprintf } from '~/locale'; +import { NAME_REGEX_LENGTH } from '../constants'; +import { mapComputedToEvent } from '../utils'; + +export default { + components: { + GlFormGroup, + GlToggle, + GlFormSelect, + GlFormTextarea, + GlButton, + GlCard, + GlLoadingIcon, + }, + props: { + formOptions: { + type: Object, + required: false, + default: () => ({}), + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + value: { + type: Object, + required: false, + default: () => ({}), + }, + labelCols: { + type: [Number, String], + required: false, + default: 3, + }, + labelAlign: { + type: String, + required: false, + default: 'right', + }, + disableCancelButton: { + type: Boolean, + required: false, + default: false, + }, + }, + nameRegexPlaceholder: '.*', + data() { + return { + uniqueId: uniqueId(), + }; + }, + computed: { + ...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'), + policyEnabledText() { + return this.enabled ? __('enabled') : __('disabled'); + }, + toggleDescriptionText() { + return sprintf( + s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'), + { + toggleStatus: `<strong>${this.policyEnabledText}</strong>`, + }, + false, + ); + }, + regexHelpText() { + return sprintf( + s__( + 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}', + ), + { + codeStart: '<code>', + codeEnd: '</code>', + }, + false, + ); + }, + nameRegexState() { + return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null; + }, + formIsInvalid() { + return this.nameRegexState === false; + }, + isFormElementDisabled() { + return !this.enabled || this.isLoading; + }, + isSubmitButtonDisabled() { + return this.formIsInvalid || this.isLoading; + }, + isCancelButtonDisabled() { + return this.disableCancelButton || this.isLoading; + }, + }, + methods: { + idGenerator(id) { + return `${id}_${this.uniqueId}`; + }, + }, +}; +</script> + +<template> + <form + ref="form-element" + class="lh-2" + @submit.prevent="$emit('submit')" + @reset.prevent="$emit('reset')" + > + <gl-card> + <template #header> + {{ s__('ContainerRegistry|Tag expiration policy') }} + </template> + <template> + <gl-form-group + :id="idGenerator('expiration-policy-toggle-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-toggle')" + :label="s__('ContainerRegistry|Expiration policy:')" + > + <div class="d-flex align-items-start"> + <gl-toggle + :id="idGenerator('expiration-policy-toggle')" + v-model="enabled" + :disabled="isLoading" + /> + <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> + </div> + </gl-form-group> + + <gl-form-group + :id="idGenerator('expiration-policy-interval-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-interval')" + :label="s__('ContainerRegistry|Expiration interval:')" + > + <gl-form-select + :id="idGenerator('expiration-policy-interval')" + v-model="older_than" + :disabled="isFormElementDisabled" + > + <option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key"> + {{ option.label }} + </option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + :id="idGenerator('expiration-policy-schedule-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-schedule')" + :label="s__('ContainerRegistry|Expiration schedule:')" + > + <gl-form-select + :id="idGenerator('expiration-policy-schedule')" + v-model="cadence" + :disabled="isFormElementDisabled" + > + <option v-for="option in formOptions.cadence" :key="option.key" :value="option.key"> + {{ option.label }} + </option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + :id="idGenerator('expiration-policy-latest-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-latest')" + :label="s__('ContainerRegistry|Number of tags to retain:')" + > + <gl-form-select + :id="idGenerator('expiration-policy-latest')" + v-model="keep_n" + :disabled="isFormElementDisabled" + > + <option v-for="option in formOptions.keepN" :key="option.key" :value="option.key"> + {{ option.label }} + </option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + :id="idGenerator('expiration-policy-name-matching-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-name-matching')" + :label=" + s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:') + " + :state="nameRegexState" + :invalid-feedback=" + s__('ContainerRegistry|The value of this input should be less than 255 characters') + " + > + <gl-form-textarea + :id="idGenerator('expiration-policy-name-matching')" + v-model="name_regex" + :placeholder="$options.nameRegexPlaceholder" + :state="nameRegexState" + :disabled="isFormElementDisabled" + trim + /> + <template #description> + <span ref="regex-description" v-html="regexHelpText"></span> + </template> + </gl-form-group> + </template> + <template #footer> + <div class="d-flex justify-content-end"> + <gl-button + ref="cancel-button" + type="reset" + class="mr-2 d-block" + :disabled="isCancelButtonDisabled" + > + {{ __('Cancel') }} + </gl-button> + <gl-button + ref="save-button" + type="submit" + :disabled="isSubmitButtonDisabled" + variant="success" + class="d-flex justify-content-center align-items-center js-no-auto-disable" + > + {{ __('Save expiration policy') }} + <gl-loading-icon v-if="isLoading" class="ml-2" /> + </gl-button> + </div> + </template> + </gl-card> + </form> +</template> diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/shared/constants.js index c0dac466b29..c0dac466b29 100644 --- a/app/assets/javascripts/registry/settings/constants.js +++ b/app/assets/javascripts/registry/shared/constants.js diff --git a/app/assets/javascripts/registry/shared/utils.js b/app/assets/javascripts/registry/shared/utils.js new file mode 100644 index 00000000000..d85a3ad28c2 --- /dev/null +++ b/app/assets/javascripts/registry/shared/utils.js @@ -0,0 +1,19 @@ +export const findDefaultOption = options => { + const item = options.find(o => o.default); + return item ? item.key : null; +}; + +export const mapComputedToEvent = (list, root) => { + const result = {}; + list.forEach(e => { + result[e] = { + get() { + return this[root][e]; + }, + set(value) { + this.$emit('input', { ...this[root], [e]: value }); + }, + }; + }); + return result; +}; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 6d7d863f273..6821df57b5a 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */ +/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, babel/camelcase, no-param-reassign */ /* global Issuable */ /* global emitSidebarEvent */ diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue index cdcfff42981..271a375ade2 100644 --- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue +++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue @@ -121,7 +121,7 @@ export default { :title="title" > <slot> - <icon name="duplicate" /> + <icon name="copy-to-clipboard" /> </slot> </gl-button> </template> diff --git a/app/assets/javascripts/webpack.js b/app/assets/javascripts/webpack.js index ced847294ae..4f558843357 100644 --- a/app/assets/javascripts/webpack.js +++ b/app/assets/javascripts/webpack.js @@ -5,5 +5,5 @@ */ if (gon && gon.webpack_public_path) { - __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line camelcase + __webpack_public_path__ = gon.webpack_public_path; // eslint-disable-line babel/camelcase } diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 044d703630e..ab0b0b02aa8 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,4 +1,4 @@ -/* eslint-disable consistent-return, camelcase, class-methods-use-this */ +/* eslint-disable consistent-return, class-methods-use-this */ // Zen Mode (full screen) textarea // @@ -91,8 +91,8 @@ export default class ZenMode { } } - scrollTo(zen_area) { - return $.scrollTo(zen_area, 0, { + scrollTo(zenArea) { + return $.scrollTo(zenArea, 0, { offset: -150, }); } diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 610d823dd3c..e1aed5393ea 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -53,7 +53,7 @@ module ButtonHelper } content_tag :button, button_attributes do - concat(sprite_icon('duplicate')) unless hide_button_icon + concat(sprite_icon('copy-to-clipboard')) unless hide_button_icon concat(button_text) end end diff --git a/app/models/concerns/delete_with_limit.rb b/app/models/concerns/delete_with_limit.rb new file mode 100644 index 00000000000..1ea18b6149b --- /dev/null +++ b/app/models/concerns/delete_with_limit.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module DeleteWithLimit + extend ActiveSupport::Concern + + class_methods do + def delete_with_limit(maximum) + limit(maximum).delete_all + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index ba585937e1c..606c4d8302f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -4,6 +4,8 @@ class Event < ApplicationRecord include Sortable include FromUnion include Presentable + include DeleteWithLimit + include CreatedAtFilterable default_scope { reorder(nil) } diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index ed2e4408ce3..03f1797f4f4 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -3,6 +3,8 @@ class WebHookLog < ApplicationRecord include SafeUrl include Presentable + include DeleteWithLimit + include CreatedAtFilterable belongs_to :web_hook diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index eb78938324d..dd2f1359e76 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -25,10 +25,9 @@ class EmailsOnPushService < Service end def initialize_properties - if properties.nil? - self.properties = {} - self.branches_to_be_notified ||= "all" - end + super + + self.branches_to_be_notified = 'all' if branches_to_be_notified.nil? end def execute(push_data) diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml index 52c7bc47ca7..1514ad55d71 100644 --- a/app/views/sherlock/queries/_general.html.haml +++ b/app/views/sherlock/queries/_general.html.haml @@ -27,7 +27,7 @@ .card-header .float-right %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } - = sprite_icon('duplicate') + = sprite_icon('copy-to-clipboard') %pre.hidden = @query.formatted_query %strong @@ -42,7 +42,7 @@ .card-header .float-right %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } - = sprite_icon('duplicate') + = sprite_icon('copy-to-clipboard') %pre.hidden = @query.explain %strong diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index f421e8dbf59..1d915832833 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -6,18 +6,12 @@ class PruneOldEventsWorker feature_category_not_owned! - # rubocop: disable CodeReuse/ActiveRecord + DELETE_LIMIT = 10_000 + def perform # Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity. - # Double nested query is used because MySQL doesn't allow DELETE subqueries on the same table. - Event.unscoped.where( - '(id IN (SELECT id FROM (?) ids_to_remove))', - Event.unscoped.where( - 'created_at < ?', - (3.years + 1.day).ago) - .select(:id) - .limit(10_000)) - .delete_all + cutoff_date = (3.years + 1.day).ago + + Event.unscoped.created_before(cutoff_date).delete_with_limit(DELETE_LIMIT) end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb index 8e48b45fc34..69a1dd43e69 100644 --- a/app/workers/prune_web_hook_logs_worker.rb +++ b/app/workers/prune_web_hook_logs_worker.rb @@ -11,20 +11,9 @@ class PruneWebHookLogsWorker # The maximum number of rows to remove in a single job. DELETE_LIMIT = 50_000 - # rubocop: disable CodeReuse/ActiveRecord def perform - # MySQL doesn't allow "DELETE FROM ... WHERE id IN ( ... )" if the inner - # query refers to the same table. To work around this we wrap the IN body in - # another sub query. - WebHookLog - .where( - 'id IN (SELECT id FROM (?) ids_to_remove)', - WebHookLog - .select(:id) - .where('created_at < ?', 90.days.ago.beginning_of_day) - .limit(DELETE_LIMIT) - ) - .delete_all + cutoff_date = 90.days.ago.beginning_of_day + + WebHookLog.created_before(cutoff_date).delete_with_limit(DELETE_LIMIT) end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/changelogs/unreleased/198577-fix-emails-on-push.yml b/changelogs/unreleased/198577-fix-emails-on-push.yml new file mode 100644 index 00000000000..70eb7e48c37 --- /dev/null +++ b/changelogs/unreleased/198577-fix-emails-on-push.yml @@ -0,0 +1,5 @@ +--- +title: Fix emails on push integrations created before 12.7 +merge_request: 23699 +author: +type: fixed diff --git a/changelogs/unreleased/pedroms-change-copy-to-clipboard-icon.yml b/changelogs/unreleased/pedroms-change-copy-to-clipboard-icon.yml new file mode 100644 index 00000000000..118441a4b82 --- /dev/null +++ b/changelogs/unreleased/pedroms-change-copy-to-clipboard-icon.yml @@ -0,0 +1,5 @@ +--- +title: Change vague copy to clipboard icon to a clearer icon. +merge_request: 23983 +author: +type: changed diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb index 2b21c9d9729..206c4daceac 100644 --- a/config/initializers/graphql.rb +++ b/config/initializers/graphql.rb @@ -6,6 +6,8 @@ GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_ke GraphQL::Schema::Object.accepts_definition(:authorize) GraphQL::Schema::Field.accepts_definition(:authorize) -GitlabSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: ENV.fetch('GITLAB_RAILS_GRAPHQL_TIMEOUT', 30).to_i) do |timeout_error, query| - Gitlab::GraphqlLogger.error(message: timeout_error.to_s, query: query.query_string, query_variables: query.provided_variables) +Gitlab::Application.config.after_initialize do + GitlabSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: ENV.fetch('GITLAB_RAILS_GRAPHQL_TIMEOUT', 30).to_i) do |timeout_error, query| + Gitlab::GraphqlLogger.error(message: timeout_error.to_s, query: query.query_string, query_variables: query.provided_variables) + end end diff --git a/package.json b/package.json index c8d35ad585e..3bb3a21a245 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ }, "devDependencies": { "@babel/plugin-transform-modules-commonjs": "^7.5.0", - "@gitlab/eslint-config": "^2.0.0", + "@gitlab/eslint-config": "^2.1.1", "@gitlab/eslint-plugin-i18n": "^1.1.0", "@gitlab/eslint-plugin-vue-i18n": "^1.2.0", "@vue/test-utils": "^1.0.0-beta.30", diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb index 9bbeb0eb260..72e2865dd6a 100644 --- a/spec/features/projects/settings/operations_settings_spec.rb +++ b/spec/features/projects/settings/operations_settings_spec.rb @@ -61,7 +61,7 @@ describe 'Projects > Settings > For a forked project', :js do within('div#project-dropdown') do click_button('Select project') - click_button('Sentry | Internal') + click_button('Sentry | internal') end click_button('Save changes') diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 1a1940f6efb..4c5bc290402 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -26,11 +26,11 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy' it 'saves expiration policy submit the form' do within '#js-registry-policies' do within '.card-body' do - find('#expiration-policy-toggle button:not(.is-disabled)').click - select('7 days until tags are automatically removed', from: 'expiration-policy-interval') - select('Every day', from: 'expiration-policy-schedule') - select('50 tags per image name', from: 'expiration-policy-latest') - fill_in('expiration-policy-name-matching', with: '*-production') + find('.gl-toggle-wrapper button:not(.is-disabled)').click + select('7 days until tags are automatically removed', from: 'Expiration interval:') + select('Every day', from: 'Expiration schedule:') + select('50 tags per image name', from: 'Number of tags to retain:') + fill_in('Docker tags with names matching this regex pattern will expire:', with: '*-production') end submit_button = find('.card-footer .btn.btn-success') expect(submit_button).not_to be_disabled diff --git a/spec/frontend/error_tracking_settings/store/getters_spec.js b/spec/frontend/error_tracking_settings/store/getters_spec.js index 2c5ff084b8a..b135fdee40b 100644 --- a/spec/frontend/error_tracking_settings/store/getters_spec.js +++ b/spec/frontend/error_tracking_settings/store/getters_spec.js @@ -47,7 +47,7 @@ describe('Error Tracking Settings - Getters', () => { it('should display correctly when a project is selected', () => { [state.selectedProject] = projectList; - expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | name'); + expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | slug'); }); it('should display correctly when no project is selected', () => { diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index 2762adfb57d..99c27ca30fb 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -1,6 +1,6 @@ import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import IdeStatusList from '~/ide/components/ide_status_list'; +import IdeStatusList from '~/ide/components/ide_status_list.vue'; const TEST_FILE = { name: 'lorem.md', diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js index 0c0d4c73d91..3f62dca4a57 100644 --- a/spec/frontend/mr_popover/mr_popover_spec.js +++ b/spec/frontend/mr_popover/mr_popover_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import MRPopover from '~/mr_popover/components/mr_popover'; +import MRPopover from '~/mr_popover/components/mr_popover.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; describe('MR Popover', () => { diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js index 8a5c5d84198..e9ba65e4387 100644 --- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js @@ -4,7 +4,7 @@ import component from '~/registry/settings/components/registry_settings_app.vue' import SettingsForm from '~/registry/settings/components/settings_form.vue'; import { createStore } from '~/registry/settings/store/'; import { SET_IS_DISABLED } from '~/registry/settings/store/mutation_types'; -import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/settings/constants'; +import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants'; describe('Registry Settings App', () => { let wrapper; diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js index 89dd161ec3e..eefb0313a0b 100644 --- a/spec/frontend/registry/settings/components/settings_form_spec.js +++ b/spec/frontend/registry/settings/components/settings_form_spec.js @@ -1,49 +1,33 @@ -import { mount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import Tracking from '~/tracking'; -import stubChildren from 'helpers/stub_children'; import component from '~/registry/settings/components/settings_form.vue'; +import expirationPolicyForm from '~/registry/shared/components/expiration_policy_form.vue'; import { createStore } from '~/registry/settings/store/'; import { - NAME_REGEX_LENGTH, UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE, -} from '~/registry/settings/constants'; -import { stringifiedFormOptions } from '../mock_data'; +} from '~/registry/shared/constants'; +import { stringifiedFormOptions } from '../../shared/mock_data'; describe('Settings Form', () => { let wrapper; let store; let dispatchSpy; - const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy'; const trackingPayload = { label: 'docker_container_retention_and_expiration_policies', }; - const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; + const findForm = () => wrapper.find(expirationPolicyForm); - const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`); - const findFormElements = (name, parent = wrapper) => - parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`); - const findCancelButton = () => wrapper.find({ ref: 'cancel-button' }); - const findSaveButton = () => wrapper.find({ ref: 'save-button' }); - const findForm = () => wrapper.find({ ref: 'form-element' }); - const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon); - - const mountComponent = (options = {}) => { - wrapper = mount(component, { - stubs: { - ...stubChildren(component), - GlCard: false, - GlLoadingIcon, - }, + const mountComponent = () => { + wrapper = shallowMount(component, { mocks: { $toast: { show: jest.fn(), }, }, store, - ...options, }); }; @@ -59,170 +43,50 @@ describe('Settings Form', () => { wrapper.destroy(); }); - it('renders', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe.each` - elementName | modelName | value | disabledByToggle - ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'} - ${'interval'} | ${'older_than'} | ${'foo'} | ${'disabled'} - ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'} - ${'latest'} | ${'keep_n'} | ${'foo'} | ${'disabled'} - ${'name-matching'} | ${'name_regex'} | ${'foo'} | ${'disabled'} - `( - `${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`, - ({ elementName, modelName, value, disabledByToggle }) => { - let formGroup; - beforeEach(() => { - formGroup = findFormGroup(elementName); - }); - it(`${elementName} form group exist in the dom`, () => { - expect(formGroup.exists()).toBe(true); - }); - - it(`${elementName} form group has a label-for property`, () => { - expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`); - }); - - it(`${elementName} form group has a label-cols property`, () => { - expect(formGroup.attributes('label-cols')).toBe(`${wrapper.vm.$options.labelsConfig.cols}`); - }); - - it(`${elementName} form group has a label-align property`, () => { - expect(formGroup.attributes('label-align')).toBe( - `${wrapper.vm.$options.labelsConfig.align}`, - ); - }); - - it(`${elementName} form group contains an input element`, () => { - expect(findFormElements(elementName, formGroup).exists()).toBe(true); - }); - - it(`${elementName} form element change updated ${modelName} with ${value}`, () => { - const element = findFormElements(elementName, formGroup); - const modelUpdateEvent = element.vm.$options.model - ? element.vm.$options.model.event - : 'input'; - element.vm.$emit(modelUpdateEvent, value); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm[modelName]).toBe(value); - }); - }); - - it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => { - store.dispatch('updateSettings', { enabled: false }); - const expectation = disabledByToggle === 'disabled' ? 'true' : undefined; - expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation); - }); - }, - ); - - describe('form actions', () => { + describe('form', () => { let form; beforeEach(() => { form = findForm(); }); - describe('cancel button', () => { - it('has type reset', () => { - expect(findCancelButton().attributes('type')).toBe('reset'); - }); - - it('is disabled the form was not changed from his original value', () => { - store.dispatch('receiveSettingsSuccess', { foo: 'bar' }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - }); - }); - - it('is disabled when the form data is loading', () => { - store.dispatch('toggleLoading'); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - }); - }); - - it('is enabled when the user changed something in the form and the data is not being loaded', () => { - store.dispatch('receiveSettingsSuccess', { foo: 'bar' }); - store.dispatch('updateSettings', { foo: 'baz' }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe(undefined); - }); + describe('data binding', () => { + it('v-model change update the settings property', () => { + dispatchSpy.mockReturnValue(); + form.vm.$emit('input', 'foo'); + expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' }); }); }); - describe('form cancel event', () => { + describe('form reset event', () => { it('calls the appropriate function', () => { dispatchSpy.mockReturnValue(); - form.trigger('reset'); + form.vm.$emit('reset'); expect(dispatchSpy).toHaveBeenCalledWith('resetSettings'); }); it('tracks the reset event', () => { dispatchSpy.mockReturnValue(); - form.trigger('reset'); + form.vm.$emit('reset'); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload); }); }); - it('save has type submit', () => { - expect(findSaveButton().attributes('type')).toBe('submit'); - }); - - describe('when isLoading is true', () => { - beforeEach(() => { - store.dispatch('toggleLoading'); - }); - - afterEach(() => { - store.dispatch('toggleLoading'); - }); - - it.each` - elementName - ${'toggle'} - ${'interval'} - ${'schedule'} - ${'latest'} - ${'name-matching'} - `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => { - expect(findFormElements(elementName).attributes('disabled')).toBe('true'); - }); - - it('submit button is disabled and shows a spinner', () => { - const button = findSaveButton(); - expect(button.attributes('disabled')).toBeTruthy(); - expect(findLoadingIcon(button)).toExist(); - }); - - it('cancel button is disabled', () => { - expect(findCancelButton().attributes('disabled')).toBeTruthy(); - }); - }); - describe('form submit event ', () => { - it('calls the appropriate function', () => { - dispatchSpy.mockResolvedValue(); - form.trigger('submit'); - expect(dispatchSpy).toHaveBeenCalled(); - }); - it('dispatches the saveSettings action', () => { dispatchSpy.mockResolvedValue(); - form.trigger('submit'); + form.vm.$emit('submit'); expect(dispatchSpy).toHaveBeenCalledWith('saveSettings'); }); it('tracks the submit event', () => { dispatchSpy.mockResolvedValue(); - form.trigger('submit'); + form.vm.$emit('submit'); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); }); it('show a success toast when submit succeed', () => { dispatchSpy.mockResolvedValue(); - form.trigger('submit'); + form.vm.$emit('submit'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success', @@ -232,7 +96,7 @@ describe('Settings Form', () => { it('show an error toast when submit fails', () => { dispatchSpy.mockRejectedValue(); - form.trigger('submit'); + form.vm.$emit('submit'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error', @@ -241,45 +105,4 @@ describe('Settings Form', () => { }); }); }); - - describe('form validation', () => { - describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => { - const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(','); - beforeEach(() => { - store.dispatch('updateSettings', { name_regex: invalidString }); - }); - - it('save btn is disabled', () => { - expect(findSaveButton().attributes('disabled')).toBeTruthy(); - }); - - it('nameRegexState is false', () => { - expect(wrapper.vm.nameRegexState).toBe(false); - }); - }); - - it('if the user did not type validation is null', () => { - store.dispatch('updateSettings', { name_regex: null }); - expect(wrapper.vm.nameRegexState).toBe(null); - return wrapper.vm.$nextTick().then(() => { - expect(findSaveButton().attributes('disabled')).toBeFalsy(); - }); - }); - - it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => { - store.dispatch('updateSettings', { name_regex: 'abc' }); - expect(wrapper.vm.nameRegexState).toBe(true); - }); - }); - - describe('help text', () => { - it('toggleDescriptionText text reflects enabled property', () => { - const toggleHelpText = findFormGroup('toggle').find('span'); - expect(toggleHelpText.html()).toContain('disabled'); - wrapper.setData({ enabled: true }); - return wrapper.vm.$nextTick().then(() => { - expect(toggleHelpText.html()).toContain('enabled'); - }); - }); - }); }); diff --git a/spec/frontend/registry/settings/store/actions_spec.js b/spec/frontend/registry/settings/store/actions_spec.js index f904d0b660a..5038dc82416 100644 --- a/spec/frontend/registry/settings/store/actions_spec.js +++ b/spec/frontend/registry/settings/store/actions_spec.js @@ -10,11 +10,14 @@ describe('Actions Registry Store', () => { ${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'} ${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined} ${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined} - `('%s action invokes %s mutation with payload %s', ({ actionName, mutationName, payload }) => { - it('should set the initial state', done => { - testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done); - }); - }); + `( + '$actionName invokes $mutationName with payload $payload', + ({ actionName, mutationName, payload }) => { + it('should set state', done => { + testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done); + }); + }, + ); describe('receiveSettingsSuccess', () => { it('calls SET_SETTINGS when data is present', () => { diff --git a/spec/frontend/registry/settings/store/getters_spec.js b/spec/frontend/registry/settings/store/getters_spec.js index d9ee53766d6..44631b97a39 100644 --- a/spec/frontend/registry/settings/store/getters_spec.js +++ b/spec/frontend/registry/settings/store/getters_spec.js @@ -1,6 +1,6 @@ import * as getters from '~/registry/settings/store/getters'; -import * as utils from '~/registry/settings/utils'; -import { formOptions } from '../mock_data'; +import * as utils from '~/registry/shared/utils'; +import { formOptions } from '../../shared/mock_data'; describe('Getters registry settings store', () => { const settings = { diff --git a/spec/frontend/registry/settings/store/mutations_spec.js b/spec/frontend/registry/settings/store/mutations_spec.js index deb59089d60..8ab0196fd4d 100644 --- a/spec/frontend/registry/settings/store/mutations_spec.js +++ b/spec/frontend/registry/settings/store/mutations_spec.js @@ -1,7 +1,7 @@ import mutations from '~/registry/settings/store/mutations'; import * as types from '~/registry/settings/store/mutation_types'; import createState from '~/registry/settings/store/state'; -import { formOptions, stringifiedFormOptions } from '../mock_data'; +import { formOptions, stringifiedFormOptions } from '../../shared/mock_data'; describe('Mutations Registry Store', () => { let mockState; @@ -28,7 +28,7 @@ describe('Mutations Registry Store', () => { mockState.settings = { foo: 'bar' }; const payload = { foo: 'baz' }; const expectedState = { ...mockState, settings: payload }; - mutations[types.UPDATE_SETTINGS](mockState, payload); + mutations[types.UPDATE_SETTINGS](mockState, { settings: payload }); expect(mockState.settings).toEqual(expectedState.settings); }); }); diff --git a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_form_spec.js.snap index 06f73c8f456..b53736951e1 100644 --- a/spec/frontend/registry/settings/components/__snapshots__/settings_form_spec.js.snap +++ b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_form_spec.js.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Settings Form renders 1`] = ` -<form> +exports[`Expiration Policy Form renders 1`] = ` +<form + class="lh-2" +> <div class="card" > @@ -56,7 +58,6 @@ exports[`Settings Form renders 1`] = ` <glformselect-stub disabled="true" id="expiration-policy-interval" - value="bar" > <option value="foo" @@ -85,7 +86,6 @@ exports[`Settings Form renders 1`] = ` <glformselect-stub disabled="true" id="expiration-policy-schedule" - value="bar" > <option value="foo" @@ -114,7 +114,6 @@ exports[`Settings Form renders 1`] = ` <glformselect-stub disabled="true" id="expiration-policy-latest" - value="bar" > <option value="foo" @@ -159,7 +158,6 @@ exports[`Settings Form renders 1`] = ` > <glbutton-stub class="mr-2 d-block" - disabled="true" size="md" type="reset" variant="secondary" diff --git a/spec/frontend/registry/shared/components/expiration_policy_form_spec.js b/spec/frontend/registry/shared/components/expiration_policy_form_spec.js new file mode 100644 index 00000000000..b51519925f1 --- /dev/null +++ b/spec/frontend/registry/shared/components/expiration_policy_form_spec.js @@ -0,0 +1,237 @@ +import { mount } from '@vue/test-utils'; +import stubChildren from 'helpers/stub_children'; +import component from '~/registry/shared/components/expiration_policy_form.vue'; + +import { NAME_REGEX_LENGTH } from '~/registry/shared/constants'; +import { formOptions } from '../mock_data'; + +describe('Expiration Policy Form', () => { + let wrapper; + + const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy'; + + const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; + + const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`); + const findFormElements = (name, parent = wrapper) => + parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`); + const findCancelButton = () => wrapper.find({ ref: 'cancel-button' }); + const findSaveButton = () => wrapper.find({ ref: 'save-button' }); + const findForm = () => wrapper.find({ ref: 'form-element' }); + const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon); + + const mountComponent = props => { + wrapper = mount(component, { + stubs: { + ...stubChildren(component), + GlCard: false, + GlLoadingIcon, + }, + propsData: { + formOptions, + ...props, + }, + methods: { + // override idGenerator to avoid having to test with dynamic uid + idGenerator: value => value, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders', () => { + mountComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + describe.each` + elementName | modelName | value | disabledByToggle + ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'} + ${'interval'} | ${'older_than'} | ${'foo'} | ${'disabled'} + ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'} + ${'latest'} | ${'keep_n'} | ${'foo'} | ${'disabled'} + ${'name-matching'} | ${'name_regex'} | ${'foo'} | ${'disabled'} + `( + `${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`, + ({ elementName, modelName, value, disabledByToggle }) => { + it(`${elementName} form group exist in the dom`, () => { + mountComponent(); + const formGroup = findFormGroup(elementName); + expect(formGroup.exists()).toBe(true); + }); + + it(`${elementName} form group has a label-for property`, () => { + mountComponent(); + const formGroup = findFormGroup(elementName); + expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`); + }); + + it(`${elementName} form group has a label-cols property`, () => { + mountComponent({ labelCols: '1' }); + const formGroup = findFormGroup(elementName); + return wrapper.vm.$nextTick().then(() => { + expect(formGroup.attributes('label-cols')).toBe('1'); + }); + }); + + it(`${elementName} form group has a label-align property`, () => { + mountComponent({ labelAlign: 'foo' }); + const formGroup = findFormGroup(elementName); + return wrapper.vm.$nextTick().then(() => { + expect(formGroup.attributes('label-align')).toBe('foo'); + }); + }); + + it(`${elementName} form group contains an input element`, () => { + mountComponent(); + const formGroup = findFormGroup(elementName); + expect(findFormElements(elementName, formGroup).exists()).toBe(true); + }); + + it(`${elementName} form element change updated ${modelName} with ${value}`, () => { + mountComponent(); + const formGroup = findFormGroup(elementName); + const element = findFormElements(elementName, formGroup); + + const modelUpdateEvent = element.vm.$options.model + ? element.vm.$options.model.event + : 'input'; + element.vm.$emit(modelUpdateEvent, value); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.emitted('input')).toEqual([[{ [modelName]: value }]]); + }); + }); + + it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => { + mountComponent({ settings: { enabled: false } }); + const formGroup = findFormGroup(elementName); + const expectation = disabledByToggle === 'disabled' ? 'true' : undefined; + expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation); + }); + }, + ); + + describe('form actions', () => { + describe('cancel button', () => { + it('has type reset', () => { + mountComponent(); + expect(findCancelButton().attributes('type')).toBe('reset'); + }); + + it('is disabled when disableCancelButton is true', () => { + mountComponent({ disableCancelButton: true }); + return wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe('true'); + }); + }); + + it('is disabled isLoading is true', () => { + mountComponent({ isLoading: true }); + return wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe('true'); + }); + }); + + it('is enabled when isLoading and disableCancelButton are false', () => { + mountComponent({ disableCancelButton: false, isLoading: false }); + return wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe(undefined); + }); + }); + }); + + describe('form cancel event', () => { + it('calls the appropriate function', () => { + mountComponent(); + findForm().trigger('reset'); + expect(wrapper.emitted('reset')).toBeTruthy(); + }); + }); + + it('save has type submit', () => { + mountComponent(); + expect(findSaveButton().attributes('type')).toBe('submit'); + }); + + describe('when isLoading is true', () => { + beforeEach(() => { + mountComponent({ isLoading: true }); + }); + + it.each` + elementName + ${'toggle'} + ${'interval'} + ${'schedule'} + ${'latest'} + ${'name-matching'} + `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => { + expect(findFormElements(elementName).attributes('disabled')).toBe('true'); + }); + + it('submit button is disabled and shows a spinner', () => { + const button = findSaveButton(); + expect(button.attributes('disabled')).toBeTruthy(); + expect(findLoadingIcon(button)).toExist(); + }); + }); + + describe('form submit event ', () => { + it('calls the appropriate function', () => { + mountComponent(); + findForm().trigger('submit'); + expect(wrapper.emitted('submit')).toBeTruthy(); + }); + }); + }); + + describe('form validation', () => { + describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => { + const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(','); + + beforeEach(() => { + mountComponent({ value: { name_regex: invalidString } }); + }); + + it('save btn is disabled', () => { + expect(findSaveButton().attributes('disabled')).toBeTruthy(); + }); + + it('nameRegexState is false', () => { + expect(wrapper.vm.nameRegexState).toBe(false); + }); + }); + + it('if the user did not type validation is null', () => { + mountComponent({ value: { name_regex: '' } }); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.nameRegexState).toBe(null); + expect(findSaveButton().attributes('disabled')).toBeFalsy(); + }); + }); + + it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => { + mountComponent({ value: { name_regex: 'foo' } }); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.nameRegexState).toBe(true); + }); + }); + }); + + describe('help text', () => { + it('toggleDescriptionText show disabled when settings.enabled is false', () => { + mountComponent(); + const toggleHelpText = findFormGroup('toggle').find('span'); + expect(toggleHelpText.html()).toContain('disabled'); + }); + + it('toggleDescriptionText show enabled when settings.enabled is true', () => { + mountComponent({ value: { enabled: true } }); + const toggleHelpText = findFormGroup('toggle').find('span'); + expect(toggleHelpText.html()).toContain('enabled'); + }); + }); +}); diff --git a/spec/frontend/registry/settings/mock_data.js b/spec/frontend/registry/shared/mock_data.js index 411363c2c95..411363c2c95 100644 --- a/spec/frontend/registry/settings/mock_data.js +++ b/spec/frontend/registry/shared/mock_data.js diff --git a/spec/frontend/releases/detail/components/app_spec.js b/spec/frontend/releases/detail/components/app_spec.js index fd5239ad44e..894cd3a8f14 100644 --- a/spec/frontend/releases/detail/components/app_spec.js +++ b/spec/frontend/releases/detail/components/app_spec.js @@ -1,6 +1,6 @@ import Vuex from 'vuex'; import { mount } from '@vue/test-utils'; -import ReleaseDetailApp from '~/releases/detail/components/app'; +import ReleaseDetailApp from '~/releases/detail/components/app.vue'; import { release } from '../../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; diff --git a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js index 13b7c426366..4853d9795b1 100644 --- a/spec/frontend/sidebar/confidential_issue_sidebar_spec.js +++ b/spec/frontend/sidebar/confidential_issue_sidebar_spec.js @@ -4,7 +4,7 @@ import ConfidentialIssueSidebar from '~/sidebar/components/confidential/confiden import EditForm from '~/sidebar/components/confidential/edit_form.vue'; import SidebarService from '~/sidebar/services/sidebar_service'; import createFlash from '~/flash'; -import RecaptchaModal from '~/vue_shared/components/recaptcha_modal'; +import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue'; jest.mock('~/flash'); jest.mock('~/sidebar/services/sidebar_service'); diff --git a/spec/frontend/vue_shared/components/recaptcha_modal_spec.js b/spec/frontend/vue_shared/components/recaptcha_modal_spec.js index 223e7187d99..8ab65efd388 100644 --- a/spec/frontend/vue_shared/components/recaptcha_modal_spec.js +++ b/spec/frontend/vue_shared/components/recaptcha_modal_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { eventHub } from '~/vue_shared/components/recaptcha_eventhub'; -import RecaptchaModal from '~/vue_shared/components/recaptcha_modal'; +import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue'; describe('RecaptchaModal', () => { const recaptchaFormId = 'recaptcha-form'; diff --git a/spec/frontend/vue_shared/components/slot_switch_spec.js b/spec/frontend/vue_shared/components/slot_switch_spec.js index 71e6087c272..73307b5573f 100644 --- a/spec/frontend/vue_shared/components/slot_switch_spec.js +++ b/spec/frontend/vue_shared/components/slot_switch_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import SlotSwitch from '~/vue_shared/components/slot_switch'; +import SlotSwitch from '~/vue_shared/components/slot_switch.vue'; describe('SlotSwitch', () => { const slots = { diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index e918c34ffef..cf8887f9731 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -173,7 +173,7 @@ describe ButtonHelper do expect(element.attr('data-clipboard-text')).to eq(nil) expect(element.inner_text).to eq("") - expect(element.to_html).to include sprite_icon('duplicate') + expect(element.to_html).to include sprite_icon('copy-to-clipboard') end end diff --git a/spec/models/concerns/delete_with_limit_spec.rb b/spec/models/concerns/delete_with_limit_spec.rb new file mode 100644 index 00000000000..52085f970f3 --- /dev/null +++ b/spec/models/concerns/delete_with_limit_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DeleteWithLimit do + describe '.delete_with_limit' do + it 'deletes a limited amount of rows' do + create_list(:web_hook_log, 4) + + expect do + WebHookLog.delete_with_limit(2) + end.to change { WebHookLog.count }.by(-2) + end + end +end diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb index 56f094ecb48..ce1952b503f 100644 --- a/spec/models/project_services/emails_on_push_service_spec.rb +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -21,6 +21,22 @@ describe EmailsOnPushService do end end + context 'when properties is missing branches_to_be_notified' do + subject { described_class.new(properties: {}) } + + it 'sets the default value to all' do + expect(subject.branches_to_be_notified).to eq('all') + end + end + + context 'when branches_to_be_notified is already set' do + subject { described_class.new(properties: { branches_to_be_notified: 'protected' }) } + + it 'does not overwrite it with the default value' do + expect(subject.branches_to_be_notified).to eq('protected') + end + end + context 'project emails' do let(:push_data) { { object_kind: 'push' } } let(:project) { create(:project, :repository) } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 5dc7de0ab8b..628d5533366 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -127,21 +127,6 @@ describe API::Services do expect(json_response['properties'].keys).to match_array(service_instance.api_field_names) end - it "returns empty hash or nil values if properties and data fields are empty" do - # deprecated services are not valid for update - initialized_service.update_attribute(:properties, {}) - - if initialized_service.data_fields_present? - initialized_service.data_fields.destroy - initialized_service.reload - end - - get api("/projects/#{project.id}/services/#{dashed_service}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['properties'].values.compact).to be_empty - end - it "returns error when authenticated but not a project owner" do project.add_developer(user2) get api("/projects/#{project.id}/services/#{dashed_service}", user2) diff --git a/yarn.lock b/yarn.lock index bf9b9c1d653..58ab2e590f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -705,18 +705,22 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@gitlab/eslint-config@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-2.0.0.tgz#e30dbf2b170a7a4ca003a321de9f4170a2512510" - integrity sha512-3Zw3ww8Q4hhVYxO7vliByD0yTeAQn4iBxOyqlASAZepZgdu/OmM4NPbWyntpTfDyHGoRGxmzEaCqv7DS6ubACA== - dependencies: - babel-eslint "^10.0.1" - eslint-config-airbnb-base "^13.1.0" - eslint-config-prettier "^3.3.0" +"@gitlab/eslint-config@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-2.1.1.tgz#64fcc8135f1a6055181fd64b991e33eb43913153" + integrity sha512-+rQA+gIcZbkaQ7GIjDjfMnYz41fFtsEaF0cRmk0KSqXWTKmOi4gcYZppIPdRvJWKhNPRS735Y5Of3gdIObINYQ== + dependencies: + "@gitlab/eslint-plugin-i18n" "^1.1.0" + "@gitlab/eslint-plugin-vue-i18n" "^1.2.0" + babel-eslint "^10.0.3" + eslint-config-airbnb-base "^13.2.0" + eslint-config-prettier "^3.6.0" + eslint-plugin-babel "^5.3.0" eslint-plugin-filenames "^1.3.2" - eslint-plugin-import "^2.18.2" - eslint-plugin-promise "^4.1.1" - eslint-plugin-vue "^5.0.0" + eslint-plugin-import "^2.20.0" + eslint-plugin-no-jquery "^2.3.1" + eslint-plugin-promise "^4.2.1" + eslint-plugin-vue "^5.2.3" "@gitlab/eslint-plugin-i18n@^1.1.0": version "1.1.0" @@ -1675,6 +1679,14 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" @@ -1832,17 +1844,17 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-eslint@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" - integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== +babel-eslint@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" + integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA== dependencies: "@babel/code-frame" "^7.0.0" "@babel/parser" "^7.0.0" "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" - eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" babel-jest@^24.1.0, babel-jest@^24.8.0: version "24.8.0" @@ -2878,6 +2890,11 @@ configstore@^3.0.0: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" +confusing-browser-globals@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== + connect-history-api-fallback@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -4156,21 +4173,22 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.12.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.2.tgz#4e874331645e9925edef141e74fc4bd144669d34" - integrity sha512-jYo/J8XU2emLXl3OLwfwtuFfuF2w6DYPs+xy9ZfVyPkDcrauu6LYrw/q2TyCtrbc/KUdCiC5e9UajRhgNkVopA== +es-abstract@^1.12.0, es-abstract@^1.17.0-next.1, es-abstract@^1.5.1, es-abstract@^1.7.0: + version "1.17.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" + integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" - is-callable "^1.1.4" - is-regex "^1.0.4" + is-callable "^1.1.5" + is-regex "^1.0.5" object-inspect "^1.7.0" object-keys "^1.1.1" - string.prototype.trimleft "^2.1.0" - string.prototype.trimright "^2.1.0" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -4213,19 +4231,19 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz#b5a1b480b80dfad16433d6c4ad84e6605052c05c" - integrity sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw== +eslint-config-airbnb-base@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz#f6ea81459ff4dec2dda200c35f1d8f7419d57943" + integrity sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w== dependencies: - eslint-restricted-globals "^0.1.1" + confusing-browser-globals "^1.0.5" object.assign "^4.1.0" - object.entries "^1.0.4" + object.entries "^1.1.0" -eslint-config-prettier@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.3.0.tgz#41afc8d3b852e757f06274ed6c44ca16f939a57d" - integrity sha512-Bc3bh5bAcKNvs3HOpSi6EfGA2IIp7EzWcg2tS4vP7stnXu/J1opihHDM7jI9JCIckyIDTgZLSWn7J3HY0j2JfA== +eslint-config-prettier@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.6.0.tgz#8ca3ffac4bd6eeef623a0651f9d754900e3ec217" + integrity sha512-ixJ4U3uTLXwJts4rmSVW/lMXjlGwCijhBJHk8iVqKKSifeI0qgFEfWl8L63isfc8Od7EiBALF6BX3jKLluf/jQ== dependencies: get-stdin "^6.0.0" @@ -4262,14 +4280,21 @@ eslint-import-resolver-webpack@^0.10.1: resolve "^1.4.0" semver "^5.3.0" -eslint-module-utils@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw== +eslint-module-utils@^2.4.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz#7878f7504824e1b857dd2505b59a8e5eda26a708" + integrity sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q== dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" +eslint-plugin-babel@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.0.tgz#2e7f251ccc249326da760c1a4c948a91c32d0023" + integrity sha512-HPuNzSPE75O+SnxHIafbW5QB45r2w78fxqwK3HmjqIUoPfPzVrq6rD+CINU3yzoDSzEhUkX07VUphbF73Lth/w== + dependencies: + eslint-rule-composer "^0.3.0" + eslint-plugin-filenames@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/eslint-plugin-filenames/-/eslint-plugin-filenames-1.3.2.tgz#7094f00d7aefdd6999e3ac19f72cea058e590cf7" @@ -4280,22 +4305,23 @@ eslint-plugin-filenames@^1.3.2: lodash.snakecase "4.1.1" lodash.upperfirst "4.3.1" -eslint-plugin-import@^2.18.2: - version "2.18.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" - integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== +eslint-plugin-import@^2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz#d749a7263fb6c29980def8e960d380a6aa6aecaa" + integrity sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ== dependencies: array-includes "^3.0.3" + array.prototype.flat "^1.2.1" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" + eslint-module-utils "^2.4.1" has "^1.0.3" minimatch "^3.0.4" object.values "^1.1.0" read-pkg-up "^2.0.0" - resolve "^1.11.0" + resolve "^1.12.0" eslint-plugin-jasmine@^2.10.1: version "2.10.1" @@ -4307,35 +4333,27 @@ eslint-plugin-jest@^22.3.0: resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.3.0.tgz#a10f10dedfc92def774ec9bb5bfbd2fb8e1c96d2" integrity sha512-P1mYVRNlOEoO5T9yTqOfucjOYf1ktmJ26NjwjH8sxpCFQa6IhBGr5TpKl3hcAAT29hOsRJVuMWmTsHoUVo9FoA== -eslint-plugin-no-jquery@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.3.0.tgz#fccdad84afa61baa4c0527dd6249cdcbfa0f74a8" - integrity sha512-XQQZM5yKO72Y8QAojNhH8oYLnLZU34FovNHVoJlPLBuBPJk0kkiPNOS/K6wRFbVgn47iZHsT6E+7mSLwbcQEsg== +eslint-plugin-no-jquery@^2.3.0, eslint-plugin-no-jquery@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.3.1.tgz#1c364cb863a38cc1570c8020155b6004cca62178" + integrity sha512-/fiQUBSOMUETnfBuiK5ewvtRbek1IRTy5ov/6RZ6nlybvZ337vyGaNPWM1KgaIoIeN7dairNrPfq0h7A0tpT3A== -eslint-plugin-promise@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db" - integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ== +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== -eslint-plugin-vue@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0.tgz#4a2cc1c0e71ea45e1bd9c1a60f925bfe68bb5710" - integrity sha512-mSv2Ebz3RaPP+XJO/mu7F+SdR9lrMyGISSExnarLFqqf3pF5wTmwWNrhHW1o9zKzKI811UVTIIkWJJvgO6SsUQ== +eslint-plugin-vue@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.3.tgz#3ee7597d823b5478804b2feba9863b1b74273961" + integrity sha512-mGwMqbbJf0+VvpGR5Lllq0PMxvTdrZ/ZPjmhkacrCHbubJeJOt+T6E3HUzAifa2Mxi7RSdJfC9HFpOeSYVMMIw== dependencies: - vue-eslint-parser "^4.0.2" + vue-eslint-parser "^5.0.0" -eslint-restricted-globals@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" - integrity sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc= - -eslint-scope@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== eslint-scope@^4.0.0, eslint-scope@^4.0.3: version "4.0.3" @@ -5033,7 +5051,7 @@ fstream@^1.0.0, fstream@^1.0.12: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.1.0, function-bind@^1.1.1: +function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== @@ -5932,10 +5950,10 @@ is-buffer@^2.0.0, is-buffer@^2.0.2: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== is-ci@^1.0.10: version "1.2.1" @@ -6153,12 +6171,12 @@ is-redirect@^1.0.0: resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== dependencies: - has "^1.0.1" + has "^1.0.3" is-regexp@^1.0.0: version "1.0.0" @@ -8254,15 +8272,15 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.entries@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" - integrity sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8= +object.entries@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" + integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" object.getownpropertydescriptors@^2.0.3: version "2.0.3" @@ -9811,10 +9829,10 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== +resolve@1.x, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" + integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== dependencies: path-parse "^1.0.6" @@ -10593,18 +10611,18 @@ string-width@^4.1.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^5.2.0" -string.prototype.trimleft@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" - integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== dependencies: define-properties "^1.1.3" function-bind "^1.1.1" -string.prototype.trimright@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" - integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== dependencies: define-properties "^1.1.3" function-bind "^1.1.1" @@ -11659,10 +11677,10 @@ vue-apollo@^3.0.0-beta.28: chalk "^2.4.1" throttle-debounce "^2.0.0" -vue-eslint-parser@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61" - integrity sha512-AUeQsYdO6+7QXCems+WvGlrXd37PHv/zcRQSQdY1xdOMwdFAPEnMBsv7zPvk0TPGulXkK/5p/ITgrjiYB7k3ag== +vue-eslint-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1" + integrity sha512-JlHVZwBBTNVvzmifwjpZYn0oPWH2SgWv5dojlZBsrhablDu95VFD+hriB1rQGwbD+bms6g+rAFhQHk6+NyiS6g== dependencies: debug "^4.1.0" eslint-scope "^4.0.0" |