summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue23
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/index.js2
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js7
-rw-r--r--app/assets/javascripts/pages/dashboard/milestones/index/index.js11
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/constants.js4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue11
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue17
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue97
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue26
-rw-r--r--app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_group_projects_with_merge_requests_enabled.query.graphql18
-rw-r--r--app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql21
-rw-r--r--app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql15
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb4
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb4
-rw-r--r--app/helpers/ci/variables_helper.rb4
-rw-r--r--app/models/concerns/ci/maskable.rb22
-rw-r--r--app/models/repository.rb55
-rw-r--r--app/views/ci/variables/_index.html.haml1
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/milestones/index.html.haml12
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/shared/_new_project_item_vue_select.html.haml2
-rw-r--r--config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml (renamed from config/feature_flags/development/readme_from_gitaly.yml)10
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json4
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb9
-rw-r--r--spec/features/dashboard/milestones_spec.rb14
-rw-r--r--spec/features/groups/empty_states_spec.rb8
-rw-r--r--spec/features/groups/merge_requests_spec.rb8
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js54
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js92
-rw-r--r--spec/frontend/pipelines/graph/graph_component_spec.js10
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js16
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js294
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js19
-rw-r--r--spec/helpers/ci/variables_helper_spec.rb11
-rw-r--r--spec/lib/gitlab/repository_cache/preloader_spec.rb4
-rw-r--r--spec/models/concerns/ci/maskable_spec.rb149
-rw-r--r--spec/models/repository_spec.rb58
-rw-r--r--yarn.lock434
46 files changed, 1181 insertions, 415 deletions
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 7c6c86f5e78..3857303f2c4 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -22,6 +22,7 @@ MUST be linked for the release bot to know that the associated merge requests sh
- [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- [ ] Create a new branch prefixing it with `security-`.
- [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template].
+- [ ] If this includes a breaking change, make sure to include a mention of it for the relevant versions in [`doc/update/index.md`](https://gitlab.com/gitlab-org/security/gitlab/-/blob/master/doc/update/index.md#version-specific-upgrading-instructions)
After your merge request has been approved according to our [approval guidelines] and by a team member of the AppSec team, you're ready to prepare the backports
@@ -46,7 +47,6 @@ After your merge request has been approved according to our [approval guidelines
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
-- [ ] If this includes a breaking change, make sure it is mentioned for the relevant versions in [`doc/update/index.md`](https://gitlab.com/gitlab-org/security/gitlab/-/blob/master/doc/update/index.md#version-specific-upgrading-instructions)
## Summary
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index d0e55637190..7279197a244 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-ddcce8f5e7878c997a9863f5c3ed532d7126256b
+a966c74ae41b0c749ea0433501cc39dbff96ce3f
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
index d468c82893d..b13f7dae2c2 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
@@ -18,6 +18,7 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import Tracking from '~/tracking';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
allEnvironments,
@@ -59,7 +60,7 @@ export default {
GlModal,
GlSprintf,
},
- mixins: [trackingMixin],
+ mixins: [glFeatureFlagsMixin(), trackingMixin],
inject: [
'awsLogoSvgPath',
'awsTipCommandsLink',
@@ -69,6 +70,7 @@ export default {
'environmentScopeLink',
'isProtectedByDefault',
'maskedEnvironmentVariablesLink',
+ 'maskableRawRegex',
'maskableRegex',
],
props: {
@@ -115,7 +117,7 @@ export default {
},
computed: {
canMask() {
- const regex = RegExp(this.maskableRegex);
+ const regex = RegExp(this.useRawMaskableRegexp ? this.maskableRawRegex : this.maskableRegex);
return regex.test(this.variable.value);
},
canSubmit() {
@@ -128,11 +130,17 @@ export default {
displayMaskedError() {
return !this.canMask && this.variable.masked;
},
+ isUsingRawRegexFlag() {
+ return this.glFeatures.ciRemoveCharacterLimitationRawMaskedVar;
+ },
isEditing() {
return this.mode === EDIT_VARIABLE_ACTION;
},
isExpanded() {
- return !this.variable.raw;
+ return !this.isRaw;
+ },
+ isRaw() {
+ return this.variable.raw;
},
isTipVisible() {
return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
@@ -168,6 +176,9 @@ export default {
return true;
},
+ useRawMaskableRegexp() {
+ return this.isRaw && this.isUsingRawRegexFlag;
+ },
variableValidationFeedback() {
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
},
@@ -322,11 +333,7 @@ export default {
class="gl-font-monospace!"
spellcheck="false"
/>
- <p
- v-if="variable.raw"
- class="gl-mt-2 gl-mb-0 text-secondary"
- data-testid="raw-variable-tip"
- >
+ <p v-if="isRaw" class="gl-mt-2 gl-mb-0 text-secondary" data-testid="raw-variable-tip">
{{ __('Variable value will be evaluated as raw string.') }}
</p>
</gl-form-group>
diff --git a/app/assets/javascripts/ci/ci_variable_list/index.js b/app/assets/javascripts/ci/ci_variable_list/index.js
index afb390746f9..4270c3c67fc 100644
--- a/app/assets/javascripts/ci/ci_variable_list/index.js
+++ b/app/assets/javascripts/ci/ci_variable_list/index.js
@@ -21,6 +21,7 @@ const mountCiVariableListApp = (containerEl) => {
isGroup,
isProject,
maskedEnvironmentVariablesLink,
+ maskableRawRegex,
maskableRegex,
projectFullPath,
projectId,
@@ -62,6 +63,7 @@ const mountCiVariableListApp = (containerEl) => {
isProject: parsedIsProject,
isProtectedByDefault,
maskedEnvironmentVariablesLink,
+ maskableRawRegex,
maskableRegex,
projectFullPath,
projectId,
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
index 1350837476b..f86dc2e2e30 100644
--- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -3,6 +3,9 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import projectSelect from '~/project_select';
+import { initNewResourceDropdown } from '~/vue_shared/components/new_resource_dropdown/init_new_resource_dropdown';
+import { RESOURCE_TYPE_MERGE_REQUEST } from '~/vue_shared/components/new_resource_dropdown/constants';
+import searchUserProjectsWithMergeRequestsEnabled from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql';
addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys, true);
@@ -13,3 +16,7 @@ initFilteredSearch({
});
projectSelect();
+initNewResourceDropdown({
+ resourceType: RESOURCE_TYPE_MERGE_REQUEST,
+ query: searchUserProjectsWithMergeRequestsEnabled,
+});
diff --git a/app/assets/javascripts/pages/dashboard/milestones/index/index.js b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
index b526fce6f7b..951941cc83d 100644
--- a/app/assets/javascripts/pages/dashboard/milestones/index/index.js
+++ b/app/assets/javascripts/pages/dashboard/milestones/index/index.js
@@ -1,3 +1,14 @@
import projectSelect from '~/project_select';
+import { initNewResourceDropdown } from '~/vue_shared/components/new_resource_dropdown/init_new_resource_dropdown';
+import { RESOURCE_TYPE_MILESTONE } from '~/vue_shared/components/new_resource_dropdown/constants';
+import searchUserGroupsAndProjects from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql';
projectSelect();
+initNewResourceDropdown({
+ resourceType: RESOURCE_TYPE_MILESTONE,
+ query: searchUserGroupsAndProjects,
+ extractProjects: (data) => [
+ ...(data?.user?.groups?.nodes ?? []),
+ ...(data?.projects?.nodes ?? []),
+ ],
+});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index bf0147ca885..40b4c289ab0 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -4,6 +4,9 @@ import { FILTERED_SEARCH } from '~/filtered_search/constants';
import { initBulkUpdateSidebar } from '~/issuable';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import projectSelect from '~/project_select';
+import { initNewResourceDropdown } from '~/vue_shared/components/new_resource_dropdown/init_new_resource_dropdown';
+import { RESOURCE_TYPE_MERGE_REQUEST } from '~/vue_shared/components/new_resource_dropdown/constants';
+import searchUserGroupProjectsWithMergeRequestsEnabled from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_group_projects_with_merge_requests_enabled.query.graphql';
const ISSUABLE_BULK_UPDATE_PREFIX = 'merge_request_';
@@ -17,3 +20,8 @@ initFilteredSearch({
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});
projectSelect();
+initNewResourceDropdown({
+ resourceType: RESOURCE_TYPE_MERGE_REQUEST,
+ query: searchUserGroupProjectsWithMergeRequestsEnabled,
+ extractProjects: (data) => data?.group?.projects?.nodes,
+});
diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js
index 85ca52f633e..e650a48bc2a 100644
--- a/app/assets/javascripts/pipelines/components/graph/constants.js
+++ b/app/assets/javascripts/pipelines/components/graph/constants.js
@@ -10,6 +10,8 @@ export const ONE_COL_WIDTH = 180;
export const STAGE_VIEW = 'stage';
export const LAYER_VIEW = 'layer';
+
+export const SKIP_RETRY_MODAL_KEY = 'skip_retry_modal';
export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
export const SINGLE_JOB = 'single_job';
@@ -20,3 +22,5 @@ export const BRIDGE_KIND = 'BRIDGE';
export const ACTION_FAILURE = 'action_failure';
export const IID_FAILURE = 'missing_iid';
+
+export const RETRY_ACTION_TITLE = 'Retry';
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1a05710a13e..aa46d2ba1a1 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -44,6 +44,11 @@ export default {
required: false,
default: () => ({}),
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
type: {
type: String,
required: false,
@@ -181,9 +186,11 @@ export default {
:linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')"
:show-links="showJobLinks"
+ :skip-retry-modal="skipRetryModal"
:type="$options.pipelineTypeConstants.UPSTREAM"
:view-type="viewType"
@error="onError"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
</template>
<template #main>
@@ -210,11 +217,13 @@ export default {
:highlighted-jobs="highlightedJobs"
:is-stage-view="isStageView"
:job-hovered="hoveredJobName"
+ :skip-retry-modal="skipRetryModal"
:source-job-hovered="hoveredSourceJobName"
:pipeline-expanded="pipelineExpanded"
:pipeline-id="pipeline.id"
:user-permissions="pipeline.userPermissions"
@refreshPipelineGraph="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
@jobHover="setJob"
@updateMeasurements="getMeasurements"
/>
@@ -228,12 +237,14 @@ export default {
:config-paths="configPaths"
:linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')"
+ :skip-retry-modal="skipRetryModal"
:show-links="showJobLinks"
:type="$options.pipelineTypeConstants.DOWNSTREAM"
:view-type="viewType"
@downstreamHovered="setSourceJob"
@pipelineExpandToggle="togglePipelineExpanded"
@refreshPipelineGraph="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
@scrollContainer="slidePipelineContainer"
@error="onError"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index 4d7596e6e16..8f76d7535f1 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -8,7 +8,14 @@ import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import DismissPipelineGraphCallout from '../../graphql/mutations/dismiss_pipeline_notification.graphql';
import getPipelineQuery from '../../graphql/queries/get_pipeline_header_data.query.graphql';
import { reportToSentry, reportMessageToSentry } from '../../utils';
-import { ACTION_FAILURE, IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
+import {
+ ACTION_FAILURE,
+ IID_FAILURE,
+ LAYER_VIEW,
+ SKIP_RETRY_MODAL_KEY,
+ STAGE_VIEW,
+ VIEW_TYPE_KEY,
+} from './constants';
import PipelineGraph from './graph_component.vue';
import GraphViewSelector from './graph_view_selector.vue';
import {
@@ -53,6 +60,7 @@ export default {
currentViewType: STAGE_VIEW,
canRefetchHeaderPipeline: false,
pipeline: null,
+ skipRetryModal: false,
showAlert: false,
showLinks: false,
};
@@ -206,8 +214,8 @@ export default {
if (!this.pipelineIid) {
this.reportFailure({ type: IID_FAILURE, skipSentry: true });
}
-
toggleQueryPollingByVisibility(this.$apollo.queries.pipeline);
+ this.skipRetryModal = Boolean(JSON.parse(localStorage.getItem(SKIP_RETRY_MODAL_KEY)));
},
errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
@@ -259,6 +267,9 @@ export default {
updateShowLinksState(val) {
this.showLinks = val;
},
+ setSkipRetryModal() {
+ this.skipRetryModal = true;
+ },
updateViewType(type) {
this.currentViewType = type;
},
@@ -293,10 +304,12 @@ export default {
:config-paths="configPaths"
:pipeline="pipeline"
:computed-pipeline-info="getPipelineInfo()"
+ :skip-retry-modal="skipRetryModal"
:show-links="showLinks"
:view-type="graphViewType"
@error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph"
+ @setSkipRetryModal="setSkipRetryModal"
/>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index 4f2be27486c..19f2a37c5ff 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -1,13 +1,14 @@
<script>
-import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { GlBadge, GlForm, GlFormCheckbox, GlLink, GlModal, GlTooltipDirective } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import { sprintf, __ } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
-import { BRIDGE_KIND, SINGLE_JOB } from './constants';
+import { BRIDGE_KIND, RETRY_ACTION_TITLE, SINGLE_JOB, SKIP_RETRY_MODAL_KEY } from './constants';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
@@ -35,17 +36,31 @@ import { BRIDGE_KIND, SINGLE_JOB } from './constants';
*/
export default {
+ confirmationModalDocLink: helpPagePath('/ci/pipelines/downstream_pipelines'),
i18n: {
bridgeBadgeText: __('Trigger job'),
unauthorizedTooltip: __('You are not authorized to run this manual job'),
+ confirmationModal: {
+ title: s__('PipelineGraph|Are you sure you want to retry %{jobName}?'),
+ description: s__(
+ 'PipelineGraph|Retrying a trigger job will create a new downstream pipeline.',
+ ),
+ linkText: s__('PipelineGraph|What is a downstream pipeline?'),
+ footer: __("Don't show this again"),
+ actionPrimary: { text: __('Retry') },
+ actionCancel: { text: __('Cancel') },
+ },
},
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
CiIcon,
- JobNameComponent,
GlBadge,
+ GlForm,
+ GlFormCheckbox,
GlLink,
+ GlModal,
+ JobNameComponent,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -86,6 +101,11 @@ export default {
required: false,
default: -1,
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
sourceJobHovered: {
type: String,
required: false,
@@ -102,6 +122,13 @@ export default {
default: SINGLE_JOB,
},
},
+ data() {
+ return {
+ currentSkipModalValue: this.skipRetryModal,
+ showConfirmationModal: false,
+ shouldTriggerActionClick: false,
+ };
+ },
computed: {
boundary() {
return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
@@ -115,6 +142,12 @@ export default {
hasDetails() {
return this.status.hasDetails;
},
+ hasRetryAction() {
+ return Boolean(this.job?.status?.action?.title === RETRY_ACTION_TITLE);
+ },
+ isRetryableBridge() {
+ return this.isBridge && this.hasRetryAction;
+ },
isSingleItem() {
return this.type === SINGLE_JOB;
},
@@ -127,6 +160,11 @@ export default {
nameComponent() {
return this.hasDetails ? 'gl-link' : 'div';
},
+ retryTriggerJobWarningText() {
+ return sprintf(this.$options.i18n.confirmationModal.title, {
+ jobName: this.job.name,
+ });
+ },
showStageName() {
return Boolean(this.stageName);
},
@@ -205,11 +243,26 @@ export default {
},
];
},
+ withConfirmationModal() {
+ return this.isRetryableBridge && !this.skipRetryModal;
+ },
+ },
+ watch: {
+ skipRetryModal(val) {
+ this.currentSkipModalValue = val;
+ this.shouldTriggerActionClick = false;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry('job_item', `error: ${err}, info: ${info}`);
},
methods: {
+ handleConfirmationModalPreferences() {
+ if (this.currentSkipModalValue) {
+ this.$emit('setSkipRetryModal');
+ localStorage.setItem(SKIP_RETRY_MODAL_KEY, String(this.currentSkipModalValue));
+ }
+ },
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
@@ -227,6 +280,15 @@ export default {
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
+ executePendingAction() {
+ this.shouldTriggerActionClick = true;
+ },
+ showActionConfirmationModal() {
+ this.showConfirmationModal = true;
+ },
+ toggleSkipRetryModalCheckbox() {
+ this.currentSkipModalValue = !this.currentSkipModalValue;
+ },
},
};
</script>
@@ -276,8 +338,12 @@ export default {
:link="status.action.path"
:action-icon="status.action.icon"
class="gl-mr-1"
+ :should-trigger-click="shouldTriggerActionClick"
+ :with-confirmation-modal="withConfirmationModal"
data-qa-selector="job_action_button"
+ @actionButtonClicked="handleConfirmationModalPreferences"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
+ @showActionConfirmationModal="showActionConfirmationModal"
/>
<action-component
v-if="hasUnauthorizedManualAction"
@@ -287,5 +353,28 @@ export default {
:link="`unauthorized-${computedJobId}`"
class="gl-mr-1"
/>
+ <gl-modal
+ v-if="showConfirmationModal"
+ ref="modal"
+ v-model="showConfirmationModal"
+ modal-id="action-confirmation-modal"
+ :title="retryTriggerJobWarningText"
+ :action-cancel="$options.i18n.confirmationModal.actionCancel"
+ :action-primary="$options.i18n.confirmationModal.actionPrimary"
+ @primary="executePendingAction"
+ @close="handleConfirmationModalPreferences"
+ @hide="handleConfirmationModalPreferences"
+ >
+ <p class="gl-mb-1">{{ $options.i18n.confirmationModal.description }}</p>
+ <gl-link :href="$options.confirmationModalDocLink" target="_blank">{{
+ $options.i18n.confirmationModal.linkText
+ }}</gl-link>
+ <div class="gl-mt-4 gl-display-flex">
+ <gl-form>
+ <gl-form-checkbox class="gl-min-h-0" @input="toggleSkipRetryModalCheckbox" />
+ </gl-form>
+ <p class="gl-m-0">{{ $options.i18n.confirmationModal.footer }}</p>
+ </div>
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index b06c2f15042..02e426064c9 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -36,6 +36,11 @@ export default {
type: Boolean,
required: true,
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
type: {
type: String,
required: true,
@@ -229,8 +234,10 @@ export default {
:pipeline="currentPipeline"
:computed-pipeline-info="getPipelineLayers(pipeline.id)"
:show-links="showLinks"
+ :skip-retry-modal="skipRetryModal"
:is-linked-pipeline="true"
:view-type="graphViewType"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
</div>
</li>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 4aec28295bd..ffd0fec2ca8 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -53,6 +53,11 @@ export default {
required: false,
default: () => ({}),
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
sourceJobHovered: {
type: String,
required: false,
@@ -164,6 +169,7 @@ export default {
v-if="singleJobExists(group)"
:job="group.jobs[0]"
:job-hovered="jobHovered"
+ :skip-retry-modal="skipRetryModal"
:source-job-hovered="sourceJobHovered"
:pipeline-expanded="pipelineExpanded"
:pipeline-id="pipelineId"
@@ -174,6 +180,7 @@ export default {
'gl-transition-duration-slow gl-transition-timing-function-ease',
]"
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
<div v-else-if="isParallel(group)" :class="{ 'gl-opacity-3': isFadedOut(group.name) }">
<job-group-dropdown
diff --git a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
index 387b01aee7e..7020bfc1e65 100644
--- a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
@@ -39,6 +39,16 @@ export default {
type: String,
required: true,
},
+ withConfirmationModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ shouldTriggerClick: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -52,6 +62,14 @@ export default {
return `${actionIconDash} js-icon-${actionIconDash}`;
},
},
+ watch: {
+ shouldTriggerClick(flag) {
+ if (flag && this.withConfirmationModal) {
+ this.executeAction();
+ this.$emit('actionButtonClicked');
+ }
+ },
+ },
errorCaptured(err, _vm, info) {
reportToSentry('action_component', `error: ${err}, info: ${info}`);
},
@@ -63,6 +81,13 @@ export default {
*
*/
onClickAction() {
+ if (this.withConfirmationModal) {
+ this.$emit('showActionConfirmationModal');
+ } else {
+ this.executeAction();
+ }
+ },
+ executeAction() {
this.$root.$emit(BV_HIDE_TOOLTIP, `js-ci-action-${this.link}`);
this.isDisabled = true;
this.isLoading = true;
@@ -91,6 +116,7 @@ export default {
<template>
<gl-button
:id="`js-ci-action-${link}`"
+ ref="button"
:class="cssClass"
:disabled="isDisabled"
class="js-ci-action gl-ci-action-icon-container ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center"
diff --git a/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_group_projects_with_merge_requests_enabled.query.graphql b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_group_projects_with_merge_requests_enabled.query.graphql
new file mode 100644
index 00000000000..578914dbbaf
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_group_projects_with_merge_requests_enabled.query.graphql
@@ -0,0 +1,18 @@
+query searchUserGroupProjectsWithMergeRequestsEnabled($fullPath: ID!, $search: String) {
+ group(fullPath: $fullPath) {
+ id
+ projects(
+ search: $search
+ withMergeRequestsEnabled: true
+ includeSubgroups: true
+ sort: ACTIVITY_DESC
+ ) {
+ nodes {
+ id
+ name
+ nameWithNamespace
+ webUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql
new file mode 100644
index 00000000000..8fe92cf7c6c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql
@@ -0,0 +1,21 @@
+query searchUserGroupsAndProjects($username: String!, $search: String) {
+ projects(sort: "latest_activity_desc", membership: true) {
+ nodes {
+ id
+ name
+ nameWithNamespace
+ webUrl
+ }
+ }
+
+ user(username: $username) {
+ id
+ groups(search: $search) {
+ nodes {
+ id
+ name
+ webUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql
new file mode 100644
index 00000000000..44ebf755728
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql
@@ -0,0 +1,15 @@
+query searchUserProjectsWithMergeRequestsEnabled($search: String) {
+ projects(
+ search: $search
+ membership: true
+ withMergeRequestsEnabled: true
+ sort: "latest_activity_desc"
+ ) {
+ nodes {
+ id
+ name
+ nameWithNamespace
+ webUrl
+ }
+ }
+}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index ade58ca0970..332e465914b 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -13,6 +13,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :disable_query_limiting, only: [:usage_data]
+ before_action do
+ push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
+ end
+
feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
:general, :reporting, :metrics_and_profiling, :network,
:preferences, :update, :reset_health_check_token
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 78e3ffa4af9..3562ee55c36 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -11,6 +11,10 @@ module Groups
before_action :push_licensed_features, only: [:show]
before_action :assign_variables_to_gon, only: [:show]
+ before_action do
+ push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
+ end
+
feature_category :continuous_integration
urgency :low
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index b330aacf3e9..5ec1f606ef2 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -12,6 +12,10 @@ module Projects
before_action :check_builds_available!
before_action :define_variables
+ before_action do
+ push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
+ end
+
helper_method :highlight_badge
feature_category :continuous_integration
diff --git a/app/helpers/ci/variables_helper.rb b/app/helpers/ci/variables_helper.rb
index 84572363a8d..a492c48e58c 100644
--- a/app/helpers/ci/variables_helper.rb
+++ b/app/helpers/ci/variables_helper.rb
@@ -47,6 +47,10 @@ module Ci
]
end
+ def ci_variable_maskable_raw_regex
+ Ci::Maskable::MASK_AND_RAW_REGEX.inspect.sub('\\A', '^').sub('\\z', '$')[1...-1]
+ end
+
def ci_variable_maskable_regex
Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(%r{^/}, '').sub(%r{/[a-z]*$}, '').gsub('\/', '/')
end
diff --git a/app/models/concerns/ci/maskable.rb b/app/models/concerns/ci/maskable.rb
index 62be0150ee0..f8f6693c122 100644
--- a/app/models/concerns/ci/maskable.rb
+++ b/app/models/concerns/ci/maskable.rb
@@ -12,10 +12,30 @@ module Ci
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
+ # * Single line
+ # * No spaces
+ # * Minimal length of 8 characters
+ # * Some fun is allowed
+ MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}.freeze
included do
validates :masked, inclusion: { in: [true, false] }
- validates :value, format: { with: REGEX }, if: :masked?
+ validates :value, format: { with: REGEX }, if: :masked_and_expanded?
+ validates :value, format: { with: MASK_AND_RAW_REGEX }, if: :masked_and_raw?
+ end
+
+ def masked_and_raw?
+ return false unless Feature.enabled?(:ci_remove_character_limitation_raw_masked_var)
+ return false unless self.class.method_defined?(:raw)
+
+ masked? && raw?
+ end
+
+ def masked_and_expanded?
+ return masked? unless Feature.enabled?(:ci_remove_character_limitation_raw_masked_var)
+ return masked? unless self.class.method_defined?(:raw)
+
+ masked? && !raw?
end
def to_runner_variable
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e939a4eb56b..3fd7b6126d8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -601,16 +601,10 @@ class Repository
cache_method_asymmetrically :has_visible_content?
def avatar
- if Feature.enabled?(:readme_from_gitaly)
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- avatar_path_gitaly
- end
- else
- # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/38327
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- if tree = file_on_head(:avatar)
- tree.path
- end
+ # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/38327
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ if tree = file_on_head(:avatar)
+ tree.path
end
end
end
@@ -637,11 +631,7 @@ class Repository
end
def readme_path
- if Feature.enabled?(:readme_from_gitaly)
- readme_path_gitaly
- else
- head_tree&.readme_path
- end
+ head_tree&.readme_path
end
cache_method :readme_path
@@ -1249,41 +1239,6 @@ class Repository
container.full_path,
container: container)
end
-
- def readme_path_gitaly
- # (?i) to enable case-insensitive mode
- #
- # Note: `Gitlab::FileDetector::PATTERNS[:readme]#to_s` won't work because of
- # incompatibility of regex engines between Rails and Gitaly.
- pattern = "(?i)#{Gitlab::FileDetector::PATTERNS[:readme].source}"
-
- readmes = fetch_file_paths_from_gitaly(pattern)
-
- choose_readme_to_display(readmes)
- end
-
- def avatar_path_gitaly
- # Note: `Gitlab::FileDetector::PATTERNS[:avatar]#to_s` won't work because of
- # incompatibility of regex engines between Rails and Gitaly.
- pattern = Gitlab::FileDetector::PATTERNS[:avatar].source
-
- fetch_file_paths_from_gitaly(pattern, limit: 1).first
- end
-
- def fetch_file_paths_from_gitaly(pattern, limit: 0)
- return [] if empty? || root_ref.nil?
-
- search_files_by_regexp(pattern, root_ref, limit: limit)
- end
-
- # Extracted from Tree#readme_path
- def choose_readme_to_display(readmes)
- previewable_readme = readmes.find { |name| Gitlab::MarkupHelper.previewable?(name) }
-
- return previewable_readme if previewable_readme
-
- readmes.find { |name| Gitlab::MarkupHelper.plain?(name) }
- end
end
Repository.prepend_mod_with('Repository')
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index be4fa19019f..8aaa09b7862 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -17,6 +17,7 @@
is_group: is_group.to_s,
group_id: @group&.id || '',
group_path: @group&.full_path,
+ maskable_raw_regex: ci_variable_maskable_raw_regex,
maskable_regex: ci_variable_maskable_regex,
protected_by_default: ci_variable_protected_by_default?.to_s,
aws_logo_svg_path: image_path('aws_logo.svg'),
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 97fb35b28ab..677e2bd6007 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -10,7 +10,7 @@
- if current_user
.page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _("merge request"), with_feature_enabled: 'merge_requests', type: :merge_requests
+ = render 'shared/new_project_item_vue_select'
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index bc8e3e6ab69..2556791da12 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -8,9 +8,7 @@
- if current_user
.page-title-controls
- = render 'shared/new_project_item_select',
- path: '-/milestones/new', label: _('Milestone'),
- include_groups: true, type: :milestones
+ = render 'shared/new_project_item_vue_select'
- if @milestone_states.any? { |name, count| count > 0 }
.top-area
@@ -22,9 +20,7 @@
= render 'shared/empty_states/milestones_tab', active_tab: params[:state] do
- if current_user
.page-title-controls
- = render 'shared/new_project_item_select',
- path: '-/milestones/new', label: _('Milestone'),
- include_groups: true, type: :milestones
+ = render 'shared/new_project_item_vue_select'
- else
.milestones
%ul.content-list
@@ -35,6 +31,4 @@
= render 'shared/empty_states/milestones' do
- if current_user
.page-title-controls
- = render 'shared/new_project_item_select',
- path: '-/milestones/new', label: _('Milestone'),
- include_groups: true, type: :milestones
+ = render 'shared/new_project_item_vue_select'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 92f6c896e7b..f2d0cfc42fd 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -10,7 +10,7 @@
- if @can_bulk_update
= render_if_exists 'projects/merge_requests/bulk_update_button'
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _("merge request"), type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
+ = render 'shared/new_project_item_vue_select'
= render 'shared/issuable/search_bar', type: :merge_requests
- if @can_bulk_update
diff --git a/app/views/shared/_new_project_item_vue_select.html.haml b/app/views/shared/_new_project_item_vue_select.html.haml
index 24d275c4975..9ea99df106e 100644
--- a/app/views/shared/_new_project_item_vue_select.html.haml
+++ b/app/views/shared/_new_project_item_vue_select.html.haml
@@ -1,2 +1,2 @@
- if any_projects?(@projects)
- .js-new-resource-dropdown
+ .js-new-resource-dropdown{ data: { group_id: @group&.id, full_path: @group&.full_path, username: @current_user&.username } }
diff --git a/config/feature_flags/development/readme_from_gitaly.yml b/config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml
index 6e440e928f1..bd293de9962 100644
--- a/config/feature_flags/development/readme_from_gitaly.yml
+++ b/config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml
@@ -1,8 +1,8 @@
---
-name: readme_from_gitaly
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108609
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387703
-milestone: '15.8'
+name: ci_remove_character_limitation_raw_masked_var
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109008
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388414
+milestone: '15.9'
type: development
-group: group::source code
+group: group::pipeline authoring
default_enabled: false
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e2727684b12..581436ae28d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -14801,6 +14801,9 @@ msgstr ""
msgid "Don't show again"
msgstr ""
+msgid "Don't show this again"
+msgstr ""
+
msgid "Done"
msgstr ""
@@ -30695,6 +30698,15 @@ msgstr ""
msgid "PipelineEditor|Waiting for CI content to load..."
msgstr ""
+msgid "PipelineGraph|Are you sure you want to retry %{jobName}?"
+msgstr ""
+
+msgid "PipelineGraph|Retrying a trigger job will create a new downstream pipeline."
+msgstr ""
+
+msgid "PipelineGraph|What is a downstream pipeline?"
+msgstr ""
+
msgid "PipelineScheduleIntervalPattern|Custom (%{linkStart}Learn more.%{linkEnd})"
msgstr ""
diff --git a/package.json b/package.json
index 246a022496f..7cd85dec640 100644
--- a/package.json
+++ b/package.json
@@ -125,8 +125,8 @@
"dropzone": "^4.2.0",
"editorconfig": "^0.15.3",
"emoji-regex": "^10.0.0",
- "esbuild": "0.15.18",
- "esbuild-loader": "^2.20.0",
+ "esbuild": "0.17.4",
+ "esbuild-loader": "^2.21.0",
"fast-mersenne-twister": "1.0.2",
"file-loader": "^6.2.0",
"fuzzaldrin-plus": "^0.6.0",
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index a146a6987bc..34bab9dffd0 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -36,11 +36,16 @@ RSpec.describe 'Dashboard Merge Requests', feature_category: :code_review_workfl
end
it 'shows projects only with merge requests feature enabled', :js do
- click_button 'Toggle project select'
+ click_button 'Select project to create merge request'
+ wait_for_requests
- page.within('.select2-results') do
+ page.within('[data-testid="new-resource-dropdown"]') do
expect(page).to have_content(project.full_name)
expect(page).not_to have_content(project_with_disabled_merge_requests.full_name)
+
+ find_button(project.full_name).click
+
+ expect(page).to have_link("New merge request in #{project.name}")
end
end
end
diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index a9f23f90bb1..3b197bbf009 100644
--- a/spec/features/dashboard/milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -37,19 +37,13 @@ RSpec.describe 'Dashboard > Milestones', feature_category: :team_planning do
describe 'new milestones dropdown', :js do
it 'takes user to a new milestone page', :js do
- click_button 'Toggle project select'
+ click_button 'Select project to create milestone'
- page.within('.select2-results') do
- first('.select2-result-label').click
+ page.within('[data-testid="new-resource-dropdown"]') do
+ click_button group.name
+ click_link "New milestone in #{group.name}"
end
- a_el = find('.js-new-project-item-link')
-
- expect(a_el).to have_content('New Milestone in ')
- expect(a_el).to have_no_content('New New Milestone in ')
-
- a_el.click
-
expect(page).to have_current_path(new_group_milestone_path(group), ignore_query: true)
end
end
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index a37c40f50e0..e123e223ae5 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -98,13 +98,9 @@ RSpec.describe 'Group empty states', feature_category: :subgroups do
end
it "the new #{issuable_name} button opens a project dropdown" do
- click_button 'Toggle project select'
+ click_button "Select project to create #{issuable_name}"
- if issuable == :issue
- expect(page).to have_button project.name
- else
- expect(page).to have_selector('.ajax-project-dropdown')
- end
+ expect(page).to have_button project.name
end
end
end
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index 8a3401d0572..bbb7d322b9a 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -77,9 +77,9 @@ RSpec.describe 'Group merge requests page', feature_category: :code_review_workf
end
it 'shows projects only with merge requests feature enabled', :js do
- find('.js-new-project-item-link').click
+ click_button 'Select project to create merge request'
- page.within('.select2-results') do
+ page.within('[data-testid="new-resource-dropdown"]') do
expect(page).to have_content(project.name_with_namespace)
expect(page).not_to have_content(project_with_merge_requests_disabled.name_with_namespace)
end
@@ -95,7 +95,7 @@ RSpec.describe 'Group merge requests page', feature_category: :code_review_workf
visit path
expect(page).to have_selector('.empty-state')
- expect(page).to have_link('Select project to create merge request')
+ expect(page).to have_button('Select project to create merge request')
expect(page).to have_selector('.issues-filters')
end
@@ -105,7 +105,7 @@ RSpec.describe 'Group merge requests page', feature_category: :code_review_workf
visit path
expect(page).to have_selector('.empty-state')
- expect(page).to have_link('Select project to create merge request')
+ expect(page).to have_button('Select project to create merge request')
expect(page).to have_selector('.issues-filters')
end
end
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
index 71826628ffd..d8bb03404f3 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
@@ -21,6 +21,8 @@ describe('Ci variable modal', () => {
let trackingSpy;
const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
+ const maskableRawRegex = '^\\S{8,}$';
+
const mockVariables = mockVariablesWithScopes(instanceString);
const defaultProvide = {
@@ -30,8 +32,12 @@ describe('Ci variable modal', () => {
awsTipLearnLink: '/learn-link',
containsVariableReferenceLink: '/reference',
environmentScopeLink: '/help/environments',
+ glFeatures: {
+ ciRemoveCharacterLimitationRawMaskedVar: true,
+ },
isProtectedByDefault: false,
maskedEnvironmentVariablesLink: '/variables-link',
+ maskableRawRegex,
maskableRegex,
};
@@ -423,6 +429,54 @@ describe('Ci variable modal', () => {
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
+ describe('when the variable is raw', () => {
+ const [variable] = mockVariables;
+ const validRawMaskedVariable = {
+ ...variable,
+ value: 'd$%^asdsadas',
+ masked: false,
+ raw: true,
+ };
+
+ describe('and FF is enabled', () => {
+ beforeEach(() => {
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: validRawMaskedVariable },
+ });
+ });
+
+ it('should not show an error with symbols', async () => {
+ await findMaskedVariableCheckbox().trigger('click');
+
+ expect(findModal().text()).not.toContain(maskError);
+ });
+
+ it('should not show an error when length is less than 8', async () => {
+ await findValueField().vm.$emit('input', 'a');
+ await findMaskedVariableCheckbox().trigger('click');
+
+ expect(findModal().text()).toContain(maskError);
+ });
+ });
+
+ describe('and FF is disabled', () => {
+ beforeEach(() => {
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: validRawMaskedVariable },
+ provide: { glFeatures: { ciRemoveCharacterLimitationRawMaskedVar: false } },
+ });
+ });
+
+ it('should show an error with symbols', async () => {
+ await findMaskedVariableCheckbox().trigger('click');
+
+ expect(findModal().text()).toContain(maskError);
+ });
+ });
+ });
+
describe('when the mask state is invalid', () => {
beforeEach(async () => {
const [variable] = mockVariables;
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index 225a095fb3b..e3eea503b46 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -13,18 +13,22 @@ describe('pipeline graph action component', () => {
const findButton = () => wrapper.findComponent(GlButton);
const findTooltipWrapper = () => wrapper.find('[data-testid="ci-action-icon-tooltip-wrapper"]');
+ const defaultProps = {
+ tooltipText: 'bar',
+ link: 'foo',
+ actionIcon: 'cancel',
+ };
+
+ const createComponent = ({ props } = {}) => {
+ wrapper = mount(ActionComponent, {
+ propsData: { ...defaultProps, ...props },
+ });
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onPost('foo.json').reply(HTTP_STATUS_OK);
-
- wrapper = mount(ActionComponent, {
- propsData: {
- tooltipText: 'bar',
- link: 'foo',
- actionIcon: 'cancel',
- },
- });
});
afterEach(() => {
@@ -32,31 +36,39 @@ describe('pipeline graph action component', () => {
wrapper.destroy();
});
- it('should render the provided title as a bootstrap tooltip', () => {
- expect(findTooltipWrapper().attributes('title')).toBe('bar');
- });
+ describe('render', () => {
+ beforeEach(() => {
+ createComponent();
+ });
- it('should update bootstrap tooltip when title changes', async () => {
- wrapper.setProps({ tooltipText: 'changed' });
+ it('should render the provided title as a bootstrap tooltip', () => {
+ expect(findTooltipWrapper().attributes('title')).toBe('bar');
+ });
- await nextTick();
- expect(findTooltipWrapper().attributes('title')).toBe('changed');
- });
+ it('should update bootstrap tooltip when title changes', async () => {
+ wrapper.setProps({ tooltipText: 'changed' });
- it('should render an svg', () => {
- expect(wrapper.find('.ci-action-icon-wrapper').exists()).toBe(true);
- expect(wrapper.find('svg').exists()).toBe(true);
+ await nextTick();
+ expect(findTooltipWrapper().attributes('title')).toBe('changed');
+ });
+
+ it('should render an svg', () => {
+ expect(wrapper.find('.ci-action-icon-wrapper').exists()).toBe(true);
+ expect(wrapper.find('svg').exists()).toBe(true);
+ });
});
describe('on click', () => {
- it('emits `pipelineActionRequestComplete` after a successful request', async () => {
- jest.spyOn(wrapper.vm, '$emit');
+ beforeEach(() => {
+ createComponent();
+ });
+ it('emits `pipelineActionRequestComplete` after a successful request', async () => {
findButton().trigger('click');
await waitForPromises();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
+ expect(wrapper.emitted().pipelineActionRequestComplete).toHaveLength(1);
});
it('renders a loading icon while waiting for request', async () => {
@@ -66,4 +78,40 @@ describe('pipeline graph action component', () => {
expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
});
});
+
+ describe('when has a confirmation modal', () => {
+ beforeEach(() => {
+ createComponent({ props: { withConfirmationModal: true, shouldTriggerClick: false } });
+ });
+
+ describe('and a first click is initiated', () => {
+ beforeEach(async () => {
+ findButton().trigger('click');
+
+ await waitForPromises();
+ });
+
+ it('emits `showActionConfirmationModal` event', () => {
+ expect(wrapper.emitted().showActionConfirmationModal).toHaveLength(1);
+ });
+
+ it('does not emit `pipelineActionRequestComplete` event', () => {
+ expect(wrapper.emitted().pipelineActionRequestComplete).toBeUndefined();
+ });
+ });
+
+ describe('and the `shouldTriggerClick` value becomes true', () => {
+ beforeEach(async () => {
+ await wrapper.setProps({ shouldTriggerClick: true });
+ });
+
+ it('does not emit `showActionConfirmationModal` event', () => {
+ expect(wrapper.emitted().showActionConfirmationModal).toBeUndefined();
+ });
+
+ it('emits `actionButtonClicked` event', () => {
+ expect(wrapper.emitted().actionButtonClicked).toHaveLength(1);
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js
index 2abb5f7dc58..d2f55ecefc0 100644
--- a/spec/frontend/pipelines/graph/graph_component_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_spec.js
@@ -96,6 +96,16 @@ describe('graph component', () => {
});
});
+ describe('when column request an update to the retry confirmation modal', () => {
+ beforeEach(() => {
+ findStageColumns().at(0).vm.$emit('setSkipRetryModal');
+ });
+
+ it('setSkipRetryModal is emitted', () => {
+ expect(wrapper.emitted().setSkipRetryModal).toHaveLength(1);
+ });
+ });
+
describe('when links are present', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 587a3c67168..5f5303a5339 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -199,6 +199,22 @@ describe('Pipeline graph wrapper', () => {
});
});
+ describe('events', () => {
+ beforeEach(async () => {
+ createComponentWithApollo();
+ await waitForPromises();
+ });
+ describe('when receiving `setSkipRetryModal` event', () => {
+ it('passes down `skipRetryModal` value as true', async () => {
+ expect(getGraph().props('skipRetryModal')).toBe(false);
+
+ await getGraph().vm.$emit('setSkipRetryModal');
+
+ expect(getGraph().props('skipRetryModal')).toBe(true);
+ });
+ });
+ });
+
describe('when there is an error with an action in the graph', () => {
beforeEach(async () => {
createComponentWithApollo();
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 05776ec0706..7b3ee276b91 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,7 +1,11 @@
+import MockAdapter from 'axios-mock-adapter';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlModal } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
+import axios from '~/lib/utils/axios_utils';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
delayedJob,
@@ -9,36 +13,66 @@ import {
mockJobWithoutDetails,
mockJobWithUnauthorizedAction,
triggerJob,
+ triggerJobWithRetryAction,
} from './mock_data';
describe('pipeline graph job item', () => {
+ useLocalStorageSpy();
+
let wrapper;
+ let mockAxios;
const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
const findJobWithLink = () => wrapper.findByTestId('job-with-link');
const findActionComponent = () => wrapper.findByTestId('ci-action-component');
const findBadge = () => wrapper.findComponent(GlBadge);
+ const findJobLink = () => wrapper.findByTestId('job-with-link');
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const clickOnModalPrimaryBtn = () => findModal().vm.$emit('primary');
+ const clickOnModalCancelBtn = () => findModal().vm.$emit('hide');
+ const clickOnModalCloseBtn = () => findModal().vm.$emit('close');
+
+ const myCustomClass1 = 'my-class-1';
+ const myCustomClass2 = 'my-class-2';
- const createWrapper = (propsData) => {
+ const defaultProps = {
+ job: mockJob,
+ };
+
+ const createWrapper = ({ props, data } = {}) => {
wrapper = extendedWrapper(
mount(JobItem, {
- propsData,
+ data() {
+ return {
+ ...data,
+ };
+ },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
}),
);
};
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
+ beforeEach(() => {
+ mockAxios = new MockAdapter(axios);
+ });
+
afterEach(() => {
+ mockAxios.restore();
wrapper.destroy();
});
describe('name with link', () => {
it('should render the job name and status with a link', async () => {
- createWrapper({ job: mockJob });
+ createWrapper();
await nextTick();
- const link = wrapper.find('a');
+ const link = findJobLink();
expect(link.attributes('href')).toBe(mockJob.status.detailsPath);
@@ -53,15 +87,17 @@ describe('pipeline graph job item', () => {
describe('name without link', () => {
beforeEach(() => {
createWrapper({
- job: mockJobWithoutDetails,
- cssClassJobName: 'css-class-job-name',
- jobHovered: 'test',
+ props: {
+ job: mockJobWithoutDetails,
+ cssClassJobName: 'css-class-job-name',
+ jobHovered: 'test',
+ },
});
});
it('should render status and name', () => {
expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
- expect(wrapper.find('a').exists()).toBe(false);
+ expect(findJobLink().exists()).toBe(false);
expect(wrapper.text()).toBe(mockJobWithoutDetails.name);
});
@@ -73,7 +109,7 @@ describe('pipeline graph job item', () => {
describe('action icon', () => {
it('should render the action icon', () => {
- createWrapper({ job: mockJob });
+ createWrapper();
const actionComponent = findActionComponent();
@@ -83,7 +119,11 @@ describe('pipeline graph job item', () => {
});
it('should render disabled action icon when user cannot run the action', () => {
- createWrapper({ job: mockJobWithUnauthorizedAction });
+ createWrapper({
+ props: {
+ job: mockJobWithUnauthorizedAction,
+ },
+ });
const actionComponent = findActionComponent();
@@ -96,13 +136,15 @@ describe('pipeline graph job item', () => {
describe('job style', () => {
beforeEach(() => {
createWrapper({
- job: mockJob,
- cssClassJobName: 'css-class-job-name',
+ props: {
+ job: mockJob,
+ cssClassJobName: 'css-class-job-name',
+ },
});
});
it('should render provided class name', () => {
- expect(wrapper.find('a').classes()).toContain('css-class-job-name');
+ expect(findJobLink().classes()).toContain('css-class-job-name');
});
it('does not show a badge on the job item', () => {
@@ -117,11 +159,13 @@ describe('pipeline graph job item', () => {
describe('status label', () => {
it('should not render status label when it is not provided', () => {
createWrapper({
- job: {
- id: 4258,
- name: 'test',
- status: {
- icon: 'status_success',
+ props: {
+ job: {
+ id: 4258,
+ name: 'test',
+ status: {
+ icon: 'status_success',
+ },
},
},
});
@@ -131,13 +175,15 @@ describe('pipeline graph job item', () => {
it('should not render status label when it is provided', () => {
createWrapper({
- job: {
- id: 4259,
- name: 'test',
- status: {
- icon: 'status_success',
- label: 'success',
- tooltip: 'success',
+ props: {
+ job: {
+ id: 4259,
+ name: 'test',
+ status: {
+ icon: 'status_success',
+ label: 'success',
+ tooltip: 'success',
+ },
},
},
});
@@ -149,7 +195,9 @@ describe('pipeline graph job item', () => {
describe('for delayed job', () => {
it('displays remaining time in tooltip', () => {
createWrapper({
- job: delayedJob,
+ props: {
+ job: delayedJob,
+ },
});
expect(findJobWithLink().attributes('title')).toBe(
@@ -161,7 +209,11 @@ describe('pipeline graph job item', () => {
describe('trigger job', () => {
describe('card', () => {
beforeEach(() => {
- createWrapper({ job: triggerJob });
+ createWrapper({
+ props: {
+ job: triggerJob,
+ },
+ });
});
it('shows a badge on the job item', () => {
@@ -182,7 +234,12 @@ describe('pipeline graph job item', () => {
`(
`trigger job should stay highlighted when downstream is expanded`,
({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ createWrapper({
+ props: {
+ job,
+ pipelineExpanded: { jobName, expanded },
+ },
+ });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
expect(findJobEl().classes()).toContain(triggerActiveClass);
@@ -196,7 +253,12 @@ describe('pipeline graph job item', () => {
`(
`trigger job should not be highlighted when downstream is not expanded`,
({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ createWrapper({
+ props: {
+ job,
+ pipelineExpanded: { jobName, expanded },
+ },
+ });
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
expect(findJobEl().classes()).not.toContain(triggerActiveClass);
@@ -208,60 +270,182 @@ describe('pipeline graph job item', () => {
describe('job classes', () => {
it('job class is shown', () => {
createWrapper({
- job: mockJob,
- cssClassJobName: 'my-class',
+ props: {
+ job: mockJob,
+ cssClassJobName: 'my-class',
+ },
});
- expect(wrapper.find('a').classes()).toContain('my-class');
+ const jobLinkEl = findJobLink();
+
+ expect(jobLinkEl.classes()).toContain('my-class');
- expect(wrapper.find('a').classes()).not.toContain(triggerActiveClass);
+ expect(jobLinkEl.classes()).not.toContain(triggerActiveClass);
});
it('job class is shown, along with hover', () => {
createWrapper({
- job: mockJob,
- cssClassJobName: 'my-class',
- sourceJobHovered: mockJob.name,
+ props: {
+ job: mockJob,
+ cssClassJobName: 'my-class',
+ sourceJobHovered: mockJob.name,
+ },
});
- expect(wrapper.find('a').classes()).toContain('my-class');
- expect(wrapper.find('a').classes()).toContain(triggerActiveClass);
+ const jobLinkEl = findJobLink();
+
+ expect(jobLinkEl.classes()).toContain('my-class');
+ expect(jobLinkEl.classes()).toContain(triggerActiveClass);
});
it('multiple job classes are shown', () => {
createWrapper({
- job: mockJob,
- cssClassJobName: ['my-class-1', 'my-class-2'],
+ props: {
+ job: mockJob,
+ cssClassJobName: [myCustomClass1, myCustomClass2],
+ },
});
- expect(wrapper.find('a').classes()).toContain('my-class-1');
- expect(wrapper.find('a').classes()).toContain('my-class-2');
+ const jobLinkEl = findJobLink();
+
+ expect(jobLinkEl.classes()).toContain(myCustomClass1);
+ expect(jobLinkEl.classes()).toContain(myCustomClass2);
- expect(wrapper.find('a').classes()).not.toContain(triggerActiveClass);
+ expect(jobLinkEl.classes()).not.toContain(triggerActiveClass);
});
it('multiple job classes are shown conditionally', () => {
createWrapper({
- job: mockJob,
- cssClassJobName: { 'my-class-1': true, 'my-class-2': true },
+ props: {
+ job: mockJob,
+ cssClassJobName: { [myCustomClass1]: true, [myCustomClass2]: true },
+ },
});
- expect(wrapper.find('a').classes()).toContain('my-class-1');
- expect(wrapper.find('a').classes()).toContain('my-class-2');
+ const jobLinkEl = findJobLink();
+
+ expect(jobLinkEl.classes()).toContain(myCustomClass1);
+ expect(jobLinkEl.classes()).toContain(myCustomClass2);
- expect(wrapper.find('a').classes()).not.toContain(triggerActiveClass);
+ expect(jobLinkEl.classes()).not.toContain(triggerActiveClass);
});
it('multiple job classes are shown, along with a hover', () => {
createWrapper({
- job: mockJob,
- cssClassJobName: ['my-class-1', 'my-class-2'],
- sourceJobHovered: mockJob.name,
+ props: {
+ job: mockJob,
+ cssClassJobName: [myCustomClass1, myCustomClass2],
+ sourceJobHovered: mockJob.name,
+ },
});
- expect(wrapper.find('a').classes()).toContain('my-class-1');
- expect(wrapper.find('a').classes()).toContain('my-class-2');
- expect(wrapper.find('a').classes()).toContain(triggerActiveClass);
+ const jobLinkEl = findJobLink();
+
+ expect(jobLinkEl.classes()).toContain(myCustomClass1);
+ expect(jobLinkEl.classes()).toContain(myCustomClass2);
+ expect(jobLinkEl.classes()).toContain(triggerActiveClass);
+ });
+ });
+
+ describe('confirmation modal', () => {
+ describe('when clicking on the action component', () => {
+ it.each`
+ skipRetryModal | exists | visibilityText
+ ${false} | ${true} | ${'shows'}
+ ${true} | ${false} | ${'hides'}
+ `(
+ '$visibilityText the modal when `skipRetryModal` is $skipRetryModal',
+ async ({ exists, skipRetryModal }) => {
+ createWrapper({
+ props: {
+ skipRetryModal,
+ job: triggerJobWithRetryAction,
+ },
+ });
+ await findActionComponent().trigger('click');
+
+ expect(findModal().exists()).toBe(exists);
+ },
+ );
+ });
+
+ describe('when showing the modal', () => {
+ it.each`
+ buttonName | shouldTriggerActionClick | actionBtn
+ ${'primary'} | ${true} | ${clickOnModalPrimaryBtn}
+ ${'cancel'} | ${false} | ${clickOnModalCancelBtn}
+ ${'close'} | ${false} | ${clickOnModalCloseBtn}
+ `(
+ 'clicking on $buttonName will pass down shouldTriggerActionClick as $shouldTriggerActionClick to the action component',
+ async ({ shouldTriggerActionClick, actionBtn }) => {
+ createWrapper({
+ props: {
+ skipRetryModal: false,
+ job: triggerJobWithRetryAction,
+ },
+ });
+ await findActionComponent().trigger('click');
+
+ await actionBtn();
+
+ expect(findActionComponent().props().shouldTriggerClick).toBe(shouldTriggerActionClick);
+ },
+ );
+ });
+
+ describe('when not checking the "do not show this again" checkbox', () => {
+ it.each`
+ actionName | actionBtn
+ ${'closing'} | ${clickOnModalCloseBtn}
+ ${'cancelling'} | ${clickOnModalCancelBtn}
+ ${'confirming'} | ${clickOnModalPrimaryBtn}
+ `(
+ 'does not emit any event and will not modify localstorage on $actionName',
+ async ({ actionBtn }) => {
+ createWrapper({
+ props: {
+ skipRetryModal: false,
+ job: triggerJobWithRetryAction,
+ },
+ });
+ await findActionComponent().trigger('click');
+ await actionBtn();
+
+ expect(wrapper.emitted().setSkipRetryModal).toBeUndefined();
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ },
+ );
+ });
+
+ describe('when checking the "do not show this again" checkbox', () => {
+ it.each`
+ actionName | actionBtn
+ ${'closing'} | ${clickOnModalCloseBtn}
+ ${'cancelling'} | ${clickOnModalCancelBtn}
+ ${'confirming'} | ${clickOnModalPrimaryBtn}
+ `(
+ 'emits "setSkipRetryModal" and set local storage key on $actionName the modal',
+ async ({ actionBtn }) => {
+ // We are passing the checkbox as a slot to the GlModal.
+ // The way GlModal is mounted, we can neither click on the box
+ // or emit an event directly. We therefore set the data property
+ // as it would be if the box was checked.
+ createWrapper({
+ data: {
+ currentSkipModalValue: true,
+ },
+ props: {
+ skipRetryModal: false,
+ job: triggerJobWithRetryAction,
+ },
+ });
+ await findActionComponent().trigger('click');
+ await actionBtn();
+
+ expect(wrapper.emitted().setSkipRetryModal).toHaveLength(1);
+ expect(localStorage.setItem).toHaveBeenCalledWith('skip_retry_modal', 'true');
+ },
+ );
});
});
});
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 6124d67af09..fc6dfe9ec03 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -1,5 +1,9 @@
import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
-import { BUILD_KIND, BRIDGE_KIND } from '~/pipelines/components/graph/constants';
+import {
+ BUILD_KIND,
+ BRIDGE_KIND,
+ RETRY_ACTION_TITLE,
+} from '~/pipelines/components/graph/constants';
export const mockPipelineResponse = {
data: {
@@ -1038,3 +1042,16 @@ export const triggerJob = {
action: null,
},
};
+
+export const triggerJobWithRetryAction = {
+ ...triggerJob,
+ status: {
+ ...triggerJob.status,
+ action: {
+ icon: 'retry',
+ title: RETRY_ACTION_TITLE,
+ path: '/root/ci-mock/builds/4259/retry',
+ method: 'post',
+ },
+ },
+};
diff --git a/spec/helpers/ci/variables_helper_spec.rb b/spec/helpers/ci/variables_helper_spec.rb
new file mode 100644
index 00000000000..d032e7f9087
--- /dev/null
+++ b/spec/helpers/ci/variables_helper_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::VariablesHelper, feature_category: :pipeline_authoring do
+ describe '#ci_variable_maskable_raw_regex' do
+ it 'converts to a javascript regex' do
+ expect(helper.ci_variable_maskable_raw_regex).to eq("^\\S{8,}$")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/repository_cache/preloader_spec.rb b/spec/lib/gitlab/repository_cache/preloader_spec.rb
index 71244dd41ed..21628481fed 100644
--- a/spec/lib/gitlab/repository_cache/preloader_spec.rb
+++ b/spec/lib/gitlab/repository_cache/preloader_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe Gitlab::RepositoryCache::Preloader, :use_clean_rails_redis_cachin
# Warm the cache but use a different model so they are not memoized
repos = Project.id_in(projects).order(:id).map(&:repository)
- allow(repos[0]).to receive(:readme_path_gitaly).and_return('README.txt')
- allow(repos[1]).to receive(:readme_path_gitaly).and_return('README.md')
+ allow(repos[0].head_tree).to receive(:readme_path).and_return('README.txt')
+ allow(repos[1].head_tree).to receive(:readme_path).and_return('README.md')
repos.map(&:exists?)
repos.map(&:readme_path)
diff --git a/spec/models/concerns/ci/maskable_spec.rb b/spec/models/concerns/ci/maskable_spec.rb
index 2b13fc21fe8..e2c1e08fe49 100644
--- a/spec/models/concerns/ci/maskable_spec.rb
+++ b/spec/models/concerns/ci/maskable_spec.rb
@@ -2,15 +2,16 @@
require 'spec_helper'
-RSpec.describe Ci::Maskable do
+RSpec.describe Ci::Maskable, feature_category: :pipeline_authoring do
let(:variable) { build(:ci_variable) }
describe 'masked value validations' do
subject { variable }
- context 'when variable is masked' do
+ context 'when variable is masked and expanded' do
before do
subject.masked = true
+ subject.raw = false
end
it { is_expected.not_to allow_value('hello').for(:value) }
@@ -20,6 +21,70 @@ RSpec.describe Ci::Maskable do
it { is_expected.to allow_value('helloworld').for(:value) }
end
+ context 'when method :raw is not defined' do
+ let(:test_var_class) do
+ Struct.new(:masked?) do
+ include ActiveModel::Validations
+ include Ci::Maskable
+ end
+ end
+
+ let(:variable) { test_var_class.new(true) }
+
+ it 'evaluates masked variables as expanded' do
+ expect(subject).not_to be_masked_and_raw
+ expect(subject).to be_masked_and_expanded
+ end
+ end
+
+ context 'when the ci_remove_character_limitation_raw_masked_var FF is disabled' do
+ before do
+ stub_feature_flags(ci_remove_character_limitation_raw_masked_var: false)
+ end
+
+ context 'when variable is masked and raw' do
+ before do
+ subject.masked = true
+ subject.raw = true
+ end
+
+ it { is_expected.not_to allow_value('hello').for(:value) }
+ it { is_expected.not_to allow_value('hello world').for(:value) }
+ it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.not_to allow_value('hello\rworld').for(:value) }
+ it { is_expected.not_to allow_value('hello&&&world').for(:value) }
+ it { is_expected.not_to allow_value('helloworld!!!!').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+
+ context 'when variable is not masked' do
+ before do
+ subject.masked = false
+ end
+
+ it { is_expected.to allow_value('hello').for(:value) }
+ it { is_expected.to allow_value('hello world').for(:value) }
+ it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('helloworld').for(:value) }
+ end
+ end
+
+ context 'when variable is masked and raw' do
+ before do
+ subject.masked = true
+ subject.raw = true
+ end
+
+ it { is_expected.not_to allow_value('hello').for(:value) }
+ it { is_expected.not_to allow_value('hello world').for(:value) }
+ it { is_expected.to allow_value('hello\rworld').for(:value) }
+ it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) }
+ it { is_expected.to allow_value('helloworld!!!').for(:value) }
+ it { is_expected.to allow_value('hell******world').for(:value) }
+ it { is_expected.to allow_value('helloworld123').for(:value) }
+ end
+
context 'when variable is not masked' do
before do
subject.masked = false
@@ -33,40 +98,70 @@ RSpec.describe Ci::Maskable do
end
end
- describe 'REGEX' do
- subject { Ci::Maskable::REGEX }
+ describe 'Regexes' do
+ context 'with MASK_AND_RAW_REGEX' do
+ subject { Ci::Maskable::MASK_AND_RAW_REGEX }
- it 'does not match strings shorter than 8 letters' do
- expect(subject.match?('hello')).to eq(false)
- end
+ it 'does not match strings shorter than 8 letters' do
+ expect(subject.match?('hello')).to eq(false)
+ end
- it 'does not match strings with spaces' do
- expect(subject.match?('hello world')).to eq(false)
- end
+ it 'does not match strings with spaces' do
+ expect(subject.match?('hello world')).to eq(false)
+ end
- it 'does not match strings with shell variables' do
- expect(subject.match?('hello$VARIABLEworld')).to eq(false)
- end
+ it 'does not match strings that span more than one line' do
+ string = <<~EOS
+ hello
+ world
+ EOS
- it 'does not match strings with escape characters' do
- expect(subject.match?('hello\rworld')).to eq(false)
+ expect(subject.match?(string)).to eq(false)
+ end
+
+ it 'matches valid strings' do
+ expect(subject.match?('hello$VARIABLEworld')).to eq(true)
+ expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
+ expect(subject.match?('hello\rworld')).to eq(true)
+ expect(subject.match?('HelloWorld%#^')).to eq(true)
+ end
end
- it 'does not match strings that span more than one line' do
- string = <<~EOS
- hello
- world
- EOS
+ context 'with REGEX' do
+ subject { Ci::Maskable::REGEX }
- expect(subject.match?(string)).to eq(false)
- end
+ it 'does not match strings shorter than 8 letters' do
+ expect(subject.match?('hello')).to eq(false)
+ end
- it 'does not match strings using unsupported characters' do
- expect(subject.match?('HelloWorld%#^')).to eq(false)
- end
+ it 'does not match strings with spaces' do
+ expect(subject.match?('hello world')).to eq(false)
+ end
+
+ it 'does not match strings with shell variables' do
+ expect(subject.match?('hello$VARIABLEworld')).to eq(false)
+ end
+
+ it 'does not match strings with escape characters' do
+ expect(subject.match?('hello\rworld')).to eq(false)
+ end
+
+ it 'does not match strings that span more than one line' do
+ string = <<~EOS
+ hello
+ world
+ EOS
- it 'matches valid strings' do
- expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
+ expect(subject.match?(string)).to eq(false)
+ end
+
+ it 'does not match strings using unsupported characters' do
+ expect(subject.match?('HelloWorld%#^')).to eq(false)
+ end
+
+ it 'matches valid strings' do
+ expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 3485c877373..f1528ac4de3 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2562,52 +2562,28 @@ RSpec.describe Repository, feature_category: :source_code_management do
describe '#avatar' do
let(:project) { create(:project, :repository) }
- it 'returns nil if repo is empty' do
- allow(repository).to receive(:empty).and_return(true)
+ it 'returns nil if repo does not exist' do
+ allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.avatar).to be_nil
end
it 'returns the first avatar file found in the repository' do
- expect(repository).to receive(:search_files_by_regexp).and_return(['logo.png'])
+ expect(repository).to receive(:file_on_head)
+ .with(:avatar)
+ .and_return(double(:tree, path: 'logo.png'))
expect(repository.avatar).to eq('logo.png')
end
it 'caches the output' do
- expect(repository).to receive(:search_files_by_regexp).once.and_return(['logo.png'])
+ expect(repository).to receive(:file_on_head)
+ .with(:avatar)
+ .once
+ .and_return(double(:tree, path: 'logo.png'))
2.times { expect(repository.avatar).to eq('logo.png') }
end
-
- context 'when feature flag readme_from_gitaly is disabled' do
- before do
- stub_feature_flags(readme_from_gitaly: false)
- end
-
- it 'returns nil if repo does not exist' do
- allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository)
-
- expect(repository.avatar).to be_nil
- end
-
- it 'returns the first avatar file found in the repository' do
- expect(repository).to receive(:file_on_head)
- .with(:avatar)
- .and_return(double(:tree, path: 'logo.png'))
-
- expect(repository.avatar).to eq('logo.png')
- end
-
- it 'caches the output' do
- expect(repository).to receive(:file_on_head)
- .with(:avatar)
- .once
- .and_return(double(:tree, path: 'logo.png'))
-
- 2.times { expect(repository.avatar).to eq('logo.png') }
- end
- end
end
describe '#expire_exists_cache' do
@@ -2732,26 +2708,12 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
it 'caches the response' do
- expect(repository).to receive(:search_files_by_regexp).and_call_original.once
+ expect(repository.head_tree).to receive(:readme_path).and_call_original.once
2.times do
expect(repository.readme_path).to eq("README.md")
end
end
-
- context 'when "readme_from_gitaly" FF is disabled' do
- before do
- stub_feature_flags(readme_from_gitaly: false)
- end
-
- it 'caches the response' do
- expect(repository.head_tree).to receive(:readme_path).and_call_original.once
-
- 2.times do
- expect(repository.readme_path).to eq("README.md")
- end
- end
- end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 6a2d6feafcd..ad5d8588457 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1065,15 +1065,225 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==
-"@esbuild/android-arm@0.15.18":
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80"
- integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==
-
-"@esbuild/linux-loong64@0.15.18":
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239"
- integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==
+"@esbuild/android-arm64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
+ integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==
+
+"@esbuild/android-arm64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.4.tgz#0a900a7e448cc038ae5a751255257fc67163ed32"
+ integrity sha512-91VwDrl4EpxBCiG6h2LZZEkuNvVZYJkv2T9gyLG/mhGG1qrM7i5SwUcg/hlSPnL/4hDT0TFcF35/XMGSn0bemg==
+
+"@esbuild/android-arm@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2"
+ integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==
+
+"@esbuild/android-arm@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.4.tgz#fe32ce82eb6064d3dc13c0d8ca0e440bbc776c93"
+ integrity sha512-R9GCe2xl2XDSc2XbQB63mFiFXHIVkOP+ltIxICKXqUPrFX97z6Z7vONCLQM1pSOLGqfLrGi3B7nbhxmFY/fomg==
+
+"@esbuild/android-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e"
+ integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==
+
+"@esbuild/android-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.4.tgz#6ae1056f6ecf1963c1d076cf5f0109b52d8049f6"
+ integrity sha512-mGSqhEPL7029XL7QHNPxPs15JVa02hvZvysUcyMP9UXdGFwncl2WU0bqx+Ysgzd+WAbv8rfNa73QveOxAnAM2w==
+
+"@esbuild/darwin-arm64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220"
+ integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==
+
+"@esbuild/darwin-arm64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.4.tgz#5064d81ee5b8d646a5b7cc3e53c98cb983c4af55"
+ integrity sha512-tTyJRM9dHvlMPt1KrBFVB5OW1kXOsRNvAPtbzoKazd5RhD5/wKlXk1qR2MpaZRYwf4WDMadt0Pv0GwxB41CVow==
+
+"@esbuild/darwin-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4"
+ integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==
+
+"@esbuild/darwin-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.4.tgz#67f0213b3333248b32a97a7fc3fee880c2157674"
+ integrity sha512-phQuC2Imrb3TjOJwLN8EO50nb2FHe8Ew0OwgZDH1SV6asIPGudnwTQtighDF2EAYlXChLoMJwqjAp4vAaACq6w==
+
+"@esbuild/freebsd-arm64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27"
+ integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==
+
+"@esbuild/freebsd-arm64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.4.tgz#8eaaa126d9ff24822c730f06a71ac2d1091dc1c2"
+ integrity sha512-oH6JUZkocgmjzzYaP5juERLpJQSwazdjZrTPgLRmAU2bzJ688x0vfMB/WTv4r58RiecdHvXOPC46VtsMy/mepg==
+
+"@esbuild/freebsd-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72"
+ integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==
+
+"@esbuild/freebsd-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.4.tgz#314eff900a71abf64d4e5bea31e430d8ebd78d79"
+ integrity sha512-U4iWGn/9TrAfpAdfd56eO0pRxIgb0a8Wj9jClrhT8hvZnOnS4dfMPW7o4fn15D/KqoiVYHRm43jjBaTt3g/2KA==
+
+"@esbuild/linux-arm64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca"
+ integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==
+
+"@esbuild/linux-arm64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.4.tgz#5bed6bb8eb1d331644f8b31c87b8df57f204e84e"
+ integrity sha512-UkGfQvYlwOaeYJzZG4cLV0hCASzQZnKNktRXUo3/BMZvdau40AOz9GzmGA063n1piq6VrFFh43apRDQx8hMP2w==
+
+"@esbuild/linux-arm@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196"
+ integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==
+
+"@esbuild/linux-arm@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.4.tgz#6eaa41f37e231d113da715a1d9cc820e5523aeb6"
+ integrity sha512-S2s9xWTGMTa/fG5EyMGDeL0wrWVgOSQcNddJWgu6rG1NCSXJHs76ZP9AsxjB3f2nZow9fWOyApklIgiTGZKhiw==
+
+"@esbuild/linux-ia32@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54"
+ integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==
+
+"@esbuild/linux-ia32@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.4.tgz#3fc352bb54e0959fda273cd2253b1c72ca41b8c2"
+ integrity sha512-3lqFi4VFo/Vwvn77FZXeLd0ctolIJH/uXkH3yNgEk89Eh6D3XXAC9/iTPEzeEpsNE5IqGIsFa5Z0iPeOh25IyA==
+
+"@esbuild/linux-loong64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8"
+ integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==
+
+"@esbuild/linux-loong64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.4.tgz#86d54f690be53669cd2a38a5333ecf2608c11189"
+ integrity sha512-HqpWZkVslDHIwdQ9D+gk7NuAulgQvRxF9no54ut/M55KEb3mi7sQS3GwpPJzSyzzP0UkjQVN7/tbk88/CaX4EQ==
+
+"@esbuild/linux-mips64el@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726"
+ integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==
+
+"@esbuild/linux-mips64el@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.4.tgz#3dbd897bd8f047fef35e69bd253b8f07ca7fe483"
+ integrity sha512-d/nMCKKh/SVDbqR9ju+b78vOr0tNXtfBjcp5vfHONCCOAL9ad8gN9dC/u+UnH939pz7wO+0u/x9y1MaZcb/lKA==
+
+"@esbuild/linux-ppc64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8"
+ integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==
+
+"@esbuild/linux-ppc64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.4.tgz#defaff6db9a60f08936fc0c59e0eabfb1055968a"
+ integrity sha512-lOD9p2dmjZcNiTU+sGe9Nn6G3aYw3k0HBJies1PU0j5IGfp6tdKOQ6mzfACRFCqXjnBuTqK7eTYpwx09O5LLfg==
+
+"@esbuild/linux-riscv64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9"
+ integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==
+
+"@esbuild/linux-riscv64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.4.tgz#270a09f6f4205a8a8c8ed3c7dbabdcebaafa8a84"
+ integrity sha512-mTGnwWwVshAjGsd8rP+K6583cPDgxOunsqqldEYij7T5/ysluMHKqUIT4TJHfrDFadUwrghAL6QjER4FeqQXoA==
+
+"@esbuild/linux-s390x@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87"
+ integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==
+
+"@esbuild/linux-s390x@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.4.tgz#197695bece68f514dcdcc286562b5d48c5dad5f9"
+ integrity sha512-AQYuUGp50XM29/N/dehADxvc2bUqDcoqrVuijop1Wv72SyxT6dDB9wjUxuPZm2HwIM876UoNNBMVd+iX/UTKVQ==
+
+"@esbuild/linux-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f"
+ integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==
+
+"@esbuild/linux-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.4.tgz#db50cdfb071c0d367025c1c98563aab1318f800e"
+ integrity sha512-+AsFBwKgQuhV2shfGgA9YloxLDVjXgUEWZum7glR5lLmV94IThu/u2JZGxTgjYby6kyXEx8lKOqP5rTEVBR0Rw==
+
+"@esbuild/netbsd-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775"
+ integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==
+
+"@esbuild/netbsd-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.4.tgz#e4d5d8022f8eddbd7d9899d58265915444f46f3b"
+ integrity sha512-zD1TKYX9553OiLS/qkXPMlWoELYkH/VkzRYNKEU+GwFiqkq0SuxsKnsCg5UCdxN3cqd+1KZ8SS3R+WG/Hxy2jQ==
+
+"@esbuild/openbsd-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35"
+ integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==
+
+"@esbuild/openbsd-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.4.tgz#9b770e1e7745824cbe155f5a742fc781855a7e68"
+ integrity sha512-PY1NjEsLRhPEFFg1AV0/4Or/gR+q2dOb9s5rXcPuCjyHRzbt8vnHJl3vYj+641TgWZzTFmSUnZbzs1zwTzjeqw==
+
+"@esbuild/sunos-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c"
+ integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==
+
+"@esbuild/sunos-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.4.tgz#4c6d2290f8bf39ab9284f5a1b9a2210858e2d6e6"
+ integrity sha512-B3Z7s8QZQW9tKGleMRXvVmwwLPAUoDCHs4WZ2ElVMWiortLJFowU1NjAhXOKjDgC7o9ByeVcwyOlJ+F2r6ZgmQ==
+
+"@esbuild/win32-arm64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a"
+ integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==
+
+"@esbuild/win32-arm64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.4.tgz#424954b6d598f40e2c5a0d85e3af07147fb41909"
+ integrity sha512-0HCu8R3mY/H5V7N6kdlsJkvrT591bO/oRZy8ztF1dhgNU5xD5tAh5bKByT1UjTGjp/VVBsl1PDQ3L18SfvtnBQ==
+
+"@esbuild/win32-ia32@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09"
+ integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==
+
+"@esbuild/win32-ia32@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.4.tgz#2c94e9c3a82c779d3f07b3fb5c482a2e3fecedb1"
+ integrity sha512-VUjhVDQycse1gLbe06pC/uaA0M+piQXJpdpNdhg8sPmeIZZqu5xPoGWVCmcsOO2gaM2cywuTYTHkXRozo3/Nkg==
+
+"@esbuild/win32-x64@0.16.17":
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
+ integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
+
+"@esbuild/win32-x64@0.17.4":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.4.tgz#9b7760cdc77678bdbc5b582fae2cf3de449df048"
+ integrity sha512-0kLAjs+xN5OjhTt/aUA6t48SfENSCKgGPfExADYTOo/UCn0ivxos9/anUVeSfg+L+2O9xkFxvJXIJfG+Q4sYSg==
"@eslint/eslintrc@^1.4.1":
version "1.4.1"
@@ -5294,145 +5504,73 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-esbuild-android-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz#20a7ae1416c8eaade917fb2453c1259302c637a5"
- integrity sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==
-
-esbuild-android-arm64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz#9cc0ec60581d6ad267568f29cf4895ffdd9f2f04"
- integrity sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==
-
-esbuild-darwin-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz#428e1730ea819d500808f220fbc5207aea6d4410"
- integrity sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==
-
-esbuild-darwin-arm64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz#b6dfc7799115a2917f35970bfbc93ae50256b337"
- integrity sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==
-
-esbuild-freebsd-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz#4e190d9c2d1e67164619ae30a438be87d5eedaf2"
- integrity sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==
-
-esbuild-freebsd-arm64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz#18a4c0344ee23bd5a6d06d18c76e2fd6d3f91635"
- integrity sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==
-
-esbuild-linux-32@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz#9a329731ee079b12262b793fb84eea762e82e0ce"
- integrity sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==
-
-esbuild-linux-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz#532738075397b994467b514e524aeb520c191b6c"
- integrity sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==
-
-esbuild-linux-arm64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz#5372e7993ac2da8f06b2ba313710d722b7a86e5d"
- integrity sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==
-
-esbuild-linux-arm@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz#e734aaf259a2e3d109d4886c9e81ec0f2fd9a9cc"
- integrity sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==
-
-esbuild-linux-mips64le@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz#c0487c14a9371a84eb08fab0e1d7b045a77105eb"
- integrity sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==
-
-esbuild-linux-ppc64le@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz#af048ad94eed0ce32f6d5a873f7abe9115012507"
- integrity sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==
-
-esbuild-linux-riscv64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz#423ed4e5927bd77f842bd566972178f424d455e6"
- integrity sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==
-
-esbuild-linux-s390x@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz#21d21eaa962a183bfb76312e5a01cc5ae48ce8eb"
- integrity sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==
-
-esbuild-loader@^2.20.0:
- version "2.20.0"
- resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.20.0.tgz#28fcff0142fa7bd227512d69f31e9a6e202bb88f"
- integrity sha512-dr+j8O4w5RvqZ7I4PPB4EIyVTd679EBQnMm+JBB7av+vu05Zpje2IpK5N3ld1VWa+WxrInIbNFAg093+E1aRsA==
- dependencies:
- esbuild "^0.15.6"
+esbuild-loader@^2.21.0:
+ version "2.21.0"
+ resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.21.0.tgz#2698a3e565b0db2bb19a3dd91c2b6c9aad526c80"
+ integrity sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g==
+ dependencies:
+ esbuild "^0.16.17"
joycon "^3.0.1"
json5 "^2.2.0"
loader-utils "^2.0.0"
tapable "^2.2.0"
- webpack-sources "^2.2.0"
-
-esbuild-netbsd-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz#ae75682f60d08560b1fe9482bfe0173e5110b998"
- integrity sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==
-
-esbuild-openbsd-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz#79591a90aa3b03e4863f93beec0d2bab2853d0a8"
- integrity sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==
-
-esbuild-sunos-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz#fd528aa5da5374b7e1e93d36ef9b07c3dfed2971"
- integrity sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==
-
-esbuild-windows-32@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz#0e92b66ecdf5435a76813c4bc5ccda0696f4efc3"
- integrity sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==
-
-esbuild-windows-64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz#0fc761d785414284fc408e7914226d33f82420d0"
- integrity sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==
-
-esbuild-windows-arm64@0.15.18:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz#5b5bdc56d341d0922ee94965c89ee120a6a86eb7"
- integrity sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==
-
-esbuild@0.15.18, esbuild@^0.15.6:
- version "0.15.18"
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.18.tgz#ea894adaf3fbc036d32320a00d4d6e4978a2f36d"
- integrity sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==
+ webpack-sources "^1.4.3"
+
+esbuild@0.17.4:
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.4.tgz#af4f8f78604c67f8e6afbdee36a3f4211ecfc859"
+ integrity sha512-zBn9MeCwT7W5F1a3lXClD61ip6vQM+H8Msb0w8zMT4ZKBpDg+rFAraNyWCDelB/2L6M3g6AXHPnsyvjMFnxtFw==
+ optionalDependencies:
+ "@esbuild/android-arm" "0.17.4"
+ "@esbuild/android-arm64" "0.17.4"
+ "@esbuild/android-x64" "0.17.4"
+ "@esbuild/darwin-arm64" "0.17.4"
+ "@esbuild/darwin-x64" "0.17.4"
+ "@esbuild/freebsd-arm64" "0.17.4"
+ "@esbuild/freebsd-x64" "0.17.4"
+ "@esbuild/linux-arm" "0.17.4"
+ "@esbuild/linux-arm64" "0.17.4"
+ "@esbuild/linux-ia32" "0.17.4"
+ "@esbuild/linux-loong64" "0.17.4"
+ "@esbuild/linux-mips64el" "0.17.4"
+ "@esbuild/linux-ppc64" "0.17.4"
+ "@esbuild/linux-riscv64" "0.17.4"
+ "@esbuild/linux-s390x" "0.17.4"
+ "@esbuild/linux-x64" "0.17.4"
+ "@esbuild/netbsd-x64" "0.17.4"
+ "@esbuild/openbsd-x64" "0.17.4"
+ "@esbuild/sunos-x64" "0.17.4"
+ "@esbuild/win32-arm64" "0.17.4"
+ "@esbuild/win32-ia32" "0.17.4"
+ "@esbuild/win32-x64" "0.17.4"
+
+esbuild@^0.16.17:
+ version "0.16.17"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
+ integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==
optionalDependencies:
- "@esbuild/android-arm" "0.15.18"
- "@esbuild/linux-loong64" "0.15.18"
- esbuild-android-64 "0.15.18"
- esbuild-android-arm64 "0.15.18"
- esbuild-darwin-64 "0.15.18"
- esbuild-darwin-arm64 "0.15.18"
- esbuild-freebsd-64 "0.15.18"
- esbuild-freebsd-arm64 "0.15.18"
- esbuild-linux-32 "0.15.18"
- esbuild-linux-64 "0.15.18"
- esbuild-linux-arm "0.15.18"
- esbuild-linux-arm64 "0.15.18"
- esbuild-linux-mips64le "0.15.18"
- esbuild-linux-ppc64le "0.15.18"
- esbuild-linux-riscv64 "0.15.18"
- esbuild-linux-s390x "0.15.18"
- esbuild-netbsd-64 "0.15.18"
- esbuild-openbsd-64 "0.15.18"
- esbuild-sunos-64 "0.15.18"
- esbuild-windows-32 "0.15.18"
- esbuild-windows-64 "0.15.18"
- esbuild-windows-arm64 "0.15.18"
+ "@esbuild/android-arm" "0.16.17"
+ "@esbuild/android-arm64" "0.16.17"
+ "@esbuild/android-x64" "0.16.17"
+ "@esbuild/darwin-arm64" "0.16.17"
+ "@esbuild/darwin-x64" "0.16.17"
+ "@esbuild/freebsd-arm64" "0.16.17"
+ "@esbuild/freebsd-x64" "0.16.17"
+ "@esbuild/linux-arm" "0.16.17"
+ "@esbuild/linux-arm64" "0.16.17"
+ "@esbuild/linux-ia32" "0.16.17"
+ "@esbuild/linux-loong64" "0.16.17"
+ "@esbuild/linux-mips64el" "0.16.17"
+ "@esbuild/linux-ppc64" "0.16.17"
+ "@esbuild/linux-riscv64" "0.16.17"
+ "@esbuild/linux-s390x" "0.16.17"
+ "@esbuild/linux-x64" "0.16.17"
+ "@esbuild/netbsd-x64" "0.16.17"
+ "@esbuild/openbsd-x64" "0.16.17"
+ "@esbuild/sunos-x64" "0.16.17"
+ "@esbuild/win32-arm64" "0.16.17"
+ "@esbuild/win32-ia32" "0.16.17"
+ "@esbuild/win32-x64" "0.16.17"
escalade@^3.1.1:
version "3.1.1"
@@ -11167,7 +11305,7 @@ sortablejs@^1.10.2, sortablejs@^1.9.0:
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
-source-list-map@^2.0.0, source-list-map@^2.0.1:
+source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
@@ -12641,14 +12779,6 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
source-list-map "^2.0.0"
source-map "~0.6.1"
-webpack-sources@^2.2.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd"
- integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==
- dependencies:
- source-list-map "^2.0.1"
- source-map "^0.6.1"
-
webpack-stats-plugin@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.3.1.tgz#1103c39a305a4e6ba15d5078db84bc0b35447417"