diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-27 15:10:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-27 15:10:44 +0300 |
commit | a4068557f4e8f0622798ccf824ac314e2008a57b (patch) | |
tree | a3ddbb6c902828009c0e02813339abe88cae9d60 | |
parent | d0a683e342c9e59bec8bb83879f0fc959b79224e (diff) |
Add latest changes from gitlab-org/gitlab@master
55 files changed, 1055 insertions, 827 deletions
diff --git a/app/assets/javascripts/groups/components/group_name_and_path.vue b/app/assets/javascripts/groups/components/group_name_and_path.vue index 983535d3e9c..9a1ea2f1812 100644 --- a/app/assets/javascripts/groups/components/group_name_and_path.vue +++ b/app/assets/javascripts/groups/components/group_name_and_path.vue @@ -6,6 +6,13 @@ import { GlInputGroupText, GlLink, GlAlert, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlTruncate, + GlSearchBoxByType, } from '@gitlab/ui'; import { debounce } from 'lodash'; @@ -15,6 +22,11 @@ import { createAlert } from '~/flash'; import { slugify } from '~/lib/utils/text_utility'; import axios from '~/lib/utils/axios_utils'; import { helpPagePath } from '~/helpers/help_page_helper'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; +import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; + +import searchGroupsWhereUserCanCreateSubgroups from '../queries/search_groups_where_user_can_create_subgroups.query.graphql'; const DEBOUNCE_DURATION = 1000; @@ -22,7 +34,6 @@ export default { i18n: { inputs: { name: { - label: s__('Groups|Group name'), placeholder: __('My awesome group'), description: s__( 'Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.', @@ -30,7 +41,6 @@ export default { invalidFeedback: s__('Groups|Enter a descriptive name for your group.'), }, path: { - label: s__('Groups|Group URL'), placeholder: __('my-awesome-group'), invalidFeedbackInvalidPattern: s__( 'GroupSettings|Choose a group path that does not start with a dash or end with a period. It can also contain alphanumeric characters and underscores.', @@ -40,9 +50,6 @@ export default { ), validFeedback: s__('Groups|Group path is available.'), }, - groupId: { - label: s__('Groups|Group ID'), - }, }, apiLoadingMessage: s__('Groups|Checking group URL availability...'), apiErrorMessage: __( @@ -51,7 +58,7 @@ export default { changingUrlWarningMessage: s__('Groups|Changing group URL can have unintended side effects.'), learnMore: s__('Groups|Learn more'), }, - nameInputSize: { md: 'lg' }, + inputSize: { md: 'lg' }, changingGroupPathHelpPagePath: helpPagePath('user/group/index', { anchor: 'change-a-groups-path', }), @@ -63,8 +70,35 @@ export default { GlInputGroupText, GlLink, GlAlert, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlTruncate, + GlSearchBoxByType, + }, + apollo: { + currentUserGroups: { + query: searchGroupsWhereUserCanCreateSubgroups, + variables() { + return { + search: this.search, + }; + }, + update(data) { + return data.currentUser?.groups?.nodes || []; + }, + skip() { + const hasNotEnoughSearchCharacters = + this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH; + + return this.shouldSkipQuery || hasNotEnoughSearchCharacters; + }, + debounce: DEBOUNCE_DELAY, + }, }, - inject: ['fields', 'basePath', 'mattermostEnabled'], + inject: ['fields', 'basePath', 'newSubgroup', 'mattermostEnabled'], data() { return { name: this.fields.name.value, @@ -76,9 +110,27 @@ export default { pathFeedbackState: null, pathInvalidFeedback: null, activeApiRequestAbortController: null, + search: '', + currentUserGroups: {}, + shouldSkipQuery: true, + selectedGroup: { + id: this.fields.parentId.value, + fullPath: this.fields.parentFullPath.value, + }, }; }, computed: { + inputLabels() { + return { + name: this.newSubgroup ? s__('Groups|Subgroup name') : s__('Groups|Group name'), + path: this.newSubgroup ? s__('Groups|Subgroup slug') : s__('Groups|Group URL'), + subgroupPath: s__('Groups|Subgroup URL'), + groupId: s__('Groups|Group ID'), + }; + }, + pathInputSize() { + return this.newSubgroup ? {} : this.$options.inputSize; + }, computedPath() { return this.apiSuggestedPath || this.path; }, @@ -129,9 +181,11 @@ export default { try { const { data: { exists, suggests }, - } = await getGroupPathAvailability(this.path, this.fields.parentId?.value, { - signal: this.activeApiRequestAbortController.signal, - }); + } = await getGroupPathAvailability( + this.path, + this.selectedGroup.id || this.fields.parentId.value, + { signal: this.activeApiRequestAbortController.signal }, + ); this.apiLoading = false; @@ -198,6 +252,21 @@ export default { this.pathInvalidFeedback = this.$options.i18n.inputs.path.invalidFeedbackInvalidPattern; this.pathFeedbackState = false; }, + handleDropdownShown() { + if (this.shouldSkipQuery) { + this.shouldSkipQuery = false; + } + + this.$refs.search.focusInput(); + }, + handleDropdownItemClick({ id, fullPath }) { + this.selectedGroup = { + id: getIdFromGraphQLId(id), + fullPath, + }; + + this.debouncedValidatePath(); + }, }, }; </script> @@ -208,10 +277,10 @@ export default { :id="fields.parentId.id" type="hidden" :name="fields.parentId.name" - :value="fields.parentId.value" + :value="selectedGroup.id" /> <gl-form-group - :label="$options.i18n.inputs.name.label" + :label="inputLabels.name" :description="$options.i18n.inputs.name.description" :label-for="fields.name.id" :invalid-feedback="$options.i18n.inputs.name.invalidFeedback" @@ -220,46 +289,102 @@ export default { <gl-form-input :id="fields.name.id" v-model="name" - class="gl-field-error-ignore" + class="gl-field-error-ignore gl-h-auto!" required :name="fields.name.name" :placeholder="$options.i18n.inputs.name.placeholder" data-qa-selector="group_name_field" - :size="$options.nameInputSize" + :size="$options.inputSize" :state="nameFeedbackState" @invalid="handleInvalidName" /> </gl-form-group> - <gl-form-group - :label="$options.i18n.inputs.path.label" - :label-for="fields.path.id" - :description="pathDescription" - :state="pathFeedbackState" - :valid-feedback="$options.i18n.inputs.path.validFeedback" - :invalid-feedback="pathInvalidFeedback" - > - <gl-form-input-group> - <template #prepend> - <gl-input-group-text class="group-root-path">{{ basePath }}</gl-input-group-text> - </template> - <gl-form-input - :id="fields.path.id" - class="gl-field-error-ignore" - :name="fields.path.name" - :value="computedPath" - :placeholder="$options.i18n.inputs.path.placeholder" - :maxlength="fields.path.maxLength" - :pattern="fields.path.pattern" - :state="pathFeedbackState" - :size="$options.nameInputSize" - required - data-qa-selector="group_path_field" - :data-bind-in="mattermostEnabled ? $options.mattermostDataBindName : null" - @input="handlePathInput" - @invalid="handleInvalidPath" - /> - </gl-form-input-group> - </gl-form-group> + + <div :class="newSubgroup && 'row gl-mb-3'"> + <gl-form-group v-if="newSubgroup" class="col-sm-6 gl-pr-0" :label="inputLabels.subgroupPath"> + <div class="input-group gl-flex-nowrap"> + <gl-button-group class="gl-w-full"> + <gl-button class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" label> + {{ basePath }} + </gl-button> + + <gl-dropdown + class="js-group-namespace-dropdown gl-flex-grow-1" + toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20" + @shown="handleDropdownShown" + > + <template #button-text> + <gl-truncate + v-if="selectedGroup.fullPath" + :text="selectedGroup.fullPath" + position="start" + with-tooltip + /> + </template> + + <gl-search-box-by-type + ref="search" + v-model.trim="search" + :is-loading="$apollo.queries.currentUserGroups.loading" + /> + + <template v-if="!$apollo.queries.currentUserGroups.loading"> + <template v-if="currentUserGroups.length"> + <gl-dropdown-item + v-for="group of currentUserGroups" + :key="group.id" + data-testid="select_group_dropdown_item" + @click="handleDropdownItemClick(group)" + > + {{ group.fullPath }} + </gl-dropdown-item> + </template> + <gl-dropdown-text v-else>{{ __('No matches found') }}</gl-dropdown-text> + </template> + </gl-dropdown> + </gl-button-group> + + <div class="gl-align-self-center gl-pl-5"> + <span class="gl-display-none gl-md-display-inline">/</span> + </div> + </div> + </gl-form-group> + + <gl-form-group + :class="newSubgroup && 'col-sm-6'" + :label="inputLabels.path" + :label-for="fields.path.id" + :description="pathDescription" + :state="pathFeedbackState" + :valid-feedback="$options.i18n.inputs.path.validFeedback" + :invalid-feedback="pathInvalidFeedback" + > + <gl-form-input-group> + <template v-if="!newSubgroup" #prepend> + <gl-input-group-text class="group-root-path"> + {{ basePath.concat(fields.parentFullPath.value) }} + </gl-input-group-text> + </template> + <gl-form-input + :id="fields.path.id" + class="gl-field-error-ignore gl-h-auto!" + :name="fields.path.name" + :value="computedPath" + :placeholder="$options.i18n.inputs.path.placeholder" + :maxlength="fields.path.maxLength" + :pattern="fields.path.pattern" + :state="pathFeedbackState" + :size="pathInputSize" + required + data-qa-selector="group_path_field" + :data-bind-in="mattermostEnabled ? $options.mattermostDataBindName : null" + @input="handlePathInput" + @invalid="handleInvalidPath" + /> + </gl-form-input-group> + </gl-form-group> + </div> + <template v-if="isEditingGroup"> <gl-alert class="gl-mb-5" :dismissible="false" variant="warning"> {{ $options.i18n.changingUrlWarningMessage }} @@ -267,7 +392,7 @@ export default { >{{ $options.i18n.learnMore }} </gl-link> </gl-alert> - <gl-form-group :label="$options.i18n.inputs.groupId.label" :label-for="fields.groupId.id"> + <gl-form-group :label="inputLabels.groupId" :label-for="fields.groupId.id"> <gl-form-input :id="fields.groupId.id" :value="fields.groupId.value" diff --git a/app/assets/javascripts/groups/create_edit_form.js b/app/assets/javascripts/groups/create_edit_form.js index 8ca0e6077e9..330d343b776 100644 --- a/app/assets/javascripts/groups/create_edit_form.js +++ b/app/assets/javascripts/groups/create_edit_form.js @@ -1,8 +1,12 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import { parseRailsFormFields } from '~/lib/utils/forms'; import { parseBoolean } from '~/lib/utils/common_utils'; import GroupNameAndPath from './components/group_name_and_path.vue'; +Vue.use(VueApollo); + export const initGroupNameAndPath = () => { const elements = document.querySelectorAll('.js-group-name-and-path'); @@ -12,13 +16,17 @@ export const initGroupNameAndPath = () => { elements.forEach((element) => { const fields = parseRailsFormFields(element); - const { basePath, mattermostEnabled } = element.dataset; + const { basePath, newSubgroup, mattermostEnabled } = element.dataset; return new Vue({ el: element, + apolloProvider: new VueApollo({ + defaultClient: createDefaultClient(), + }), provide: { fields, basePath, + newSubgroup: parseBoolean(newSubgroup), mattermostEnabled: parseBoolean(mattermostEnabled), }, render(h) { diff --git a/app/assets/javascripts/groups/queries/search_groups_where_user_can_create_subgroups.query.graphql b/app/assets/javascripts/groups/queries/search_groups_where_user_can_create_subgroups.query.graphql new file mode 100644 index 00000000000..c45a31ef387 --- /dev/null +++ b/app/assets/javascripts/groups/queries/search_groups_where_user_can_create_subgroups.query.graphql @@ -0,0 +1,11 @@ +query searchGroupsWhereUserCanCreateSubgroups($search: String) { + currentUser { + id + groups(permissionScope: TRANSFER_PROJECTS, search: $search) { + nodes { + id + fullPath + } + } + } +} diff --git a/app/assets/javascripts/pages/groups/new/components/app.vue b/app/assets/javascripts/pages/groups/new/components/app.vue index 713287f65b4..f01e5e595a3 100644 --- a/app/assets/javascripts/pages/groups/new/components/app.vue +++ b/app/assets/javascripts/pages/groups/new/components/app.vue @@ -2,51 +2,74 @@ import importGroupIllustration from '@gitlab/svgs/dist/illustrations/group-import.svg'; import newGroupIllustration from '@gitlab/svgs/dist/illustrations/group-new.svg'; -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue'; import createGroupDescriptionDetails from './create_group_description_details.vue'; -const PANELS = [ - { - name: 'create-group-pane', - selector: '#create-group-pane', - title: s__('GroupsNew|Create group'), - description: s__( - 'GroupsNew|Assemble related projects together and grant members access to several projects at once.', - ), - illustration: newGroupIllustration, - details: createGroupDescriptionDetails, - }, - { - name: 'import-group-pane', - selector: '#import-group-pane', - title: s__('GroupsNew|Import group'), - description: s__('GroupsNew|Import a group and related data from another GitLab instance.'), - illustration: importGroupIllustration, - details: 'Migrate your existing groups from another instance of GitLab.', - }, -]; - export default { components: { NewNamespacePage, }, props: { + parentGroupName: { + type: String, + required: false, + default: '', + }, + importExistingGroupPath: { + type: String, + required: false, + default: '', + }, hasErrors: { type: Boolean, required: false, default: false, }, }, - PANELS, + computed: { + initialBreadcrumb() { + return this.parentGroupName || __('New group'); + }, + panels() { + return [ + { + name: 'create-group-pane', + selector: '#create-group-pane', + title: this.parentGroupName + ? s__('GroupsNew|Create subgroup') + : s__('GroupsNew|Create group'), + description: s__( + 'GroupsNew|Assemble related projects together and grant members access to several projects at once.', + ), + illustration: newGroupIllustration, + details: createGroupDescriptionDetails, + detailProps: { + parentGroupName: this.parentGroupName, + importExistingGroupPath: this.importExistingGroupPath, + }, + }, + { + name: 'import-group-pane', + selector: '#import-group-pane', + title: s__('GroupsNew|Import group'), + description: s__( + 'GroupsNew|Import a group and related data from another GitLab instance.', + ), + illustration: importGroupIllustration, + details: 'Migrate your existing groups from another instance of GitLab.', + }, + ]; + }, + }, }; </script> <template> <new-namespace-page :jump-to-last-persisted-panel="hasErrors" - :initial-breadcrumb="__('New group')" - :panels="$options.PANELS" + :initial-breadcrumb="initialBreadcrumb" + :panels="panels" :title="s__('GroupsNew|Create new group')" persistence-key="new_group_last_active_tab" /> diff --git a/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue b/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue index 35193171fb8..be8542628c4 100644 --- a/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue +++ b/app/assets/javascripts/pages/groups/new/components/create_group_description_details.vue @@ -1,6 +1,22 @@ <script> import { GlSprintf, GlLink } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; + +const DESCRIPTION_DETAILS = { + group: [ + s__( + 'GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.', + ), + s__('GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}.'), + ], + subgroup: [ + s__( + 'GroupsNew|%{groupsLinkStart}Groups%{groupsLinkEnd} and %{subgroupsLinkStart}subgroups%{subgroupsLinkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.', + ), + s__('GroupsNew|You can also %{linkStart}import an existing group%{linkEnd}.'), + ], +}; export default { components: { @@ -11,30 +27,46 @@ export default { groupsHelpPath: helpPagePath('user/group/index'), subgroupsHelpPath: helpPagePath('user/group/subgroups/index'), }, + props: { + parentGroupName: { + type: String, + required: false, + default: '', + }, + importExistingGroupPath: { + type: String, + required: false, + default: '', + }, + }, + descriptionDetails: DESCRIPTION_DETAILS, }; </script> <template> <div> <p> - <gl-sprintf - :message=" - s__( - 'GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects.', - ) - " - > + <gl-sprintf v-if="parentGroupName" :message="$options.descriptionDetails.subgroup[0]"> + <template #groupsLink="{ content }"> + <gl-link :href="$options.paths.groupsHelpPath" target="_blank">{{ content }}</gl-link> + </template> + <template #subgroupsLink="{ content }"> + <gl-link :href="$options.paths.subgroupsHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + <gl-sprintf v-else :message="$options.descriptionDetails.group[0]"> <template #link="{ content }"> <gl-link :href="$options.paths.groupsHelpPath" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </p> <p> - <gl-sprintf - :message=" - s__('GroupsNew|Groups can also be nested by creating %{linkStart}subgroups%{linkEnd}.') - " - > + <gl-sprintf v-if="parentGroupName" :message="$options.descriptionDetails.subgroup[1]"> + <template #link="{ content }"> + <gl-link :href="importExistingGroupPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + <gl-sprintf v-else :message="$options.descriptionDetails.group[1]"> <template #link="{ content }"> <gl-link :href="$options.paths.subgroupsHelpPath" target="_blank">{{ content }}</gl-link> </template> diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js index 7c409010510..7dab5258b24 100644 --- a/app/assets/javascripts/pages/groups/new/index.js +++ b/app/assets/javascripts/pages/groups/new/index.js @@ -16,9 +16,18 @@ BindInOut.initAll(); initFilePickers(); function initNewGroupCreation(el) { - const { hasErrors, verificationRequired, verificationFormUrl, subscriptionsUrl } = el.dataset; + const { + hasErrors, + parentGroupName, + importExistingGroupPath, + verificationRequired, + verificationFormUrl, + subscriptionsUrl, + } = el.dataset; const props = { + parentGroupName, + importExistingGroupPath, hasErrors: parseBoolean(hasErrors), }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js index b551cd2fd60..bc84459e298 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js @@ -34,6 +34,36 @@ const nonStandardEvents = { }, counter: {}, }, + metrics: { + uniqueUser: { + expand: ['i_testing_metrics_report_widget_total'], + }, + counter: {}, + }, + browserPerformance: { + uniqueUser: { + expand: ['i_testing_web_performance_widget_total'], + }, + counter: {}, + }, + licenseCompliance: { + uniqueUser: { + expand: ['i_testing_license_compliance_widget_total'], + }, + counter: {}, + }, + loadPerformance: { + uniqueUser: { + expand: ['i_testing_load_performance_widget_total'], + }, + counter: {}, + }, + statusChecks: { + uniqueUser: { + expand: ['i_testing_status_checks_widget'], + }, + counter: {}, + }, }; function combineDeepArray(path, ...objects) { diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js index 22e907f7e48..c3fe70cb5df 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js @@ -6,7 +6,6 @@ import { EXTENSION_ICONS } from '../../constants'; export default { name: 'WidgetAccessibility', enablePolling: true, - telemetry: false, i18n: { loading: s__('Reports|Accessibility scanning results are being parsed'), error: s__('Reports|Accessibility scanning failed loading results'), diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index a0d8ca117a4..2b9804796ae 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -14,6 +14,7 @@ import { glEmojiTag } from '~/emoji'; import createFlash from '~/flash'; import { followUser, unfollowUser } from '~/rest_api'; import { isUserBusy } from '~/set_status_modal/utils'; +import Tracking from '~/tracking'; import { USER_POPOVER_DELAY } from './constants'; const MAX_SKELETON_LINES = 4; @@ -37,6 +38,7 @@ export default { directives: { SafeHtml: GlSafeHtmlDirective, }, + mixins: [Tracking.mixin()], props: { target: { type: HTMLElement, @@ -117,6 +119,11 @@ export default { }, async follow() { this.toggleFollowLoading = true; + + this.track('click_button', { + label: 'follow_from_user_popover', + }); + try { await followUser(this.user.id); this.$emit('follow'); @@ -132,6 +139,11 @@ export default { }, async unfollow() { this.toggleFollowLoading = true; + + this.track('click_button', { + label: 'unfollow_from_user_popover', + }); + try { await unfollowUser(this.user.id); this.$emit('unfollow'); diff --git a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue index 8e9b8ef3e6f..232749a2d01 100644 --- a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue +++ b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue @@ -125,7 +125,7 @@ export default { <h4>{{ activePanel.title }}</h4> <p v-if="hasTextDetails">{{ details }}</p> - <component :is="details" v-else /> + <component :is="details" v-else v-bind="activePanel.detailProps || {}" /> <slot name="extra-description"></slot> </div> diff --git a/app/components/pajamas/button_component.html.haml b/app/components/pajamas/button_component.html.haml index 8ce7d9e0315..5cf57deb7f1 100644 --- a/app/components/pajamas/button_component.html.haml +++ b/app/components/pajamas/button_component.html.haml @@ -1,4 +1,4 @@ -= content_tag tag, {**@button_options, **base_attributes, class: button_class, href: @href, target: @target } do += content_for :pajamas_button_content, flush: true do - if @loading = gl_loading_icon(inline: true, css_class: 'gl-button-icon gl-button-loading-indicator') - if @icon && (!@loading || content) @@ -6,3 +6,10 @@ - if content %span.gl-button-text{ class: @button_text_classes } = content + +- if link? + = link_to @href, { **@button_options, **base_attributes, class: button_class, target: @target, method: @method } do + = content_for :pajamas_button_content +- else + = content_tag 'button', { **@button_options, **base_attributes, class: button_class } do + = content_for :pajamas_button_content diff --git a/app/components/pajamas/button_component.rb b/app/components/pajamas/button_component.rb index c6193d1ae05..4233e446d5b 100644 --- a/app/components/pajamas/button_component.rb +++ b/app/components/pajamas/button_component.rb @@ -13,6 +13,7 @@ module Pajamas # @param [String] icon # @param [String] href # @param [String] target + # @param [Symbol] method # @param [Hash] button_options # @param [String] button_text_classes # @param [String] icon_classes @@ -28,6 +29,7 @@ module Pajamas icon: nil, href: nil, target: nil, + method: nil, button_options: {}, button_text_classes: nil, icon_classes: nil @@ -43,6 +45,7 @@ module Pajamas @icon = icon @href = href @target = filter_attribute(target, TARGET_OPTIONS) + @method = filter_attribute(method, METHOD_OPTIONS) @button_options = button_options @button_text_classes = button_text_classes @icon_classes = icon_classes @@ -75,6 +78,7 @@ module Pajamas SIZE_OPTIONS = [:small, :medium].freeze TYPE_OPTIONS = [:button, :reset, :submit].freeze TARGET_OPTIONS = %w[_self _blank _parent _top].freeze + METHOD_OPTIONS = [:get, :post, :put, :delete, :patch].freeze CATEGORY_CLASSES = { primary: '', @@ -101,8 +105,8 @@ module Pajamas delegate :sprite_icon, to: :helpers delegate :gl_loading_icon, to: :helpers - def tag - @href ? 'a' : 'button' + def link? + @href.present? end def base_attributes diff --git a/app/graphql/mutations/ci/pipeline/cancel.rb b/app/graphql/mutations/ci/pipeline/cancel.rb index 3ec6eee9f54..c52e3b4f4b8 100644 --- a/app/graphql/mutations/ci/pipeline/cancel.rb +++ b/app/graphql/mutations/ci/pipeline/cancel.rb @@ -13,7 +13,6 @@ module Mutations if pipeline.cancelable? pipeline.cancel_running - pipeline.cancel { success: true, errors: [] } else diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 4751e64ba0e..bb92792de2d 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -134,6 +134,13 @@ module GroupsHelper @group_projects_sort || @sort || params[:sort] || sort_value_recently_created end + def subgroup_creation_data(group) + { + parent_group_name: group.parent&.name, + import_existing_group_path: new_group_path(parent_id: group.parent_id, anchor: 'import-group-pane') + } + end + def verification_for_group_creation_data # overridden in EE {} @@ -144,11 +151,9 @@ module GroupsHelper false end - def group_name_and_path_app_data(group) - parent = group.parent - + def group_name_and_path_app_data { - base_path: URI.join(root_url, parent&.full_path || "").to_s, + base_path: root_url, mattermost_enabled: Gitlab.config.mattermost.enabled.to_s } end @@ -156,7 +161,7 @@ module GroupsHelper def subgroups_and_projects_list_app_data(group) { show_schema_markup: 'true', - new_subgroup_path: new_group_path(parent_id: group.id), + new_subgroup_path: new_group_path(parent_id: group.id, anchor: 'create-group-pane'), new_project_path: new_project_path(namespace_id: group.id), new_subgroup_illustration: image_path('illustrations/subgroup-create-new-sm.svg'), new_project_illustration: image_path('illustrations/project-create-new-sm.svg'), diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index fb8fafe59f3..dc7d8049556 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -42,7 +42,7 @@ module Nav ::Gitlab::Nav::TopNavMenuItem.build( id: 'new_subgroup', title: _('New subgroup'), - href: new_group_path(parent_id: group.id), + href: new_group_path(parent_id: group.id, anchor: 'create-group-pane'), data: { track_action: 'click_link_new_subgroup', track_label: 'plus_menu_dropdown' } ) ) diff --git a/app/mailers/emails/admin_notification.rb b/app/mailers/emails/admin_notification.rb index 9d02d4132a1..3766b4447d1 100644 --- a/app/mailers/emails/admin_notification.rb +++ b/app/mailers/emails/admin_notification.rb @@ -15,23 +15,7 @@ module Emails email = user.notification_email_or_default mail to: email, subject: "Unsubscribed from GitLab administrator notifications" end - - def user_auto_banned_email(admin_id, user_id, max_project_downloads:, within_seconds:, group: nil) - admin = User.find(admin_id) - @user = User.find(user_id) - @max_project_downloads = max_project_downloads - @within_minutes = within_seconds / 60 - @ban_scope = if group.present? - _('your group (%{group_name})' % { group_name: group.name }) - else - _('your GitLab instance') - end - - Gitlab::I18n.with_locale(admin.preferred_language) do - email_with_layout( - to: admin.notification_email_or_default, - subject: subject(_("We've detected unusual activity"))) - end - end end end + +Emails::AdminNotification.prepend_mod diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 7bcc97914e5..a254690de72 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -9,7 +9,7 @@ = _('Update your group name, description, avatar, and visibility.') = link_to _('Learn more about groups.'), help_page_path('user/group/index') .col-lg-8 - = render 'shared/group_form', f: f + = render 'shared/groups/group_name_and_path_fields', f: f = render 'shared/group_form_description', f: f .form-group.gl-form-group{ role: 'group' } = f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label' diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index bd893ca3162..2911e9991f2 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -33,7 +33,10 @@ .js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), group_id: @group.id, container_class: 'gl-mx-2 gl-mt-3 gl-vertical-align-top', no_flip: 'true' } } - if can_create_subgroups .gl-px-2.gl-sm-w-auto.gl-w-full - = link_to _("New subgroup"), new_group_path(parent_id: @group.id), class: "btn btn-default gl-button gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_subgroup_button' } + = link_to _("New subgroup"), + new_group_path(parent_id: @group.id, anchor: 'create-group-pane'), + class: "btn btn-default gl-button gl-mt-3 gl-sm-w-auto gl-w-full", + data: { qa_selector: 'new_subgroup_button' } - if can_create_projects .gl-px-2.gl-sm-w-auto.gl-w-full = link_to _("New project"), new_project_path(namespace_id: @group.id), class: "btn btn-confirm gl-button gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_project_button' } diff --git a/app/views/groups/_new_group_fields.html.haml b/app/views/groups/_new_group_fields.html.haml index 0527d38159b..632884051f0 100644 --- a/app/views/groups/_new_group_fields.html.haml +++ b/app/views/groups/_new_group_fields.html.haml @@ -1,31 +1,34 @@ +- parent = @group.parent +- submit_label = parent ? s_('GroupsNew|Create subgroup') : s_('GroupsNew|Create group') = form_errors(@group, pajamas_alert: true) -= render 'shared/group_form', f: f, autofocus: true += render 'shared/groups/group_name_and_path_fields', f: f, autofocus: true, new_subgroup: !!parent -.row - .form-group.gl-form-group.col-sm-12 - %label.label-bold - = _('Visibility level') - %p - = _('Who will be able to see this group?') - = link_to _('View the documentation'), help_page_path("user/public_access"), target: '_blank', rel: 'noopener noreferrer' - = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false - -- if Gitlab.config.mattermost.enabled +- unless parent .row - = render 'create_chat_team', f: f + .form-group.gl-form-group.col-sm-12 + %label.label-bold + = _('Visibility level') + %p + = _('Who will be able to see this group?') + = link_to _('View the documentation'), help_page_path("user/public_access"), target: '_blank', rel: 'noopener noreferrer' + = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false -- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? - = render 'personalize', f: f + - if Gitlab.config.mattermost.enabled + .row + = render 'create_chat_team', f: f -.row.js-invite-members-section - .col-sm-4 - = render_if_exists 'shared/groups/invite_members' + - unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? + = render 'personalize', f: f -- if captcha_required? - .row.recaptcha + .row.js-invite-members-section .col-sm-4 - = recaptcha_tags nonce: content_security_policy_nonce + = render_if_exists 'shared/groups/invite_members' + + - if captcha_required? + .row.recaptcha + .col-sm-4 + = recaptcha_tags nonce: content_security_policy_nonce .row .col-sm-12 - = f.submit _('Create group'), class: "btn gl-button btn-confirm" + = f.submit submit_label, class: "btn gl-button btn-confirm", data: { qa_selector: 'create_group_button' } = link_to _('Cancel'), dashboard_groups_path, class: 'btn gl-button btn-default btn-cancel' diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 3fb2b88dadd..8384c906eeb 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -6,7 +6,8 @@ .group-edit-container.gl-mt-5 - .js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s }.merge(verification_for_group_creation_data) } + .js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s }.merge(subgroup_creation_data(@group), + verification_for_group_creation_data) } .row{ 'v-cloak': true } #create-group-pane.tab-pane diff --git a/app/views/layouts/_google_tag_manager_head.html.haml b/app/views/layouts/_google_tag_manager_head.html.haml index 25af51ca9cb..f5c823465be 100644 --- a/app/views/layouts/_google_tag_manager_head.html.haml +++ b/app/views/layouts/_google_tag_manager_head.html.haml @@ -1,4 +1,23 @@ - return unless google_tag_manager_enabled? +- if Feature.enabled?(:gitlab_gtm_datalayer, type: :ops) + = javascript_tag do + :plain + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + + gtag('consent', 'default', { + 'analytics_storage': 'denied', + 'ad_storage': 'denied', + 'functionality_storage': 'denied', + 'region': ['EU', 'UK', 'PE', 'RU'], + 'wait_for_update': 500 + }); + gtag('consent', 'default', { + 'analytics_storage': 'granted', + 'ad_storage': 'granted', + 'functionality_storage': 'granted', + 'wait_for_update': 500 + }); - if Feature.enabled?(:gtm_nonce, type: :ops) = javascript_tag nonce: content_security_policy_nonce do diff --git a/app/views/notify/user_auto_banned_email.html.haml b/app/views/notify/user_auto_banned_email.html.haml deleted file mode 100644 index 8c33cd7299d..00000000000 --- a/app/views/notify/user_auto_banned_email.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe -- link_end = '</a>'.html_safe -= email_default_heading(_("We've detected some unusual activity")) -%p - = _('We want to let you know %{username} has been banned from %{scope} due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes, scope: @ban_scope } -%p - = _('If this is a mistake, you can %{link_start}unban them%{link_end}.').html_safe % { link_start: link_start % { url: admin_users_url(filter: 'banned') }, link_end: link_end } -%p - = _('You can adjust rules on auto-banning %{link_start}here%{link_end}.').html_safe % { link_start: link_start % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') }, link_end: link_end } diff --git a/app/views/notify/user_auto_banned_email.text.erb b/app/views/notify/user_auto_banned_email.text.erb deleted file mode 100644 index 336973c2e42..00000000000 --- a/app/views/notify/user_auto_banned_email.text.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%= _("We've detected some unusual activity") %> - -<%= _('We want to let you know %{username} has been banned from %{scope} due to them downloading more than %{max_project_downloads} project repositories within %{within_minutes} minutes.') % { username: sanitize_name(@user.name), max_project_downloads: @max_project_downloads, within_minutes: @within_minutes, scope: @ban_scope } %> - -<%= _('If this is a mistake, you can unban them: %{url}.') % { url: admin_users_url(filter: 'banned') } %> - -<%= _('You can adjust rules on auto-banning here: %{url}.') % { url: network_admin_application_settings_url(anchor: 'js-ip-limits-settings') } %> diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml deleted file mode 100644 index db5e055a1c4..00000000000 --- a/app/views/shared/_group_form.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- parent = @group.parent -- group_path = root_url -- group_path << parent.full_path + '/' if parent - - -= render 'shared/groups/group_name_and_path_fields', f: f diff --git a/app/views/shared/groups/_group_name_and_path_fields.html.haml b/app/views/shared/groups/_group_name_and_path_fields.html.haml index 634b8448535..08192cc0cc5 100644 --- a/app/views/shared/groups/_group_name_and_path_fields.html.haml +++ b/app/views/shared/groups/_group_name_and_path_fields.html.haml @@ -1,5 +1,6 @@ -.js-group-name-and-path{ data: group_name_and_path_app_data(@group) } +.js-group-name-and-path{ data: group_name_and_path_app_data.merge(new_subgroup: local_assigns[:new_subgroup].to_s) } = f.hidden_field :name, data: { js_name: 'name' } = f.hidden_field :path, maxlength: ::Namespace::URL_MAX_LENGTH, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, data: { js_name: 'path' } = f.hidden_field :parent_id, value: @group.parent&.id, data: { js_name: 'parentId' } + = f.hidden_field :parent_full_path, value: @group.parent&.full_path, data: { js_name: 'parentFullPath' } = f.hidden_field :id, data: { js_name: 'groupId' } diff --git a/app/views/shared/milestones/_header.html.haml b/app/views/shared/milestones/_header.html.haml index 18db556e024..334785685d5 100644 --- a/app/views/shared/milestones/_header.html.haml +++ b/app/views/shared/milestones/_header.html.haml @@ -20,10 +20,10 @@ #promote-milestone-modal - if milestone.active? - = render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :close }), button_options: { class: 'btn-grouped btn-close', data: { method: 'put' }, rel: 'nofollow' }) do + = render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :close }), method: :put, button_options: { class: 'btn-grouped btn-close' }) do = _('Close milestone') - else - = render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :activate }), button_options: { class: 'btn-grouped', data: { method: 'put' }, rel: 'nofollow' }) do + = render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :activate }), method: :put, button_options: { class: 'btn-grouped' }) do = _('Reopen milestone') = render 'shared/milestones/delete_button' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 5c82b22fc0e..0eb243137b6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -42,11 +42,13 @@ = sprite_icon('user') - if current_user && current_user.id != @user.id - if current_user.following?(@user) - = link_to user_unfollow_path(@user, :json) , class: link_classes + 'btn gl-button btn-default', method: :post do - = _('Unfollow') + = form_tag user_unfollow_path(@user, :json), class: link_classes + 'gl-display-inline-block' do + = render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-w-full', data: { track_action: 'click_button', track_label: 'unfollow_from_profile' } }) do + = _('Unfollow') - else - = link_to user_follow_path(@user, :json) , class: link_classes + 'btn gl-button btn-confirm', method: :post, data: { qa_selector: 'follow_user_link' } do - = _('Follow') + = form_tag user_follow_path(@user, :json), class: link_classes + 'gl-display-inline-block' do + = render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'gl-w-full', data: { qa_selector: 'follow_user_link', track_action: 'click_button', track_label: 'follow_from_profile' } }) do + = _('Follow') .profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] } .avatar-holder diff --git a/app/workers/ci/runners/reconcile_existing_runner_versions_cron_worker.rb b/app/workers/ci/runners/reconcile_existing_runner_versions_cron_worker.rb index 035b2563e56..f87f7794792 100644 --- a/app/workers/ci/runners/reconcile_existing_runner_versions_cron_worker.rb +++ b/app/workers/ci/runners/reconcile_existing_runner_versions_cron_worker.rb @@ -12,9 +12,23 @@ module Ci feature_category :runner_fleet urgency :low + deduplicate :until_executed idempotent! - def perform + def perform(cronjob_scheduled = true) + if cronjob_scheduled + # Introduce some randomness across the day so that instances don't all hit the GitLab Releases API + # around the same time of day + period = rand(0..12.hours.in_seconds) + self.class.perform_in(period, false) + + Sidekiq.logger.info( + class: self.class.name, + message: "rescheduled job for #{period.seconds.from_now}") + + return + end + result = ::Ci::Runners::ReconcileExistingRunnerVersionsService.new.execute result.each { |key, value| log_extra_metadata_on_done(key, value) } end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 78ddc0afa3a..6861864999f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -542,7 +542,7 @@ production: &base # Periodically update ci_runner_versions table with up-to-date versions and status. ci_runner_versions_reconciliation_worker: - cron: "20 * * * *" + cron: "@daily" # GitLab EE only jobs. These jobs are automatically enabled for an EE # installation, and ignored for a CE installation. diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2de4efbe8ef..2306fccd7f6 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -631,7 +631,7 @@ Settings.cron_jobs['loose_foreign_keys_cleanup_worker'] ||= Settingslogic.new({} Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['cron'] ||= '*/1 * * * *' Settings.cron_jobs['loose_foreign_keys_cleanup_worker']['job_class'] = 'LooseForeignKeys::CleanupWorker' Settings.cron_jobs['ci_runner_versions_reconciliation_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['ci_runner_versions_reconciliation_worker']['cron'] ||= '20 * * * *' +Settings.cron_jobs['ci_runner_versions_reconciliation_worker']['cron'] ||= '@daily' Settings.cron_jobs['ci_runner_versions_reconciliation_worker']['job_class'] = 'Ci::Runners::ReconcileExistingRunnerVersionsCronWorker' Gitlab.ee do diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md index ffcd496e2c3..e8373b0c0b7 100644 --- a/doc/user/application_security/dast/browser_based.md +++ b/doc/user/application_security/dast/browser_based.md @@ -5,34 +5,42 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: reference, howto --- -# DAST browser-based crawler **(ULTIMATE)** +# DAST browser-based analyzer **(ULTIMATE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323423) in GitLab 13.12. WARNING: This product is in an early-access stage and is considered a [beta](../../../policy/alpha-beta-support.md#beta-features) feature. -GitLab DAST's new browser-based crawler is a crawl engine built by GitLab to test Single Page Applications (SPAs) and traditional web applications. -Due to the reliance of modern web applications on JavaScript, handling SPAs or applications that are dependent on JavaScript is paramount to ensuring proper coverage of an application for Dynamic Application Security Testing (DAST). +GitLab DAST's browser-based analyzer was built by GitLab to test Single Page Applications (SPAs) and +traditional web applications. It both crawls the web application and analyzes the resulting output +for vulnerabilities. Analysis of modern applications, heavily reliant on JavaScript, is vital to +ensuring DAST coverage. -The browser-based crawler works by loading the target application into a specially-instrumented Chromium browser. A snapshot of the page is taken before a search to find any actions that a user might perform, -such as clicking on a link or filling in a form. For each action found, the crawler executes it, takes a new snapshot, and determines what in the page changed from the previous snapshot. -Crawling continues by taking more snapshots and finding subsequent actions. +The browser-based scanner works by loading the target application into a specially-instrumented +Chromium browser. A snapshot of the page is taken before a search to find any actions that a user +might perform, such as clicking on a link or filling in a form. For each action found, the +browser-based scanner executes it, takes a new snapshot, and determines what in the page changed +from the previous snapshot. Crawling continues by taking more snapshots and finding subsequent +actions. The benefit of scanning by following user actions in a browser is that the crawler can +interact with the target application much like a real user would, identifying complex flows that +traditional web crawlers don't understand. This results in better coverage of the website. -The benefit of crawling by following user actions in a browser is that the crawler can interact with the target application much like a real user would, identifying complex flows that traditional web crawlers don't understand. This results in better coverage of the website. +The browser-based scanner should provide greater coverage for most web applications, compared +with the current DAST AJAX crawler. While both crawlers are +used together with the current DAST scanner, the combination of the browser-based crawler with the +current DAST scanner is much more effective at finding and testing every page in an application. -Using the browser-based crawler should provide greater coverage for most web applications, compared with the current DAST AJAX crawler. The new crawler replaces the AJAX crawler and is specifically designed to maximize crawl coverage in modern web applications. While both crawlers are currently used in conjunction with the existing DAST scanner, the combination of the browser-based crawler with the current DAST scanner is much more effective at finding and testing every page in an application. +## Enable browser-based analyzer -## Enable browser-based crawler - -The browser-based crawler is an extension to the GitLab DAST product. DAST should be included in the CI/CD configuration and the browser-based crawler enabled using CI/CD variables: +To enable the browser-based analyzer: 1. Ensure the DAST [prerequisites](index.md#prerequisites) are met. -1. Include the [DAST CI template](index.md#include-the-dast-template). -1. Set the target website using the `DAST_WEBSITE` CI/CD variable. +1. Include the [DAST CI/CD template](index.md#include-the-dast-template). +1. Set the target website using the [`DAST_WEBSITE` CI/CD variable](index.md#available-cicd-variables). 1. Set the CI/CD variable `DAST_BROWSER_SCAN` to `true`. -An example configuration might look like the following: +Example extract of `.gitlab-ci.yml` file: ```yaml include: @@ -77,13 +85,20 @@ The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX ## Vulnerability detection -While the browser-based crawler crawls modern web applications efficiently, vulnerability detection is still managed by the standard DAST/Zed Attack Proxy (ZAP) solution. +Vulnerability detection is gradually being migrated from the default Zed Attack Proxy (ZAP) solution +to the browser-based analyzer. For details of the vulnerability detection already migrated, see +[browser-based vulnerability checks](checks/index.md). -The crawler runs the target website in a browser with DAST/ZAP configured as the proxy server. This ensures that all requests and responses made by the browser are passively scanned by DAST/ZAP. -When running a full scan, active vulnerability checks executed by DAST/ZAP do not use a browser. This difference in how vulnerabilities are checked can cause issues that require certain features of the target website to be disabled to ensure the scan works as intended. +The crawler runs the target website in a browser with DAST/ZAP configured as the proxy server. This +ensures that all requests and responses made by the browser are passively scanned by DAST/ZAP. When +running a full scan, active vulnerability checks executed by DAST/ZAP do not use a browser. This +difference in how vulnerabilities are checked can cause issues that require certain features of the +target website to be disabled to ensure the scan works as intended. -For example, for a target website that contains forms with Anti-CSRF tokens, a passive scan works as intended because the browser displays pages and forms as if a user is viewing the page. -However, active vulnerability checks that run in a full scan cannot submit forms containing Anti-CSRF tokens. In such cases, we recommend you disable Anti-CSRF tokens when running a full scan. +For example, for a target website that contains forms with Anti-CSRF tokens, a passive scan works as +intended because the browser displays pages and forms as if a user is viewing the page. However, +active vulnerability checks that run in a full scan cannot submit forms containing Anti-CSRF tokens. +In such cases, we recommend you disable Anti-CSRF tokens when running a full scan. ## Managing scan time diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md index 0609b843e86..72af822d7b8 100644 --- a/doc/user/project/insights/index.md +++ b/doc/user/project/insights/index.md @@ -68,12 +68,14 @@ bugsCharts: description: "Open bugs created per month" type: bar query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - group_by: month - period_limit: 24 + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + group_by: month + period_limit: 24 ``` Each chart definition is made up of a hash composed of key-value pairs. @@ -85,12 +87,14 @@ For example, here's single chart definition: description: "Open bugs created per month" type: bar query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - group_by: month - period_limit: 24 + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + group_by: month + period_limit: 24 ``` ## Configuration parameters @@ -104,7 +108,7 @@ The following table lists available parameters for charts: | [`title`](#title) | The title of the chart. This displays on the Insights page. | | [`description`](#description) | A description for the individual chart. This displays above the relevant chart. | | [`type`](#type) | The type of chart: `bar`, `line` or `stacked-bar`. | -| [`query`](#query) | A hash that defines the conditions for issues / merge requests to be part of the chart. | +| [`query`](#query) | A hash that defines the data source and filtering conditions for the chart. | ## Parameter details @@ -155,8 +159,7 @@ Supported values are: ### `query` -`query` allows to define the conditions for issues / merge requests to be part -of the chart. +`query` allows to define the data source and various filtering conditions for the chart. Example: @@ -166,6 +169,29 @@ monthlyBugsCreated: description: "Open bugs created per month" type: bar query: + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + collection_labels: + - S1 + - S2 + - S3 + - S4 + group_by: week + period_limit: 104 +``` + +The legacy format without the `data_source` parameter is still supported: + +```yaml +monthlyBugsCreated: + title: "Monthly bugs created" + description: "Open bugs created per month" + type: bar + query: issuable_type: issue issuable_state: opened filter_labels: @@ -179,7 +205,15 @@ monthlyBugsCreated: period_limit: 104 ``` -#### `query.issuable_type` +#### `query.data_source` + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in GitLab 15.3. + +The `data_source` parameter was introduced to allow visualizing data from different data sources. Currently `issuable` is the only supported value. + +#### `Issuable` query parameters + +##### `query.params.issuable_type` Defines the type of "issuable" you want to create a chart for. @@ -188,7 +222,7 @@ Supported values are: - `issue`: The chart displays issues' data. - `merge_request`: The chart displays merge requests' data. -#### `query.issuable_state` +##### `query.params.issuable_state` Filter by the current state of the queried "issuable". @@ -202,7 +236,7 @@ Supported values are: - `merged`: Merged merge requests. - `all`: Issues / merge requests in all states -#### `query.filter_labels` +##### `query.params.filter_labels` Filter by labels currently applied to the queried "issuable". @@ -216,14 +250,16 @@ monthlyBugsCreated: title: "Monthly regressions created" type: bar query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - - regression + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + - regression ``` -#### `query.collection_labels` +#### `query.params.collection_labels` Group "issuable" by the configured labels. @@ -237,15 +273,17 @@ weeklyBugsBySeverity: title: "Weekly bugs by severity" type: stacked-bar query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - collection_labels: - - S1 - - S2 - - S3 - - S4 + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + collection_labels: + - S1 + - S2 + - S3 + - S4 ``` #### `query.group_by` @@ -325,10 +363,12 @@ monthlyBugsCreated: description: "Open bugs created per month" type: bar query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug projects: only: - 3 # You can use the project ID @@ -355,41 +395,47 @@ bugsCharts: type: bar <<: *projectsOnly query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - group_by: month - period_limit: 24 + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + group_by: month + period_limit: 24 - title: "Weekly bugs by severity" type: stacked-bar <<: *projectsOnly query: - issuable_type: issue - issuable_state: opened - filter_labels: - - bug - collection_labels: - - S1 - - S2 - - S3 - - S4 - group_by: week - period_limit: 104 + data_source: issuables + params: + issuable_type: issue + issuable_state: opened + filter_labels: + - bug + collection_labels: + - S1 + - S2 + - S3 + - S4 + group_by: week + period_limit: 104 - title: "Monthly bugs by team" type: line <<: *projectsOnly query: - issuable_type: merge_request - issuable_state: opened - filter_labels: - - bug - collection_labels: - - Manage - - Plan - - Create - group_by: month - period_limit: 24 + data_source: issuables + params: + issuable_type: merge_request + issuable_state: opened + filter_labels: + - bug + collection_labels: + - Manage + - Plan + - Create + group_by: month + period_limit: 24 ``` diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2a80ac49694..5ed1d6aba94 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18802,6 +18802,9 @@ msgstr "" msgid "GroupsEmptyState|You do not have necessary permissions to create a subgroup or project in this group. Please contact an owner of this group to create a new subgroup or project." msgstr "" +msgid "GroupsNew|%{groupsLinkStart}Groups%{groupsLinkEnd} and %{subgroupsLinkStart}subgroups%{subgroupsLinkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects." +msgstr "" + msgid "GroupsNew|%{linkStart}Groups%{linkEnd} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects." msgstr "" @@ -18820,6 +18823,9 @@ msgstr "" msgid "GroupsNew|Create new group" msgstr "" +msgid "GroupsNew|Create subgroup" +msgstr "" + msgid "GroupsNew|Create this in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, use a short expiration date when creating the token." msgstr "" @@ -18865,6 +18871,9 @@ msgstr "" msgid "GroupsNew|Upload file" msgstr "" +msgid "GroupsNew|You can also %{linkStart}import an existing group%{linkEnd}." +msgstr "" + msgid "GroupsNew|e.g. h8d3f016698e..." msgstr "" @@ -18943,6 +18952,15 @@ msgstr "" msgid "Groups|Save changes" msgstr "" +msgid "Groups|Subgroup URL" +msgstr "" + +msgid "Groups|Subgroup name" +msgstr "" + +msgid "Groups|Subgroup slug" +msgstr "" + msgid "Guideline" msgstr "" @@ -23516,9 +23534,6 @@ msgstr "" msgid "LinkedResources|Linked resources" msgstr "" -msgid "LinkedResources|Read more about linked resources" -msgstr "" - msgid "LinkedResources|Remove" msgstr "" @@ -23534,6 +23549,9 @@ msgstr "" msgid "LinkedResources|Text (Optional)" msgstr "" +msgid "LinkedResources|Use this space to add links to the resources your team needs as they work to resolve the incident." +msgstr "" + msgid "Links" msgstr "" diff --git a/package.json b/package.json index 23ae610f8be..78060fcabe0 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "graphql": "^15.7.2", "graphql-tag": "^2.11.0", "highlight.js": "^11.5.1", - "immer": "^7.0.7", + "immer": "^9.0.15", "ipaddr.js": "^1.9.1", "jed": "^1.1.1", "jquery": "^3.6.0", @@ -239,7 +239,7 @@ "md5": "^2.2.1", "miragejs": "^0.1.40", "mock-apollo-client": "1.2.0", - "nodemon": "^2.0.4", + "nodemon": "^2.0.19", "prettier": "2.2.1", "prosemirror-schema-basic": "^1.1.2", "prosemirror-schema-list": "^1.1.6", diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index 09a9af7aaf7..1f17b470ada 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -12,7 +12,7 @@ module QA end view 'app/views/groups/_new_group_fields.html.haml' do - element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern + element :create_group_button end view 'app/views/groups/_import_group_from_another_instance_panel.html.haml' do @@ -34,6 +34,10 @@ module QA click_button 'Create group' end + def create_subgroup + click_button 'Create subgroup' + end + def set_gitlab_url(url) fill_element(:import_gitlab_url, url) end diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb index 0d5515aacdf..58b3badbb22 100644 --- a/qa/qa/page/project/settings/integrations.rb +++ b/qa/qa/page/project/settings/integrations.rb @@ -6,6 +6,7 @@ module QA module Settings class Integrations < QA::Page::Base view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do + element :jenkins_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern @@ -22,10 +23,12 @@ module QA def click_jira_link click_element :jira_link end + + def click_jenkins_ci_link + click_element :jenkins_link + end end end end end end - -QA::Page::Project::Settings::Integrations.prepend_mod_with('Page::Project::Settings::Integrations', namespace: QA) diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index c3da9d47de5..8a830303b56 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -38,10 +38,8 @@ module QA group_show.go_to_new_subgroup Page::Group::New.perform do |group_new| - group_new.click_create_group group_new.set_path(path) - group_new.set_visibility('Public') - group_new.create + group_new.create_subgroup end # Ensure that the group was actually created diff --git a/spec/components/pajamas/button_component_spec.rb b/spec/components/pajamas/button_component_spec.rb index a8c96042580..00423fd22a4 100644 --- a/spec/components/pajamas/button_component_spec.rb +++ b/spec/components/pajamas/button_component_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do let(:content) { "Button content" } let(:options) { {} } - describe 'basic usage' do + RSpec.shared_examples 'basic button behavior' do before do render_inline(subject) do |c| content @@ -59,7 +59,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do describe 'disabled' do context 'by default (false)' do it 'does not have disabled styling and behavior' do - expect(page).not_to have_css ".disabled[disabled='disabled'][aria-disabled='true']" + expect(page).not_to have_css ".disabled[disabled][aria-disabled]" end end @@ -67,7 +67,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do let(:options) { { disabled: true } } it 'has disabled styling and behavior' do - expect(page).to have_css ".disabled[disabled='disabled'][aria-disabled='true']" + expect(page).to have_css ".disabled[disabled][aria-disabled]" end end end @@ -75,7 +75,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do describe 'loading' do context 'by default (false)' do it 'is not disabled' do - expect(page).not_to have_css ".disabled[disabled='disabled']" + expect(page).not_to have_css ".disabled[disabled]" end it 'does not render a spinner' do @@ -87,7 +87,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do let(:options) { { loading: true } } it 'is disabled' do - expect(page).to have_css ".disabled[disabled='disabled']" + expect(page).to have_css ".disabled[disabled]" end it 'renders a spinner' do @@ -218,9 +218,13 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do end end end + end + + context 'button component renders a button' do + include_examples 'basic button behavior' describe 'type' do - context 'by default (without href)' do + context 'by default' do it 'has type "button"' do expect(page).to have_css "button[type='button']" end @@ -238,34 +242,42 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do end end - context 'when set to unkown type' do + context 'when set to unknown type' do let(:options) { { type: :madeup } } it 'has type "button"' do expect(page).to have_css "button[type='button']" end end + end + end - context 'for links (with href)' do - let(:options) { { href: 'https://example.com', type: :reset } } + context 'button component renders a link' do + let(:options) { { href: 'https://gitlab.com', target: '_blank' } } - it 'ignores type' do - expect(page).not_to have_css "[type]" - end - end + it "renders a link instead of the button" do + expect(page).not_to have_css "button[type='button']" + expect(page).to have_css "a[href='https://gitlab.com'][target='_blank']" end - describe 'link button' do - it 'renders a button tag with type="button" when "href" is not set' do - expect(page).to have_css "button[type='button']" + include_examples 'basic button behavior' + + describe 'type' do + let(:options) { { href: 'https://example.com', type: :reset } } + + it 'ignores type' do + expect(page).not_to have_css "[type]" end + end + + describe 'method' do + where(:method) { [:get, :post, :put, :delete, :patch] } - context 'when "href" is provided' do - let(:options) { { href: 'https://gitlab.com', target: '_blank' } } + let(:options) { { href: 'https://gitlab.com', method: method } } - it "renders a link instead of the button" do - expect(page).not_to have_css "button[type='button']" - expect(page).to have_css "a[href='https://gitlab.com'][target='_blank']" + with_them do + it 'has the correct data-method attribute' do + expect(page).to have_css "a[data-method='#{method}']" end end end diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 9a1e216c6d2..1e49e7b02fe 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -84,7 +84,7 @@ RSpec.describe 'Group show page' do it 'shows `Create new subgroup` link' do expect(page).to have_link( s_('GroupsEmptyState|Create new subgroup'), - href: new_group_path(parent_id: group.id) + href: new_group_path(parent_id: group.id, anchor: 'create-group-pane') ) end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index ece6167b193..a146f534191 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -221,14 +221,13 @@ RSpec.describe 'Group' do let(:user) { create(:admin) } before do - visit new_group_path(parent_id: group.id) + visit new_group_path(parent_id: group.id, anchor: 'create-group-pane') end context 'when admin mode is enabled', :enable_admin_mode do it 'creates a nested group' do - click_link 'Create group' - fill_in 'Group name', with: 'bar' - click_button 'Create group' + fill_in 'Subgroup name', with: 'bar' + click_button 'Create subgroup' expect(page).to have_current_path(group_path('foo/bar'), ignore_query: true) expect(page).to have_selector 'h1', text: 'bar' @@ -237,7 +236,7 @@ RSpec.describe 'Group' do context 'when admin mode is disabled' do it 'is not allowed' do - expect(page).not_to have_button('Create group') + expect(page).not_to have_button('Create subgroup') end end end @@ -250,11 +249,10 @@ RSpec.describe 'Group' do sign_out(:user) sign_in(user) - visit new_group_path(parent_id: group.id) - click_link 'Create group' + visit new_group_path(parent_id: group.id, anchor: 'create-group-pane') - fill_in 'Group name', with: 'bar' - click_button 'Create group' + fill_in 'Subgroup name', with: 'bar' + click_button 'Create subgroup' expect(page).to have_current_path(group_path('foo/bar'), ignore_query: true) expect(page).to have_selector 'h1', text: 'bar' @@ -268,7 +266,7 @@ RSpec.describe 'Group' do end context 'when creating subgroup' do - let(:path) { new_group_path(parent_id: group.id) } + let(:path) { new_group_path(parent_id: group.id, anchor: 'create-group-pane') } it 'does not render recaptcha' do visit path @@ -278,24 +276,50 @@ RSpec.describe 'Group' do end end + context 'when many parent groups are available' do + let_it_be(:group2) { create(:group, path: 'foo2') } + let_it_be(:group3) { create(:group, path: 'foo3') } + + before do + group.add_owner(user) + group2.add_maintainer(user) + group3.add_developer(user) + visit new_group_path(parent_id: group.id, anchor: 'create-group-pane') + end + + it 'creates private subgroup' do + fill_in 'Subgroup name', with: 'bar' + click_button 'foo' + + expect(page).to have_css('[data-testid="select_group_dropdown_item"]', text: 'foo2') + expect(page).not_to have_css('[data-testid="select_group_dropdown_item"]', text: 'foo3') + + click_button 'foo2' + click_button 'Create subgroup' + + expect(page).to have_current_path(group_path('foo2/bar'), ignore_query: true) + expect(page).to have_selector('h1', text: 'bar') + expect(page).to have_selector('.visibility-icon [data-testid="lock-icon"]') + end + end + describe 'real-time group url validation', :js do let_it_be(:subgroup) { create(:group, path: 'sub', parent: group) } before do group.add_owner(user) - visit new_group_path(parent_id: group.id) - click_link 'Create group' + visit new_group_path(parent_id: group.id, anchor: 'create-group-pane') end it 'shows a message if group url is available' do - fill_in 'Group URL', with: group.path + fill_in 'Subgroup slug', with: group.path wait_for_requests expect(page).to have_content('Group path is available') end it 'shows an error if group url is taken' do - fill_in 'Group URL', with: subgroup.path + fill_in 'Subgroup slug', with: subgroup.path wait_for_requests expect(page).to have_content("Group path is unavailable. Path has been replaced with a suggested available path.") @@ -308,7 +332,7 @@ RSpec.describe 'Group' do sign_out(:user) sign_in(create(:user)) - visit new_group_path(parent_id: group.id) + visit new_group_path(parent_id: group.id, anchor: 'create-group-pane') expect(page).to have_title('Not Found') expect(page).to have_content('Page Not Found') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index a83d4191f38..e54fda8edda 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -394,7 +394,7 @@ RSpec.describe 'Pipeline', :js do expect(page).to have_selector('button[aria-label="Cancel downstream pipeline"]') end - context 'when canceling' do + context 'when canceling', :sidekiq_inline do before do find('button[aria-label="Cancel downstream pipeline"]').click wait_for_requests diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 2a444dad486..068e1fd4243 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -132,10 +132,10 @@ RSpec.describe 'User page' do let_it_be(:followee) { create(:user) } let_it_be(:follower) { create(:user) } - it 'does not show link to follow' do + it 'does not show button to follow' do subject - expect(page).not_to have_link(text: 'Follow', class: 'gl-button') + expect(page).not_to have_button(text: 'Follow', class: 'gl-button') end it 'shows 0 followers and 0 following' do @@ -155,11 +155,11 @@ RSpec.describe 'User page' do expect(page).to have_content('1 following') end - it 'does show link to follow' do + it 'does show button to follow' do sign_in(user) visit user_path(followee) - expect(page).to have_link(text: 'Follow', class: 'gl-button') + expect(page).to have_button(text: 'Follow', class: 'gl-button') end it 'does show link to unfollow' do @@ -168,7 +168,7 @@ RSpec.describe 'User page' do visit user_path(followee) - expect(page).to have_link(text: 'Unfollow', class: 'gl-button') + expect(page).to have_button(text: 'Unfollow', class: 'gl-button') end end end diff --git a/spec/frontend/groups/components/group_name_and_path_spec.js b/spec/frontend/groups/components/group_name_and_path_spec.js index 9c9bdead6fa..823d2ed286a 100644 --- a/spec/frontend/groups/components/group_name_and_path_spec.js +++ b/spec/frontend/groups/components/group_name_and_path_spec.js @@ -1,18 +1,23 @@ -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; import { merge } from 'lodash'; -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlDropdown, GlTruncate, GlDropdownItem } from '@gitlab/ui'; import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; import GroupNameAndPath from '~/groups/components/group_name_and_path.vue'; import { getGroupPathAvailability } from '~/rest_api'; import { createAlert } from '~/flash'; import { helpPagePath } from '~/helpers/help_page_helper'; +import searchGroupsWhereUserCanCreateSubgroups from '~/groups/queries/search_groups_where_user_can_create_subgroups.query.graphql'; jest.mock('~/flash'); jest.mock('~/rest_api', () => ({ getGroupPathAvailability: jest.fn(), })); +Vue.use(VueApollo); + describe('GroupNameAndPath', () => { let wrapper; @@ -20,6 +25,17 @@ describe('GroupNameAndPath', () => { const mockGroupUrl = 'my-awesome-group'; const mockGroupUrlSuggested = 'my-awesome-group1'; + const mockQueryResponse = jest.fn().mockResolvedValue({ + data: { + currentUser: { + id: '1', + groups: { + nodes: [{ id: '2', fullPath: '/path2' }], + }, + }, + }, + }); + const defaultProvide = { basePath: 'http://gitlab.com/', fields: { @@ -32,13 +48,20 @@ describe('GroupNameAndPath', () => { pattern: '[a-zA-Z0-9_\\.][a-zA-Z0-9_\\-\\.]*[a-zA-Z0-9_\\-]|[a-zA-Z0-9_]', }, parentId: { name: 'group[parent_id]', id: 'group_parent_id', value: '1' }, + parentFullPath: { name: 'group[parent_full_path]', id: 'group_full_path', value: '/path1' }, groupId: { name: 'group[id]', id: 'group_id', value: '' }, }, + newSubgroup: false, mattermostEnabled: false, }; const createComponent = ({ provide = {} } = {}) => { - wrapper = mountExtended(GroupNameAndPath, { provide: merge({}, defaultProvide, provide) }); + wrapper = mountExtended(GroupNameAndPath, { + provide: merge({}, defaultProvide, provide), + apolloProvider: createMockApollo([ + [searchGroupsWhereUserCanCreateSubgroups, mockQueryResponse], + ]), + }); }; const createComponentEditGroup = ({ path = mockGroupUrl } = {}) => { createComponent({ @@ -46,8 +69,11 @@ describe('GroupNameAndPath', () => { }); }; - const findGroupNameField = () => wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.name.label); - const findGroupUrlField = () => wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.path.label); + const findGroupNameField = () => wrapper.findByLabelText('Group name'); + const findGroupUrlField = () => wrapper.findByLabelText('Group URL'); + const findSubgroupNameField = () => wrapper.findByLabelText('Subgroup name'); + const findSubgroupSlugField = () => wrapper.findByLabelText('Subgroup slug'); + const findSelectedGroup = () => wrapper.findComponent(GlTruncate); const findAlert = () => extendedWrapper(wrapper.findComponent(GlAlert)); const apiMockAvailablePath = () => { @@ -79,6 +105,41 @@ describe('GroupNameAndPath', () => { }); }); + describe('when creating a new subgroup', () => { + beforeEach(() => { + createComponent({ provide: { newSubgroup: true } }); + }); + + it('updates `Subgroup slug` field as user types', async () => { + await findSubgroupNameField().setValue(mockGroupName); + + expect(findSubgroupSlugField().element.value).toBe(mockGroupUrl); + }); + + describe('when user selects parent group', () => { + it('updates `Subgroup URL` dropdown and calls API', async () => { + expect(findSelectedGroup().text()).toContain('/path1'); + + await findSubgroupNameField().setValue(mockGroupName); + + wrapper.findComponent(GlDropdown).vm.$emit('shown'); + await wrapper.vm.$apollo.queries.currentUserGroups.refetch(); + jest.runOnlyPendingTimers(); + await waitForPromises(); + + wrapper.findComponent(GlDropdownItem).vm.$emit('click'); + await nextTick(); + + expect(findSelectedGroup().text()).toContain('/path2'); + expect(getGroupPathAvailability).toHaveBeenCalled(); + + expect(wrapper.findByText(GroupNameAndPath.i18n.inputs.path.validFeedback).exists()).toBe( + true, + ); + }); + }); + }); + describe('when editing a group', () => { it('does not update `Group URL` field and does not call API', async () => { const groupUrl = 'foo-bar'; @@ -346,9 +407,7 @@ describe('GroupNameAndPath', () => { it('shows `Group ID` field', () => { createComponentEditGroup(); - expect( - wrapper.findByLabelText(GroupNameAndPath.i18n.inputs.groupId.label).element.value, - ).toBe('1'); + expect(wrapper.findByLabelText('Group ID').element.value).toBe('1'); }); }); }); diff --git a/spec/frontend/pages/groups/new/components/app_spec.js b/spec/frontend/pages/groups/new/components/app_spec.js new file mode 100644 index 00000000000..ab483316086 --- /dev/null +++ b/spec/frontend/pages/groups/new/components/app_spec.js @@ -0,0 +1,39 @@ +import { shallowMount } from '@vue/test-utils'; +import App from '~/pages/groups/new/components/app.vue'; +import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue'; + +describe('App component', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = shallowMount(App, { propsData }); + }; + + const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage); + + const findCreateGroupPanel = () => + findNewNamespacePage() + .props('panels') + .find((panel) => panel.name === 'create-group-pane'); + + afterEach(() => { + wrapper.destroy(); + }); + + it('creates correct component for group creation', () => { + createComponent(); + + expect(findNewNamespacePage().props('initialBreadcrumb')).toBe('New group'); + expect(findCreateGroupPanel().title).toBe('Create group'); + }); + + it('creates correct component for subgroup creation', () => { + const props = { parentGroupName: 'parent', importExistingGroupPath: '/path' }; + + createComponent(props); + + expect(findNewNamespacePage().props('initialBreadcrumb')).toBe('parent'); + expect(findCreateGroupPanel().title).toBe('Create subgroup'); + expect(findCreateGroupPanel().detailProps).toEqual(props); + }); +}); diff --git a/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js b/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js new file mode 100644 index 00000000000..56a1fd03f71 --- /dev/null +++ b/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js @@ -0,0 +1,57 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlSprintf, GlLink } from '@gitlab/ui'; +import CreateGroupDescriptionDetails from '~/pages/groups/new/components/create_group_description_details.vue'; +import { helpPagePath } from '~/helpers/help_page_helper'; + +describe('CreateGroupDescriptionDetails component', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = shallowMount(CreateGroupDescriptionDetails, { + propsData, + stubs: { GlSprintf, GlLink }, + }); + }; + + const findLinkHref = (at) => wrapper.findAllComponents(GlLink).at(at); + + afterEach(() => { + wrapper.destroy(); + }); + + it('creates correct component for group creation', () => { + createComponent(); + + const groupsLink = findLinkHref(0); + expect(groupsLink.attributes('href')).toBe(helpPagePath('user/group/index')); + expect(groupsLink.text()).toBe('Groups'); + + const subgroupsLink = findLinkHref(1); + expect(subgroupsLink.text()).toBe('subgroups'); + expect(subgroupsLink.attributes('href')).toBe(helpPagePath('user/group/subgroups/index')); + + expect(wrapper.text()).toBe( + 'Groups allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects. Groups can also be nested by creating subgroups.', + ); + }); + + it('creates correct component for subgroup creation', () => { + createComponent({ parentGroupName: 'parent', importExistingGroupPath: '/path' }); + + const groupsLink = findLinkHref(0); + expect(groupsLink.attributes('href')).toBe(helpPagePath('user/group/index')); + expect(groupsLink.text()).toBe('Groups'); + + const subgroupsLink = findLinkHref(1); + expect(subgroupsLink.text()).toBe('subgroups'); + expect(subgroupsLink.attributes('href')).toBe(helpPagePath('user/group/subgroups/index')); + + const importGroupLink = findLinkHref(2); + expect(importGroupLink.text()).toBe('import an existing group'); + expect(importGroupLink.attributes('href')).toBe('/path'); + + expect(wrapper.text()).toBe( + 'Groups and subgroups allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects. You can also import an existing group.', + ); + }); +}); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index 9550368eefc..9aede4ab28a 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -6,6 +6,7 @@ import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; import { followUser, unfollowUser } from '~/api/user_api'; +import { mockTracking } from 'helpers/tracking_helper'; jest.mock('~/flash'); jest.mock('~/api/user_api', () => ({ @@ -51,6 +52,18 @@ describe('User Popover Component', () => { const findUserLocalTime = () => wrapper.findByTestId('user-popover-local-time'); const findToggleFollowButton = () => wrapper.findByTestId('toggle-follow-button'); + const itTracksToggleFollowButtonClick = (expectedLabel) => { + it('tracks click', async () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + await findToggleFollowButton().trigger('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', { + label: expectedLabel, + }); + }); + }; + const createWrapper = (props = {}) => { wrapper = mountExtended(UserPopover, { propsData: { @@ -341,6 +354,8 @@ describe('User Popover Component', () => { expect(wrapper.emitted().unfollow).toBeFalsy(); }); + itTracksToggleFollowButtonClick('follow_from_user_popover'); + describe('when an error occurs', () => { beforeEach(() => { followUser.mockRejectedValue({}); @@ -388,6 +403,8 @@ describe('User Popover Component', () => { expect(wrapper.emitted().unfollow.length).toBe(1); }); + itTracksToggleFollowButtonClick('unfollow_from_user_popover'); + describe('when an error occurs', () => { beforeEach(async () => { unfollowUser.mockRejectedValue({}); diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 36e44a9b941..2c1061d2f1b 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -452,9 +452,31 @@ RSpec.describe GroupsHelper do end end - describe '#group_name_and_path_app_data' do - let_it_be(:group) { build(:group, name: 'My awesome group', path: 'my-awesome-group') } + describe '#subgroup_creation_data' do + let_it_be(:name) { 'parent group' } + let_it_be(:group) { build(:group, name: name) } let_it_be(:subgroup) { build(:group, parent: group) } + + context 'when group has a parent' do + it 'returns expected hash' do + expect(subgroup_creation_data(subgroup)).to eq({ + import_existing_group_path: '/groups/new#import-group-pane', + parent_group_name: name + }) + end + end + + context 'when group does not have a parent' do + it 'returns expected hash' do + expect(subgroup_creation_data(group)).to eq({ + import_existing_group_path: '/groups/new#import-group-pane', + parent_group_name: nil + }) + end + end + end + + describe '#group_name_and_path_app_data' do let_it_be(:root_url) { 'https://gitlab.com/' } before do @@ -464,17 +486,10 @@ RSpec.describe GroupsHelper do context 'when group has a parent' do it 'returns expected hash' do - expect(group_name_and_path_app_data(subgroup)).to match( - { base_path: 'https://gitlab.com/my-awesome-group', mattermost_enabled: 'true' } - ) - end - end - - context 'when group does not have a parent' do - it 'returns expected hash' do - expect(group_name_and_path_app_data(group)).to match( - { base_path: root_url, mattermost_enabled: 'true' } - ) + expect(group_name_and_path_app_data).to match({ + base_path: 'https://gitlab.com/', + mattermost_enabled: 'true' + }) end end end @@ -493,7 +508,7 @@ RSpec.describe GroupsHelper do it 'returns expected hash' do expect(helper.subgroups_and_projects_list_app_data(group)).to match({ show_schema_markup: 'true', - new_subgroup_path: including("groups/new?parent_id=#{group.id}"), + new_subgroup_path: including("groups/new?parent_id=#{group.id}#create-group-pane"), new_project_path: including("/projects/new?namespace_id=#{group.id}"), new_subgroup_illustration: including('illustrations/subgroup-create-new-sm'), new_project_illustration: including('illustrations/project-create-new-sm'), diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb index 2fe237fb996..45664a7e0bd 100644 --- a/spec/helpers/nav/new_dropdown_helper_spec.rb +++ b/spec/helpers/nav/new_dropdown_helper_spec.rb @@ -173,7 +173,7 @@ RSpec.describe Nav::NewDropdownHelper do menu_item: ::Gitlab::Nav::TopNavMenuItem.build( id: 'new_subgroup', title: 'New subgroup', - href: "/groups/new?parent_id=#{group.id}", + href: "/groups/new?parent_id=#{group.id}#create-group-pane", data: { track_action: 'click_link_new_subgroup', track_label: 'plus_menu_dropdown' } ) ) diff --git a/spec/mailers/emails/admin_notification_spec.rb b/spec/mailers/emails/admin_notification_spec.rb index 1b770d6d4a2..33b8558bfa3 100644 --- a/spec/mailers/emails/admin_notification_spec.rb +++ b/spec/mailers/emails/admin_notification_spec.rb @@ -11,68 +11,4 @@ RSpec.describe Emails::AdminNotification do expect(Notify).to be_respond_to(email_method) end end - - describe 'user_auto_banned_email' do - let_it_be(:admin) { create(:user) } - let_it_be(:user) { create(:user) } - - let(:max_project_downloads) { 5 } - let(:time_period) { 600 } - let(:group) { nil } - - subject do - Notify.user_auto_banned_email( - admin.id, user.id, - max_project_downloads: max_project_downloads, - within_seconds: time_period, - group: group - ) - end - - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'appearance header and footer enabled' - it_behaves_like 'appearance header and footer not enabled' - - it 'is sent to the administrator' do - is_expected.to deliver_to admin.email - end - - it 'has the correct subject' do - is_expected.to have_subject "We've detected unusual activity" - end - - it 'includes the name of the user' do - is_expected.to have_body_text user.name - end - - it 'includes the scope of the ban' do - is_expected.to have_body_text "banned from your GitLab instance" - end - - it 'includes the reason' do - is_expected.to have_body_text "due to them downloading more than 5 project repositories within 10 minutes" - end - - it 'includes a link to unban the user' do - is_expected.to have_body_text admin_users_url(filter: 'banned') - end - - it 'includes a link to change the settings' do - is_expected.to have_body_text network_admin_application_settings_url(anchor: 'js-ip-limits-settings') - end - - it 'includes the email reason' do - is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>} - end - - context 'when scoped to a group' do - let(:group) { create(:group) } - - it 'includes the scope of the ban' do - is_expected.to have_body_text "banned from your group (#{group.name})" - end - end - end end diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb index d9106aa42c4..6ec1b7ce9b6 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb @@ -40,7 +40,7 @@ RSpec.describe 'PipelineCancel' do expect(build).not_to be_canceled end - it "cancels all cancelable builds from a pipeline" do + it 'cancels all cancelable builds from a pipeline', :sidekiq_inline do build = create(:ci_build, :running, pipeline: pipeline) post_graphql_mutation(mutation, current_user: user) diff --git a/spec/requests/api/issue_links_spec.rb b/spec/requests/api/issue_links_spec.rb index 90238c8bf76..98f72f22cdc 100644 --- a/spec/requests/api/issue_links_spec.rb +++ b/spec/requests/api/issue_links_spec.rb @@ -162,12 +162,29 @@ RSpec.describe API::IssueLinks do end context 'when unauthenticated' do - it 'returns 401' do - issue_link = create(:issue_link) + context 'when accessing an issue of a private project' do + it 'returns 401' do + issue_link = create(:issue_link) - perform_request(issue_link.id) + perform_request(issue_link.id) - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + # This isn't ideal, see https://gitlab.com/gitlab-org/gitlab/-/issues/364077 + context 'when accessing an issue of a public project' do + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:public_issue) { create(:issue, project: project) } + + it 'returns 401' do + issue_link = create(:issue_link, source: issue, target: public_issue) + + perform_request(issue_link.id) + + expect(response).to have_gitlab_http_status(:unauthorized) + end end end diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb index 208da345e7f..79c22871b44 100644 --- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb +++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb @@ -40,7 +40,10 @@ RSpec.describe 'layouts/header/_new_dropdown' do it 'has a "New subgroup" link' do render - expect(rendered).to have_link('New subgroup', href: new_group_path(parent_id: group.id)) + expect(rendered).to have_link( + 'New subgroup', + href: new_group_path(parent_id: group.id, anchor: 'create-group-pane') + ) end end diff --git a/spec/workers/ci/runners/reconcile_existing_runner_versions_cron_worker_spec.rb b/spec/workers/ci/runners/reconcile_existing_runner_versions_cron_worker_spec.rb new file mode 100644 index 00000000000..ab310bffe37 --- /dev/null +++ b/spec/workers/ci/runners/reconcile_existing_runner_versions_cron_worker_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::Runners::ReconcileExistingRunnerVersionsCronWorker do + subject(:worker) { described_class.new } + + describe '#perform' do + context 'when scheduled by cronjob' do + it 'reschedules itself' do + expect(described_class).to(receive(:perform_in).with(a_value_between(0, 12.hours.in_seconds), false)) + expect(::Ci::Runners::ReconcileExistingRunnerVersionsService).not_to receive(:new) + + worker.perform + end + end + + context 'when self-scheduled' do + include_examples 'an idempotent worker' do + subject(:perform) { perform_multiple(false, worker: worker) } + + it 'executes the service' do + expect_next_instance_of(Ci::Runners::ReconcileExistingRunnerVersionsService) do |service| + expect(service).to receive(:execute).and_return({}) + end.exactly(worker_exec_times) + + perform + end + end + + it 'logs the service result' do + expect_next_instance_of(Ci::Runners::ReconcileExistingRunnerVersionsService) do |service| + expect(service).to receive(:execute).and_return({ some_job_result_key: 'some_value' }) + end + + worker.perform(false) + + expect(worker.logging_extras).to eq({ + 'extra.ci_runners_reconcile_existing_runner_versions_cron_worker.some_job_result_key' => 'some_value' + }) + end + end + end +end diff --git a/tests.yml b/tests.yml index 5fb0abe20b0..3b1ac26a195 100644 --- a/tests.yml +++ b/tests.yml @@ -19,6 +19,10 @@ mapping: - source: ee/lib/(.+)\.rb test: ee/spec/lib/%s_spec.rb + # See https://gitlab.com/gitlab-org/gitlab/-/issues/368628 + - source: lib/gitlab/usage_data_counters/(.+)\.rb + test: spec/lib/gitlab/usage_data_spec.rb + # FOSS lib & tooling should map to respective spec - source: (tooling/)?lib/(.+)\.rb test: spec/%slib/%s_spec.rb diff --git a/yarn.lock b/yarn.lock index 6d1ee1f56cc..da5f941600d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1607,11 +1607,6 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -1631,13 +1626,6 @@ resolved "https://registry.yarnpkg.com/@sourcegraph/code-host-integration/-/code-host-integration-0.0.60.tgz#2043877fabb7eb986fcb61b67ee480afbb29f4f0" integrity sha512-T+MvM8SUF7daA279hyQgwmva3J5LvPqwgQ/mWwxdVshehOQIPLUd310I0c6x6nZ0F/x4UjDWgRWzAqy6NLwV1w== -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@testing-library/dom@^7.16.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.5.tgz#862124eec8c37ad184716379f09742476b23815d" @@ -2628,13 +2616,6 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.10.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -2653,9 +2634,9 @@ ansi-html-community@^0.0.8: integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" @@ -3131,20 +3112,6 @@ bootstrap@4.5.3, "bootstrap@>=4.5.3 <5.0.0": resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ== -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3385,19 +3352,6 @@ cache-loader@^4.1.0: neo-async "^2.6.1" schema-utils "^2.0.0" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -3473,14 +3427,6 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -3528,7 +3474,7 @@ cheerio@^1.0.0-rc.9: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -"chokidar@>=3.0.0 <4.0.0", chokidar@^2.1.8, chokidar@^3.2.2, chokidar@^3.4.1, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^2.1.8, chokidar@^3.4.1, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -3600,11 +3546,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== - clipboard@^2.0.0, clipboard@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba" @@ -3639,13 +3580,6 @@ clone-regexp@^2.1.0: dependencies: is-regexp "^2.0.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3837,18 +3771,6 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - confusing-browser-globals@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" @@ -4098,11 +4020,6 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - css-color-names@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -4161,9 +4078,9 @@ css-values@^0.1.0: postcss-value-parser "^3.3.0" css-what@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.0.tgz#f0bf4f8bac07582722346ab243f6a35b512cfc47" - integrity sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA== + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== css@^2.1.0: version "2.2.4" @@ -4773,7 +4690,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.2.6, debug@^3.2.7: +debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4822,13 +4739,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -4856,11 +4766,6 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -5090,27 +4995,15 @@ domutils@^2.5.2, domutils@^2.6.0: domelementtype "^2.2.0" domhandler "^4.2.0" -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - dropzone@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3" integrity sha1-++esu5kY4HBkiQcu9mPv/u+KefM= dset@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.1.tgz#07de5af7a8d03eab337ad1a8ba77fe17bba61a8c" - integrity sha512-hYf+jZNNqJBD2GiMYb+5mqOIX4R4RRHXU3qWMWYN+rqcR2/YpRL2bUHr8C8fU+5DNvqYjJ8YvMGSLuVPWU1cNg== - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + version "3.1.2" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a" + integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q== duplexer@^0.1.2: version "0.1.2" @@ -5191,11 +5084,6 @@ emoji-regex@^10.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.0.tgz#96559e19f82231b436403e059571241d627c42b8" integrity sha512-KmJa8l6uHi1HrBI34udwlzZY1jOEuID/ft4d8BSSEdRyap7PwBEt910453PJa5MuGvxkLqlt4Uvhu7tttFHViw== -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -5327,11 +5215,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -6254,14 +6137,14 @@ get-stdin@~9.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== -get-stream@^4.0.0, get-stream@^4.1.0: +get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== @@ -6332,13 +6215,6 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== - dependencies: - ini "^1.3.5" - global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -6399,23 +6275,6 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -6579,11 +6438,6 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -6725,9 +6579,9 @@ hoist-non-react-statics@^3.3.2: react-is "^16.7.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^4.0.1: version "4.1.0" @@ -6783,11 +6637,6 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6924,10 +6773,10 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -immer@^7.0.7: - version "7.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.7.tgz#9dfe713d49bf871cc59aedfce59b1992fa37a977" - integrity sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw== +immer@^9.0.15: + version "9.0.15" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.15.tgz#0b9169e5b1d22137aba7d43f8a81a495dd1b62dc" + integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ== immutable@^4.0.0: version "4.1.0" @@ -6942,11 +6791,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - import-lazy@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" @@ -7003,7 +6847,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -7193,11 +7037,6 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -7215,24 +7054,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - is-number-object@^1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" @@ -7252,16 +7078,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -7372,11 +7188,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -8011,11 +7822,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -8036,10 +7842,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -8076,13 +7882,13 @@ jsonfile@^4.0.0: graceful-fs "^4.1.6" jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" jszip-utils@^0.0.2: @@ -8108,13 +7914,6 @@ katex@^0.13.2: dependencies: commander "^6.0.0" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - khroma@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b" @@ -8173,13 +7972,6 @@ known-css-properties@^0.25.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776" integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA== -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -8428,16 +8220,6 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - lowlight@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" @@ -9174,11 +8956,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -9520,9 +9297,9 @@ node-modules-regexp@^1.0.0: integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= node-notifier@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" - integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== + version "8.0.2" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== dependencies: growly "^1.3.0" is-wsl "^2.2.0" @@ -9536,21 +9313,21 @@ node-releases@^2.0.3: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== -nodemon@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== +nodemon@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" + integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== dependencies: - chokidar "^3.2.2" - debug "^3.2.6" + chokidar "^3.5.2" + debug "^3.2.7" ignore-by-default "^1.0.1" minimatch "^3.0.4" - pstree.remy "^1.1.7" + pstree.remy "^1.1.8" semver "^5.7.1" + simple-update-notifier "^1.0.7" supports-color "^5.5.0" touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" + undefsafe "^2.0.5" nopt@^4.0.3: version "4.0.3" @@ -9599,11 +9376,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -9814,11 +9586,6 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -9896,16 +9663,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - pako@~1.0.2, pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -10243,11 +10000,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - pretender@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/pretender/-/pretender-3.4.3.tgz#a3b4160516007075d29127262f3a0063d19896e9" @@ -10495,7 +10247,7 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -pstree.remy@^1.1.7: +pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== @@ -10546,13 +10298,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== - dependencies: - escape-goat "^2.0.0" - purgecss-from-html@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/purgecss-from-html/-/purgecss-from-html-4.0.3.tgz#28d86d3dc8292581c4ab529a77a57daf7c2dd940" @@ -10646,16 +10391,6 @@ raw-loader@^4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10804,20 +10539,6 @@ regexpu-core@^5.0.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -registry-auth-token@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" - integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - regjsgen@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" @@ -10988,13 +10709,6 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.2 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -11225,24 +10939,17 @@ selfsigned@^2.0.1: dependencies: node-forge "^1" -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.0.0: +semver@7.0.0, semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -11415,6 +11122,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-update-notifier@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== + dependencies: + semver "~7.0.0" + sirv@^1.0.7: version "1.0.11" resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4" @@ -11632,16 +11346,16 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -11720,16 +11434,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11768,7 +11473,7 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= -strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -12018,11 +11723,6 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== - terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -12047,9 +11747,9 @@ terser-webpack-plugin@^1.4.3: worker-farm "^1.7.0" terser@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - integrity sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg== + version "4.8.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" + integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -12149,9 +11849,9 @@ tmp@^0.0.33: os-tmpdir "~1.0.2" tmpl@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" - integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-arraybuffer@^1.0.0: version "1.0.1" @@ -12170,11 +11870,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -12408,12 +12103,10 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undefsafe@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" - integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= - dependencies: - debug "^2.2.0" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== underscore-contrib@~0.3.0: version "0.3.0" @@ -12433,9 +12126,9 @@ underscore@~1.8.3: integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= undici@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.0.0.tgz#3c1e08c7f0df90c485d5d8dbb0517e11e34f2090" - integrity sha512-VhUpiZ3No1DOPPQVQnsDZyfcbTTcHdcgWej1PdFnSvOeJmOVDgiOHkunJmBLfmjt4CqgPQddPVjSWW0dsTs5Yg== + version "5.8.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f" + integrity sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" @@ -12497,13 +12190,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - unist-builder@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" @@ -12594,25 +12280,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -12634,13 +12301,6 @@ url-loader@^4.1.1: mime-types "^2.1.27" schema-utils "^3.0.0" -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" @@ -13200,13 +12860,6 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -13274,11 +12927,6 @@ ws@^8.3.0, ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xhr-mock@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/xhr-mock/-/xhr-mock-2.5.1.tgz#c591498a8269cc1ce5fefac20d590357affd348b" @@ -13331,9 +12979,9 @@ xterm@3.14.5: integrity sha512-DVmQ8jlEtL+WbBKUZuMxHMBgK/yeIZwkXB81bH+MGaKKnJGYwA+770hzhXPfwEIokK9On9YIFPRleVp/5G7z9g== y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== yallist@^2.1.2: version "2.1.2" |