Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Pipeline Authoring Issue Implementation.md15
-rw-r--r--.rubocop_todo/layout/hash_alignment.yml10
-rw-r--r--app/assets/javascripts/crm/components/form.vue20
-rw-r--r--app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue7
-rw-r--r--app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql1
-rw-r--r--app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql1
-rw-r--r--app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue27
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue9
-rw-r--r--app/assets/javascripts/ide/components/ide_project_header.vue1
-rw-r--r--app/assets/javascripts/integrations/overrides/components/integration_overrides.vue1
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue16
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js32
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_block.vue42
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue1
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue2
-rw-r--r--app/controllers/admin/applications_controller.rb1
-rw-r--r--app/controllers/projects/pipelines/stages_controller.rb3
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/oauth_access_token.rb1
-rw-r--r--app/serializers/integrations/project_entity.rb1
-rw-r--r--app/views/admin/applications/index.html.haml3
-rw-r--r--doc/ci/pipelines/multi_project_pipelines.md90
-rw-r--r--doc/ci/yaml/includes.md14
-rw-r--r--doc/ci/yaml/index.md26
-rw-r--r--doc/development/fe_guide/index.md2
-rw-r--r--doc/development/feature_flags/index.md15
-rw-r--r--doc/development/features_inside_dot_gitlab.md4
-rw-r--r--doc/development/integrations/secure.md2
-rw-r--r--doc/development/redis/new_redis_instance.md2
-rw-r--r--doc/development/sidekiq/compatibility_across_updates.md2
-rw-r--r--doc/development/testing_guide/contract/consumer_tests.md113
-rw-r--r--doc/development/testing_guide/contract/index.md4
-rw-r--r--doc/gitlab-basics/start-using-git.md2
-rw-r--r--doc/raketasks/spdx.md2
-rw-r--r--doc/security/index.md2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb2
-rw-r--r--qa/qa/support/loglinking.rb16
-rw-r--r--qa/spec/support/loglinking_spec.rb32
-rw-r--r--spec/features/issues/related_issues_spec.rb20
-rw-r--r--spec/frontend/crm/contact_form_wrapper_spec.js9
-rw-r--r--spec/frontend/crm/form_spec.js38
-rw-r--r--spec/frontend/crm/mock_data.js10
-rw-r--r--spec/frontend/crm/organization_form_wrapper_spec.js2
-rw-r--r--spec/frontend/ide/components/ide_project_header_spec.js2
-rw-r--r--spec/frontend/integrations/overrides/components/integration_overrides_spec.js3
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_block_spec.js3
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js23
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/project_avatar_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js1
-rw-r--r--spec/models/ci/pipeline_spec.rb18
-rw-r--r--spec/models/oauth_access_token_spec.rb21
-rw-r--r--spec/requests/admin/integrations_controller_spec.rb1
-rw-r--r--spec/serializers/integrations/project_entity_spec.rb1
-rw-r--r--yarn.lock76
59 files changed, 528 insertions, 288 deletions
diff --git a/.gitlab/issue_templates/Pipeline Authoring Issue Implementation.md b/.gitlab/issue_templates/Pipeline Authoring Issue Implementation.md
index 4544e675256..523a50dfdf8 100644
--- a/.gitlab/issue_templates/Pipeline Authoring Issue Implementation.md
+++ b/.gitlab/issue_templates/Pipeline Authoring Issue Implementation.md
@@ -12,6 +12,21 @@
## Proposal
+## Additional details
+<!--
+_NOTE: If the issue has addressed all of these questions, this separate section can be removed._
+-->
+
+Some relevant technical details, if applicable, such as:
+
+- Does this need a ~"feature flag"?
+- Is there an example response showing the data structure that should be returned (new endpoints only)?
+- What permissions should be used?
+- Is this EE or CE?
+ - [ ] EE
+ - [ ] CE
+- Additional comments:
+
## Implementation Table
<!--
diff --git a/.rubocop_todo/layout/hash_alignment.yml b/.rubocop_todo/layout/hash_alignment.yml
index 49f4a094c52..e93806cd24d 100644
--- a/.rubocop_todo/layout/hash_alignment.yml
+++ b/.rubocop_todo/layout/hash_alignment.yml
@@ -258,16 +258,6 @@ Layout/HashAlignment:
- 'lib/tasks/gitlab/import_export/export.rake'
- 'lib/tasks/gitlab/import_export/import.rake'
- 'lib/tasks/tanuki_emoji.rake'
- - 'qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/13_secure/change_vulnerability_status_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/13_secure/security_reports_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_enforced_sso_git_access_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/1_manage/group/group_saml_non_enforced_sso_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb'
- - 'qa/qa/specs/features/ee/browser_ui/5_package/dependency_proxy_sso_spec.rb'
- - 'qa/qa/support/loglinking.rb'
- - 'qa/spec/support/loglinking_spec.rb'
- 'spec/controllers/concerns/product_analytics_tracking_spec.rb'
- 'spec/controllers/concerns/redis_tracking_spec.rb'
- 'spec/controllers/import/bitbucket_controller_spec.rb'
diff --git a/app/assets/javascripts/crm/components/form.vue b/app/assets/javascripts/crm/components/form.vue
index 72def54aedf..ea6a6892bbd 100644
--- a/app/assets/javascripts/crm/components/form.vue
+++ b/app/assets/javascripts/crm/components/form.vue
@@ -1,5 +1,13 @@
<script>
-import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlButton,
+ GlDrawer,
+ GlFormCheckbox,
+ GlFormGroup,
+ GlFormInput,
+ GlFormSelect,
+} from '@gitlab/ui';
import { get as getPropValueByPath, isEmpty } from 'lodash';
import { produce } from 'immer';
import { MountingPortal } from 'portal-vue';
@@ -26,6 +34,7 @@ export default {
GlAlert,
GlButton,
GlDrawer,
+ GlFormCheckbox,
GlFormGroup,
GlFormInput,
GlFormSelect,
@@ -113,7 +122,9 @@ export default {
const { fields, model } = this;
return fields.some((field) => {
- return field.required && isEmpty(model[field.name]);
+ return (
+ field.required && isEmpty(model[field.name]) && typeof model[field.name] !== 'boolean'
+ );
});
},
variables() {
@@ -216,6 +227,8 @@ export default {
});
},
getFieldLabel(field) {
+ if (field.bool) return null;
+
const optionalSuffix = field.required ? '' : ` ${MSG_OPTIONAL}`;
return field.label + optionalSuffix;
},
@@ -273,6 +286,9 @@ export default {
v-model="model[field.name]"
:options="field.values"
/>
+ <gl-form-checkbox v-else-if="field.bool" :id="field.name" v-model="model[field.name]"
+ ><span class="gl-font-weight-bold">{{ field.label }}</span></gl-form-checkbox
+ >
<gl-form-input v-else :id="field.name" v-bind="field.input" v-model="model[field.name]" />
</gl-form-group>
<span class="gl-float-right">
diff --git a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
index f114ffedfe6..be3b9bd72b8 100644
--- a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
+++ b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue
@@ -74,7 +74,7 @@ export default {
return { groupId: this.groupGraphQLId };
},
fields() {
- return [
+ const fields = [
{ name: 'firstName', label: __('First name'), required: true },
{ name: 'lastName', label: __('Last name'), required: true },
{ name: 'email', label: __('Email'), required: true },
@@ -86,6 +86,11 @@ export default {
},
{ name: 'description', label: __('Description') },
];
+
+ if (this.isEditMode)
+ fields.push({ name: 'active', label: s__('Crm|Active'), required: true, bool: true });
+
+ return fields;
},
organizationSelectValues() {
const values = this.organizations.map((o) => {
diff --git a/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql b/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql
index cef4083b446..fb8ab46534c 100644
--- a/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql
+++ b/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql
@@ -6,6 +6,7 @@ fragment ContactFragment on CustomerRelationsContact {
email
phone
description
+ active
organization {
__typename
id
diff --git a/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql b/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql
index 4adc5742d3a..0f91490dd85 100644
--- a/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql
+++ b/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql
@@ -4,4 +4,5 @@ fragment OrganizationFragment on CustomerRelationsOrganization {
name
defaultRate
description
+ active
}
diff --git a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue
index 38468e1f4e4..5fd0294b0ea 100644
--- a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue
+++ b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue
@@ -52,16 +52,23 @@ export default {
additionalCreateParams() {
return { groupId: this.groupGraphQLId };
},
- },
- fields: [
- { name: 'name', label: __('Name'), required: true },
- {
- name: 'defaultRate',
- label: s__('Crm|Default rate'),
- input: { type: 'number', step: '0.01' },
+ fields() {
+ const fields = [
+ { name: 'name', label: __('Name'), required: true },
+ {
+ name: 'defaultRate',
+ label: s__('Crm|Default rate'),
+ input: { type: 'number', step: '0.01' },
+ },
+ { name: 'description', label: __('Description') },
+ ];
+
+ if (this.isEditMode)
+ fields.push({ name: 'active', label: s__('Crm|Active'), required: true, bool: true });
+
+ return fields;
},
- { name: 'description', label: __('Description') },
- ],
+ },
};
</script>
@@ -73,7 +80,7 @@ export default {
:mutation="mutation"
:additional-create-params="additionalCreateParams"
:existing-id="organizationGraphQLId"
- :fields="$options.fields"
+ :fields="fields"
:title="title"
:success-message="successMessage"
/>
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index ad163a2a615..0e5acd0928b 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -104,12 +104,9 @@ export default {
class="d-inline-flex mb-2"
/>
<gl-button-group class="gl-ml-4 gl-mb-4" data-testid="commit-sha-group">
- <gl-button
- label
- class="gl-font-monospace"
- data-testid="commit-sha-short-id"
- v-text="commit.short_id"
- />
+ <gl-button label class="gl-font-monospace" data-testid="commit-sha-short-id">{{
+ commit.short_id
+ }}</gl-button>
<modal-copy-button
:text="commit.id"
:title="__('Copy commit SHA')"
diff --git a/app/assets/javascripts/ide/components/ide_project_header.vue b/app/assets/javascripts/ide/components/ide_project_header.vue
index 1c25a8e634d..3296dc2060c 100644
--- a/app/assets/javascripts/ide/components/ide_project_header.vue
+++ b/app/assets/javascripts/ide/components/ide_project_header.vue
@@ -18,6 +18,7 @@ export default {
<div class="context-header ide-context-header">
<a :href="project.web_url" :title="s__('IDE|Go to project')" data-testid="go-to-project-link">
<project-avatar
+ :project-id="project.id"
:project-name="project.name"
:project-avatar-url="project.avatar_url"
:size="48"
diff --git a/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
index 1255ed01f6d..a8389e32b40 100644
--- a/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
+++ b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
@@ -125,6 +125,7 @@ export default {
>
<project-avatar
class="gl-mr-3"
+ :project-id="item.id"
:project-avatar-url="item.avatar_url"
:project-name="item.name"
aria-hidden="true"
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index da72cbeb856..4046e1ade82 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -189,25 +189,23 @@ export default {
v-if="hasEnvironment"
:href="environmentLink.link"
data-testid="job-environment-link"
- v-text="environmentLink.name"
- />
+ >{{ environmentLink.name }}</gl-link
+ >
</template>
<template #clusterNameOrLink>
<gl-link
v-if="clusterNameOrLink.path"
:href="clusterNameOrLink.path"
data-testid="job-cluster-link"
- v-text="clusterNameOrLink.name"
- />
+ >{{ clusterNameOrLink.name }}</gl-link
+ >
<template v-else>{{ clusterNameOrLink.name }}</template>
</template>
<template #kubernetesNamespace>{{ kubernetesNamespace }}</template>
<template #deploymentLink>
- <gl-link
- :href="deploymentLink.path"
- data-testid="job-deployment-link"
- v-text="deploymentLink.name"
- />
+ <gl-link :href="deploymentLink.path" data-testid="job-deployment-link">{{
+ deploymentLink.name
+ }}</gl-link>
</template>
</gl-sprintf>
</p>
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 4d838c2dc90..952cb7082fd 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -350,7 +350,7 @@ function linesFromSelection(textArea) {
* @param {Object} textArea - the targeted text area
* @param {Number} selectionStart - start position of original selection
* @param {Number} selectionEnd - end position of original selection
- * @param {Number} startPos - start pos of first line
+ * @param {Number} lineStart - start pos of first line
* @param {Number} firstLineChange - number of characters changed on first line
* @param {Number} totalChanged - total number of characters changed
*/
@@ -358,23 +358,20 @@ function setNewSelectionRange(
textArea,
selectionStart,
selectionEnd,
- startPos,
+ lineStart,
firstLineChange,
totalChanged,
) {
+ let newStart = Math.max(lineStart, selectionStart + firstLineChange);
+ let newEnd = Math.max(lineStart, selectionEnd + totalChanged);
+
if (selectionStart === selectionEnd) {
- textArea.setSelectionRange(
- Math.max(0, selectionStart + firstLineChange),
- Math.max(0, selectionEnd + firstLineChange),
- );
- } else if (selectionStart === startPos) {
- textArea.setSelectionRange(selectionStart, Math.max(0, selectionEnd + totalChanged));
- } else {
- textArea.setSelectionRange(
- Math.max(0, selectionStart + firstLineChange),
- Math.max(0, selectionEnd + totalChanged),
- );
+ newEnd = newStart;
+ } else if (selectionStart === lineStart) {
+ newStart = lineStart;
}
+
+ textArea.setSelectionRange(newStart, newEnd);
}
/**
@@ -390,10 +387,8 @@ function indentLines(textArea) {
textArea.setSelectionRange(startPos, endPos);
lines.forEach((line) => {
- if (line.length > 0) {
- line = INDENT_CHAR.repeat(INDENT_LENGTH) + line;
- totalAdded += INDENT_LENGTH;
- }
+ line = INDENT_CHAR.repeat(INDENT_LENGTH) + line;
+ totalAdded += INDENT_LENGTH;
shiftedLines.push(line);
});
@@ -439,7 +434,8 @@ function outdentLines(textArea) {
const textToInsert = shiftedLines.join('\n');
- insertText(textArea, textToInsert);
+ if (totalRemoved > 0) insertText(textArea, textToInsert);
+
setNewSelectionRange(
textArea,
selectionStart,
diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue
index bb3f6afe3fd..553904765a4 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_block.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue
@@ -166,11 +166,9 @@ export default {
<div class="card card-slim gl-overflow-hidden">
<div
:class="{ 'panel-empty-heading border-bottom-0': !hasBody, 'gl-border-b-0': !isOpen }"
- class="card-header gl-display-flex gl-justify-content-space-between gl-align-items-center gl-bg-gray-10"
+ class="gl-display-flex gl-justify-content-space-between gl-line-height-24 gl-py-3 gl-px-5 gl-bg-gray-10 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
- <h3
- class="card-title h5 position-relative gl-my-0 gl-display-flex gl-align-items-center gl-h-7"
- >
+ <h3 class="card-title h5 gl-my-0 gl-display-flex gl-align-items-center gl-flex-grow-1">
<gl-link
id="user-content-related-issues"
class="anchor position-absolute gl-text-decoration-none"
@@ -189,28 +187,30 @@ export default {
<gl-icon name="question" :size="12" />
</gl-link>
- <div class="gl-display-inline-flex">
- <div class="js-related-issues-header-issue-count gl-display-inline-flex gl-mx-5">
- <span class="gl-display-inline-flex gl-align-items-center">
- <gl-icon :name="issuableTypeIcon" class="gl-mr-2 gl-text-gray-500" />
- {{ badgeLabel }}
- </span>
- </div>
- <gl-button
- v-if="canAdmin"
- data-qa-selector="related_issues_plus_button"
- data-testid="add-button"
- icon="plus"
- :aria-label="addIssuableButtonText"
- :class="qaClass"
- @click="$emit('toggleAddRelatedIssuesForm', $event)"
- />
+ <div class="js-related-issues-header-issue-count gl-display-inline-flex gl-mx-3">
+ <span class="gl-display-inline-flex gl-align-items-center">
+ <gl-icon :name="issuableTypeIcon" class="gl-mr-2 gl-text-gray-500" />
+ {{ badgeLabel }}
+ </span>
</div>
</h3>
<slot name="header-actions"></slot>
- <div class="gl-pl-4 gl-ml-3">
+ <gl-button
+ v-if="canAdmin"
+ size="small"
+ data-qa-selector="related_issues_plus_button"
+ data-testid="related-issues-plus-button"
+ :aria-label="addIssuableButtonText"
+ :class="qaClass"
+ class="gl-ml-3"
+ @click="$emit('toggleAddRelatedIssuesForm', $event)"
+ >
+ <slot name="add-button-text">{{ __('Add') }}</slot>
+ </gl-button>
+ <div class="gl-pl-3 gl-ml-3 gl-border-l-1 gl-border-l-solid gl-border-l-gray-100">
<gl-button
category="tertiary"
+ size="small"
:icon="toggleIcon"
:aria-label="toggleLabel"
data-testid="toggle-links"
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 9f2cf8505d3..7f408485326 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -196,12 +196,9 @@ export default {
</gl-link>
</div>
<gl-button-group class="gl-ml-4 js-commit-sha-group">
- <gl-button
- label
- class="gl-font-monospace"
- data-testid="last-commit-id-label"
- v-text="showCommitId"
- />
+ <gl-button label class="gl-font-monospace" data-testid="last-commit-id-label">{{
+ showCommitId
+ }}</gl-button>
<clipboard-button
:text="commit.sha"
:title="__('Copy commit SHA')"
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar.vue b/app/assets/javascripts/vue_shared/components/project_avatar.vue
index 6ac96d38b9f..f65cc8bf2f3 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar.vue
@@ -1,5 +1,6 @@
<script>
import { GlAvatar } from '@gitlab/ui';
+import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
export default {
@@ -8,9 +9,12 @@ export default {
},
props: {
projectId: {
- type: Number,
+ type: [Number, String],
default: 0,
required: false,
+ validator(value) {
+ return typeof value === 'string' ? isGid(value) : true;
+ },
},
projectName: {
type: String,
@@ -36,6 +40,9 @@ export default {
avatarAlt() {
return this.alt ?? this.projectName;
},
+ entityId() {
+ return isGid(this.projectId) ? getIdFromGraphQLId(this.projectId) : this.projectId;
+ },
},
AVATAR_SHAPE_OPTION_RECT,
};
@@ -44,7 +51,7 @@ export default {
<template>
<gl-avatar
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
- :entity-id="projectId"
+ :entity-id="entityId"
:entity-name="projectName"
:src="projectAvatarUrl"
:alt="avatarAlt"
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
index 19ffbe37ce7..66643ff4026 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue
@@ -53,6 +53,7 @@ export default {
>
<gl-icon v-if="selected" class="js-selected-icon" name="mobile-issue-close" />
<project-avatar
+ :project-id="project.id"
:project-avatar-url="projectAvatarUrl"
:project-name="projectNameWithNamespace"
class="gl-mr-3"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index 7483bef6e23..aa9cd2bbe4b 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -244,7 +244,7 @@ export default {
>
{{ $options.i18n.addChildButtonLabel }}
</gl-button>
- <div class="gl-border-l-1 gl-border-l-solid gl-border-l-gray-50 gl-pl-3 gl-ml-3">
+ <div class="gl-border-l-1 gl-border-l-solid gl-border-l-gray-100 gl-pl-3 gl-ml-3">
<gl-button
category="tertiary"
size="small"
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index a6a21cf3649..b0d7c8cb8f2 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -11,7 +11,6 @@ class Admin::ApplicationsController < Admin::ApplicationController
def index
applications = ApplicationsFinder.new.execute
@applications = Kaminari.paginate_array(applications).page(params[:page])
- @application_counts = OauthAccessToken.distinct_resource_owner_counts(@applications)
end
def show
diff --git a/app/controllers/projects/pipelines/stages_controller.rb b/app/controllers/projects/pipelines/stages_controller.rb
index 0447bbf29e7..c94d468cf2e 100644
--- a/app/controllers/projects/pipelines/stages_controller.rb
+++ b/app/controllers/projects/pipelines/stages_controller.rb
@@ -4,6 +4,7 @@ module Projects
module Pipelines
class StagesController < Projects::Pipelines::ApplicationController
before_action :authorize_update_pipeline!
+ before_action :stage, only: [:play_manual]
urgency :low, [
:play_manual
@@ -26,7 +27,7 @@ module Projects
private
def stage
- @pipeline_stage ||= pipeline.find_stage_by_name!(params[:stage_name])
+ @stage ||= pipeline.stage(params[:stage_name]).presence || render_404
end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4bc8a9dfd54..a94330270e2 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1224,10 +1224,6 @@ module Ci
stages.find_by(name: name)
end
- def find_stage_by_name!(name)
- stages.find_by!(name: name)
- end
-
def full_error_messages
errors ? errors.full_messages.to_sentence : ""
end
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
index 20130f01d44..87e82c04783 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -6,7 +6,6 @@ class OauthAccessToken < Doorkeeper::AccessToken
alias_attribute :user, :resource_owner
- scope :distinct_resource_owner_counts, ->(applications) { where(application: applications).distinct.group(:application_id).count(:resource_owner_id) }
scope :latest_per_application, -> { select('distinct on(application_id) *').order(application_id: :desc, created_at: :desc) }
scope :preload_application, -> { preload(:application) }
diff --git a/app/serializers/integrations/project_entity.rb b/app/serializers/integrations/project_entity.rb
index ee28c7c19c1..c091133eb39 100644
--- a/app/serializers/integrations/project_entity.rb
+++ b/app/serializers/integrations/project_entity.rb
@@ -4,6 +4,7 @@ module Integrations
class ProjectEntity < Grape::Entity
include RequestAwareEntity
+ expose :id
expose :avatar_url
expose :full_name
expose :name
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index 180871e48dd..b603c7e5f49 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -29,8 +29,6 @@
%th
= _('Callback URL')
%th
- = _('Clients')
- %th
= _('Trusted')
%th
= _('Confidential')
@@ -41,7 +39,6 @@
%tr{ id: "application_#{application.id}" }
%td= link_to application.name, admin_application_path(application)
%td= application.redirect_uri
- %td= @application_counts[application.id].to_i
%td= application.trusted? ? _('Yes'): _('No')
%td= application.confidential? ? _('Yes'): _('No')
%td= link_to 'Edit', edit_admin_application_path(application), class: 'gl-button btn btn-link'
diff --git a/doc/ci/pipelines/multi_project_pipelines.md b/doc/ci/pipelines/multi_project_pipelines.md
index 962cc28bf30..512501f72ba 100644
--- a/doc/ci/pipelines/multi_project_pipelines.md
+++ b/doc/ci/pipelines/multi_project_pipelines.md
@@ -230,6 +230,96 @@ In the upstream pipeline:
artifacts: true
```
+#### Pass artifacts to a downstream pipeline
+
+You can pass artifacts to a downstream pipeline by using [`needs:project`](../yaml/index.md#needsproject).
+
+1. In a job in the upstream pipeline, save the artifacts using the [`artifacts`](../yaml/index.md#artifacts) keyword.
+1. Trigger the downstream pipeline with a trigger job:
+
+ ```yaml
+ build_artifacts:
+ stage: build
+ script:
+ - echo "This is a test artifact!" >> artifact.txt
+ artifacts:
+ paths:
+ - artifact.txt
+
+ deploy:
+ stage: deploy
+ trigger: my/downstream_project
+ ```
+
+1. In a job in the downstream pipeline, fetch the artifacts from the upstream pipeline
+ by using `needs:project`. Set `job` to the job in the upstream pipeline to fetch artifacts from,
+ `ref` to the branch, and `artifacts: true`.
+
+ ```yaml
+ test:
+ stage: test
+ script:
+ - cat artifact.txt
+ needs:
+ - project: my/upstream_project
+ job: build_artifacts
+ ref: main
+ artifacts: true
+ ```
+
+#### Pass artifacts to a downstream pipeline from a Merge Request pipeline
+
+When you use `needs:project` to [pass artifacts to a downstream pipeline](#pass-artifacts-to-a-downstream-pipeline),
+the `ref` value is usually a branch name, like `main` or `development`.
+
+For merge request pipelines, the `ref` value is in the form of `refs/merge-requests/<id>/head`,
+where `id` is the merge request ID. You can retrieve this ref with the [`CI_MERGE_REQUEST_REF_PATH`](../variables/predefined_variables.md#predefined-variables-for-merge-request-pipelines)
+CI/CD variable. Do not use a branch name as the `ref` with merge request pipelines,
+because the downstream pipeline attempts to fetch artifacts from the latest branch pipeline.
+
+To fetch the artifacts from the upstream `merge request` pipeline instead of the `branch` pipeline,
+pass this variable to the downstream pipeline using variable inheritance:
+
+1. In a job in the upstream pipeline, save the artifacts using the [`artifacts`](../yaml/index.md#artifacts) keyword.
+1. In the job that triggers the downstream pipeline, pass the `$CI_MERGE_REQUEST_REF_PATH` variable by using
+ [variable inheritance](#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword):
+
+ ```yaml
+ build_artifacts:
+ stage: build
+ script:
+ - echo "This is a test artifact!" >> artifact.txt
+ artifacts:
+ paths:
+ - artifact.txt
+
+ upstream_job:
+ variables:
+ UPSTREAM_REF: $CI_MERGE_REQUEST_REF_PATH
+ trigger:
+ project: my/downstream_project
+ branch: my-branch
+ ```
+
+1. In a job in the downstream pipeline, fetch the artifacts from the upstream pipeline
+ by using `needs:project`. Set the `ref` to the `UPSTREAM_REF` variable, and `job`
+ to the job in the upstream pipeline to fetch artifacts from:
+
+ ```yaml
+ test:
+ stage: test
+ script:
+ - cat artifact.txt
+ needs:
+ - project: my/upstream_project
+ job: build_artifacts
+ ref: UPSTREAM_REF
+ artifacts: true
+ ```
+
+This method works for fetching artifacts from a regular merge request parent pipeline,
+but fetching artifacts from [merge results](merged_results_pipelines.md) pipelines is not supported.
+
#### Use `rules` or `only`/`except` with multi-project pipelines
You can use CI/CD variables or the [`rules`](../yaml/index.md#rulesif) keyword to
diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md
index 838710834ba..5158f80ea64 100644
--- a/doc/ci/yaml/includes.md
+++ b/doc/ci/yaml/includes.md
@@ -289,10 +289,10 @@ smoke-test-job:
In `include` sections in your `.gitlab-ci.yml` file, you can use:
-- [Project variables](../variables/index.md#add-a-cicd-variable-to-a-project)
-- [Group variables](../variables/index.md#add-a-cicd-variable-to-a-group)
-- [Instance variables](../variables/index.md#add-a-cicd-variable-to-an-instance)
-- Project [predefined variables](../variables/predefined_variables.md)
+- [Project variables](../variables/index.md#add-a-cicd-variable-to-a-project).
+- [Group variables](../variables/index.md#add-a-cicd-variable-to-a-group).
+- [Instance variables](../variables/index.md#add-a-cicd-variable-to-an-instance).
+- Project [predefined variables](../variables/predefined_variables.md).
- In GitLab 14.2 and later, the `$CI_COMMIT_REF_NAME` [predefined variable](../variables/predefined_variables.md).
When used in `include`, the `CI_COMMIT_REF_NAME` variable returns the full
@@ -322,7 +322,11 @@ include:
file: '.compliance-gitlab-ci.yml'
```
-For an example of how you can include these predefined variables, and the variables' impact on CI/CD jobs,
+You cannot use variables defined in jobs, or in a global [`variables`](../yaml/index.md#variables)
+section which defines the default variables for all jobs. Includes are evaluated before jobs,
+so these variables cannot be used with `include`.
+
+For an example of how you can include predefined variables, and the variables' impact on CI/CD jobs,
see this [CI/CD variable demo](https://youtu.be/4XR8gw3Pkos).
## Use `rules` with `include`
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 1d1640b9b51..d6fb118f4af 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -171,11 +171,11 @@ Use `include:local` instead of symbolic links.
**Possible inputs**:
-- A full path relative to the root directory (`/`).
+A full path relative to the root directory (`/`):
+
- The YAML file must have the extension `.yml` or `.yaml`.
- You can [use `*` and `**` wildcards in the file path](includes.md#use-includelocal-with-wildcard-file-paths).
-
-CI/CD variables [are supported](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
+- You can use [certain CI/CD variables](includes.md#use-variables-with-include).
**Example of `include:local`**:
@@ -208,10 +208,10 @@ use `include:file`. You can use `include:file` in combination with `include:proj
**Possible inputs**:
-- A full path, relative to the root directory (`/`). The YAML file must have the
- extension `.yml` or `.yaml`.
+A full path, relative to the root directory (`/`):
-CI/CD variables [are supported](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
+- The YAML file must have the extension `.yml` or `.yaml`.
+- You can use [certain CI/CD variables](includes.md#use-variables-with-include).
**Example of `include:file`**:
@@ -268,10 +268,11 @@ Use `include:remote` with a full URL to include a file from a different location
**Possible inputs**:
-- A public URL accessible by an HTTP/HTTPS `GET` request. Authentication with the
- remote URL is not supported. The YAML file must have the extension `.yml` or `.yaml`.
+A public URL accessible by an HTTP/HTTPS `GET` request:
-CI/CD variables [are supported](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
+- Authentication with the remote URL is not supported.
+- The YAML file must have the extension `.yml` or `.yaml`.
+- You can use [certain CI/CD variables](includes.md#use-variables-with-include).
**Example of `include:remote`**:
@@ -296,9 +297,12 @@ Use `include:template` to include [`.gitlab-ci.yml` templates](https://gitlab.co
**Possible inputs**:
-- [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
+A [CI/CD template](../examples/index.md#cicd-templates):
-CI/CD variables [are supported](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
+- Templates are stored in [`lib/gitlab/ci/templates`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
+ Not all templates are designed to be used with `include:template`, so check template
+ comments before using one.
+- You can use [certain CI/CD variables](includes.md#use-variables-with-include).
**Example of `include:template`**:
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 544985d7edc..02086ec5f1b 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -147,7 +147,7 @@ Best practices for [client-side logging](logging.md) for GitLab frontend develop
## [Internationalization (i18n) and Translations](../i18n/externalization.md)
-Frontend internationalization support is described in [this document](../i18n/).
+Frontend internationalization support is described in [this document](../i18n/index.md).
The [externalization part of the guide](../i18n/externalization.md) explains the helpers/methods available.
## [Troubleshooting](troubleshooting.md)
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index a8dc16f2895..d7589681ce6 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -426,6 +426,21 @@ Feature.enabled?(:a_feature, project) && Feature.disabled?(:a_feature_override,
/chatops run feature set --project=gitlab-org/gitlab a_feature_override true
```
+#### Percentage-based actor selection
+
+When using the percentage rollout of actors on multiple feature flags, the actors for each feature flag are selected separately.
+
+For example, the following feature flags are enabled for a certain percentage of actors:
+
+```plaintext
+/chatops run chatops feature set feature-set-1 25 --actors
+/chatops run chatops feature set feature-set-2 25 --actors
+```
+
+If a project A has `:feature-set-1` enabled, there is no guarantee that project A also has `:feature-set-2` enabled.
+
+For more detail, see [This is how percentages work in Flipper](https://www.hackwithpassion.com/this-is-how-percentages-work-in-flipper).
+
#### Use actors for verifying in production
WARNING:
diff --git a/doc/development/features_inside_dot_gitlab.md b/doc/development/features_inside_dot_gitlab.md
index ca7dbd6adde..f30a041931e 100644
--- a/doc/development/features_inside_dot_gitlab.md
+++ b/doc/development/features_inside_dot_gitlab.md
@@ -14,8 +14,8 @@ When implementing new features, please refer to these existing features to avoid
- [Merge request Templates](../user/project/description_templates.md#create-a-merge-request-template): `.gitlab/merge_request_templates/`.
- [GitLab agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/configuration_repository.md#layout): `.gitlab/agents/`.
- [CODEOWNERS](../user/project/code_owners.md#set-up-code-owners): `.gitlab/CODEOWNERS`.
-- [Route Maps](../ci/review_apps/#route-maps): `.gitlab/route-map.yml`.
+- [Route Maps](../ci/review_apps/index.md#route-maps): `.gitlab/route-map.yml`.
- [Customize Auto DevOps Helm Values](../topics/autodevops/customize.md#customize-values-for-helm-chart): `.gitlab/auto-deploy-values.yaml`.
- [Insights](../user/project/insights/index.md#configure-your-insights): `.gitlab/insights.yml`.
- [Service Desk Templates](../user/project/service_desk.md#using-customized-email-templates): `.gitlab/service_desk_templates/`.
-- [Web IDE](../user/project/web_ide/#web-ide-configuration-file): `.gitlab/.gitlab-webide.yml`.
+- [Web IDE](../user/project/web_ide/index.md#web-ide-configuration-file): `.gitlab/.gitlab-webide.yml`.
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index a02fc018969..55e57a3c2ee 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -314,7 +314,7 @@ This documentation gives an overview of the report JSON format,
as well as recommendations and examples to help integrators set its fields.
The format is extensively described in the documentation of
[SAST](../../user/application_security/sast/index.md#reports-json-format),
-[DAST](../../user/application_security/dast/#reports),
+[DAST](../../user/application_security/dast/index.md#reports),
[Dependency Scanning](../../user/application_security/dependency_scanning/index.md#reports-json-format),
and [Container Scanning](../../user/application_security/container_scanning/index.md#reports-json-format)
diff --git a/doc/development/redis/new_redis_instance.md b/doc/development/redis/new_redis_instance.md
index 8731cc469ce..efaf1e5a6d0 100644
--- a/doc/development/redis/new_redis_instance.md
+++ b/doc/development/redis/new_redis_instance.md
@@ -169,7 +169,7 @@ MultiStore uses two feature flags to control the actual migration:
- `use_primary_and_secondary_stores_for_[store_name]`
- `use_primary_store_as_default_for_[store_name]`
-For example, if our new Redis instance is called `Gitlab::Redis::Foo`, we can [create](../../../ee/development/feature_flags/#create-a-new-feature-flag) two feature flags by executing:
+For example, if our new Redis instance is called `Gitlab::Redis::Foo`, we can [create](../feature_flags/index.md#create-a-new-feature-flag) two feature flags by executing:
```shell
bin/feature-flag use_primary_and_secondary_stores_for_foo
diff --git a/doc/development/sidekiq/compatibility_across_updates.md b/doc/development/sidekiq/compatibility_across_updates.md
index a9897929596..1d369b5a970 100644
--- a/doc/development/sidekiq/compatibility_across_updates.md
+++ b/doc/development/sidekiq/compatibility_across_updates.md
@@ -28,7 +28,7 @@ others - particularly [latency-sensitive jobs](worker_attributes.md#latency-sens
this will result in a poor user experience.
This only applies to new worker classes when they are first introduced.
-As we recommend [using feature flags](../feature_flags/) as a general
+As we recommend [using feature flags](../feature_flags/index.md) as a general
development process, it's best to control the entire change (including
scheduling of the new Sidekiq worker) with a feature flag.
diff --git a/doc/development/testing_guide/contract/consumer_tests.md b/doc/development/testing_guide/contract/consumer_tests.md
index df7c9ee0abd..46f4f446ad9 100644
--- a/doc/development/testing_guide/contract/consumer_tests.md
+++ b/doc/development/testing_guide/contract/consumer_tests.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Writing consumer tests
-This tutorial guides you through writing a consumer test from scratch. To start, the consumer tests are written using [`jest-pact`](https://github.com/pact-foundation/jest-pact) that builds on top of [`pact-js`](https://github.com/pact-foundation/pact-js). This tutorial shows you how to write a consumer test for the `/discussions.json` endpoint, which is actually `/:namespace_name/:project_name/-/merge_requests/:id/discussions.json`.
+This tutorial guides you through writing a consumer test from scratch. To start, the consumer tests are written using [`jest-pact`](https://github.com/pact-foundation/jest-pact) that builds on top of [`pact-js`](https://github.com/pact-foundation/pact-js). This tutorial shows you how to write a consumer test for the `/discussions.json` REST API endpoint, which is actually `/:namespace_name/:project_name/-/merge_requests/:id/discussions.json`. For an example of a GraphQL consumer test, see [`spec/contracts/consumer/specs/project/pipeline/show.spec.js`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/spec/contracts/consumer/specs/project/pipeline/show.spec.js).
## Create the skeleton
@@ -24,7 +24,7 @@ To learn more about how the contract test directory is structured, see the contr
The Pact consumer test is defined through the `pactWith` function that takes `PactOptions` and the `PactFn`.
```javascript
-const { pactWith } = require('jest-pact');
+import { pactWith } from 'jest-pact';
pactWith(PactOptions, PactFn);
```
@@ -34,7 +34,7 @@ pactWith(PactOptions, PactFn);
`PactOptions` with `jest-pact` introduces [additional options](https://github.com/pact-foundation/jest-pact/blob/dce370c1ab4b7cb5dff12c4b62246dc229c53d0e/README.md#defaults) that build on top of the ones [provided in `pact-js`](https://github.com/pact-foundation/pact-js#constructor). In most cases, you define the `consumer`, `provider`, `log`, and `dir` options for these tests.
```javascript
-const { pactWith } = require('jest-pact');
+import { pactWith } from 'jest-pact';
pactWith(
{
@@ -54,7 +54,7 @@ To learn more about how to name the consumers and providers, see contract testin
The `PactFn` is where your tests are defined. This is where you set up the mock provider and where you can use the standard Jest methods like [`Jest.describe`](https://jestjs.io/docs/api#describename-fn), [`Jest.beforeEach`](https://jestjs.io/docs/api#beforeeachfn-timeout), and [`Jest.it`](https://jestjs.io/docs/api#testname-fn-timeout). For more information, see [https://jestjs.io/docs/api](https://jestjs.io/docs/api).
```javascript
-const { pactWith } = require('jest-pact');
+import { pactWith } from 'jest-pact';
pactWith(
{
@@ -70,7 +70,7 @@ pactWith(
});
- it('return a successful body', () => {
+ it('return a successful body', async () => {
});
});
@@ -92,8 +92,8 @@ For this tutorial, define four attributes for the `Interaction`:
After you define the `Interaction`, add that interaction to the mock provider by calling `addInteraction`.
```javascript
-const { pactWith } = require('jest-pact');
-const { Matchers } = require('@pact-foundation/pact');
+import { pactWith } from 'jest-pact';
+import { Matchers } from '@pact-foundation/pact';
pactWith(
{
@@ -132,7 +132,7 @@ pactWith(
provider.addInteraction(interaction);
});
- it('return a successful body', () => {
+ it('return a successful body', async () => {
});
});
@@ -142,38 +142,36 @@ pactWith(
### Response body `Matchers`
-Notice how we use `Matchers` in the `body` of the expected response. This allows us to be flexible enough to accept different values but still be strict enough to distinguish between valid and invalid values. We must ensure that we have a tight definition that is neither too strict nor too lax. Read more about the [different types of `Matchers`](https://github.com/pact-foundation/pact-js#using-the-v3-matching-rules).
+Notice how we use `Matchers` in the `body` of the expected response. This allows us to be flexible enough to accept different values but still be strict enough to distinguish between valid and invalid values. We must ensure that we have a tight definition that is neither too strict nor too lax. Read more about the [different types of `Matchers`](https://github.com/pact-foundation/pact-js/blob/master/docs/matching.md). We are currently using the V2 matching rules.
## Write the test
After the mock provider is set up, you can write the test. For this test, you make a request and expect a particular response.
-First, set up the client that makes the API request. To do that, create `spec/contracts/consumer/endpoints/project/merge_requests.js` and add the following API request.
+First, set up the client that makes the API request. To do that, create `spec/contracts/consumer/resources/api/project/merge_requests.js` and add the following API request. If the endpoint is a GraphQL, then we create it under `spec/contracts/consumer/resources/graphql` instead.
```javascript
-const axios = require('axios');
-
-exports.getDiscussions = (endpoint) => {
- const url = endpoint.url;
-
- return axios
- .request({
- method: 'GET',
- baseURL: url,
- url: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
- headers: { Accept: '*/*' },
- })
- .then((response) => response.data);
-};
+import axios from 'axios';
+
+export async function getDiscussions(endpoint) {
+ const { url } = endpoint;
+
+ return axios({
+ method: 'GET',
+ baseURL: url,
+ url: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
+ headers: { Accept: '*/*' },
+ })
+}
```
After that's set up, import it to the test file and call it to make the request. Then, you can make the request and define your expectations.
```javascript
-const { pactWith } = require('jest-pact');
-const { Matchers } = require('@pact-foundation/pact');
+import { pactWith } from 'jest-pact';
+import { Matchers } from '@pact-foundation/pact';
-const { getDiscussions } = require('../endpoints/project/merge_requests');
+import { getDiscussions } from '../../../resources/api/project/merge_requests';
pactWith(
{
@@ -211,17 +209,17 @@ pactWith(
};
});
- it('return a successful body', () => {
- return getDiscussions({
+ it('return a successful body', async () => {
+ const discussions = await getDiscussions({
url: provider.mockService.baseUrl,
- }).then((discussions) => {
- expect(discussions).toEqual(Matchers.eachLike({
- id: 'fd73763cbcbf7b29eb8765d969a38f7d735e222a',
- project_id: 6954442,
- ...
- resolved: true
- }));
});
+
+ expect(discussions).toEqual(Matchers.eachLike({
+ id: 'fd73763cbcbf7b29eb8765d969a38f7d735e222a',
+ project_id: 6954442,
+ ...
+ resolved: true
+ }));
});
});
},
@@ -237,7 +235,7 @@ As you may have noticed, the request and response definitions can get large. Thi
Create a file under `spec/contracts/consumer/fixtures/project/merge_request` called `discussions.fixture.js` where you will place the `request` and `response` definitions.
```javascript
-const { Matchers } = require('@pact-foundation/pact');
+import { Matchers } from '@pact-foundation/pact';
const body = Matchers.eachLike({
id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
@@ -254,11 +252,15 @@ const Discussions = {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
- body: body,
+ body,
},
- request: {
+ scenario: {
+ state: 'a merge request with discussions exists',
uponReceiving: 'a request for discussions',
+ },
+
+ request: {
withRequest: {
method: 'GET',
path: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
@@ -275,36 +277,41 @@ exports.Discussions = Discussions;
With all of that moved to the `fixture`, you can simplify the test to the following:
```javascript
-const { pactWith } = require('jest-pact');
+import { pactWith } from 'jest-pact';
+
+import { Discussions } from '../../../fixtures/project/merge_request/discussions.fixture';
+import { getDiscussions } from '../../../resources/api/project/merge_requests';
-const { Discussions } = require('../fixtures/discussions.fixture');
-const { getDiscussions } = require('../endpoints/project/merge_requests');
+const CONSUMER_NAME = 'MergeRequest#show';
+const PROVIDER_NAME = 'Merge Request Discussions Endpoint';
+const CONSUMER_LOG = '../logs/consumer.log';
+const CONTRACT_DIR = '../contracts/project/merge_request/show';
pactWith(
{
- consumer: 'MergeRequest#show',
- provider: 'Merge Request Discussions Endpoint',
- log: '../logs/consumer.log',
- dir: '../contracts/project/merge_request/show',
+ consumer: CONSUMER_NAME,
+ provider: PROVIDER_NAME,
+ log: CONSUMER_LOG,
+ dir: CONTRACT_DIR,
},
(provider) => {
- describe('Merge Request Discussions Endpoint', () => {
+ describe(PROVIDER_NAME, () => {
beforeEach(() => {
const interaction = {
- state: 'a merge request with discussions exists',
+ ...Discussions.scenario,
...Discussions.request,
willRespondWith: Discussions.success,
};
- return provider.addInteraction(interaction);
+ provider.addInteraction(interaction);
});
- it('return a successful body', () => {
- return getDiscussions({
+ it('return a successful body', async () => {
+ const discussions = await getDiscussions({
url: provider.mockService.baseUrl,
- }).then((discussions) => {
- expect(discussions).toEqual(Discussions.body);
});
+
+ expect(discussions).toEqual(Discussions.body);
});
});
},
diff --git a/doc/development/testing_guide/contract/index.md b/doc/development/testing_guide/contract/index.md
index f61f842e167..30a4adaca44 100644
--- a/doc/development/testing_guide/contract/index.md
+++ b/doc/development/testing_guide/contract/index.md
@@ -50,11 +50,11 @@ Having an organized and sensible folder structure for the test suite makes it ea
The consumer tests are grouped according to the different pages in the application. Each file contains various types of requests found in a page. As such, the consumer test files are named using the Rails standards of how pages are referenced. For example, the project pipelines page would be the `Project::Pipeline#index` page so the equivalent consumer test would be located in `consumer/specs/project/pipelines/index.spec.js`.
-When defining the location to output the contract generated by the test, we want to follow the same file structure which would be `contracts/project/pipelines/` for this example. This is the structure in `consumer/endpoints` and `consumer/fixtures` as well.
+When defining the location to output the contract generated by the test, we want to follow the same file structure which would be `contracts/project/pipelines/` for this example. This is the structure in `consumer/resources` and `consumer/fixtures` as well.
#### Provider tests
-The provider tests are grouped similarly to our controllers. Each of these tests contains various tests for an API endpoint. For example, the API endpoint to get a list of pipelines for a project would be located in `provider/pact_helpers/project/pipelines/get_list_project_pipelines_helper.rb`. The provider states are structured the same way.
+The provider tests are grouped similarly to our controllers. Each of these tests contains various tests for an API endpoint. For example, the API endpoint to get a list of pipelines for a project would be located in `provider/pact_helpers/project/pipelines/get_list_project_pipelines_helper.rb`. The provider states are grouped according to the different pages in the application similar to the consumer tests.
### Naming conventions
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 65680227ad0..c6de723246c 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -391,7 +391,7 @@ git checkout <default-branch>
git merge <feature-branch>
```
-In GitLab, you typically use a [merge request](../user/project/merge_requests/) to merge your changes, instead of using the command line.
+In GitLab, you typically use a [merge request](../user/project/merge_requests/index.md) to merge your changes, instead of using the command line.
To create a merge request from a fork to an upstream repository, see the
[forking workflow](../user/project/repository/forking_workflow.md).
diff --git a/doc/raketasks/spdx.md b/doc/raketasks/spdx.md
index 18f058f695e..81da52bc327 100644
--- a/doc/raketasks/spdx.md
+++ b/doc/raketasks/spdx.md
@@ -19,6 +19,6 @@ sudo gitlab-rake gitlab:spdx:import
bundle exec rake gitlab:spdx:import RAILS_ENV=production
```
-To perform this task in the [offline environment](../user/application_security/offline_deployments/#defining-offline-environments),
+To perform this task in the [offline environment](../user/application_security/offline_deployments/index.md#defining-offline-environments),
an outbound connection to [`licenses.json`](https://spdx.org/licenses/licenses.json) should be
allowed.
diff --git a/doc/security/index.md b/doc/security/index.md
index 73ac5028db5..7d5cb9653b1 100644
--- a/doc/security/index.md
+++ b/doc/security/index.md
@@ -30,6 +30,6 @@ type: index
## Securing your GitLab installation
-Consider access control features like [Sign up restrictions](../user/admin_area/settings/sign_up_restrictions.md) and [Authentication options](../topics/authentication/) to harden your GitLab instance and minimize the risk of unwanted user account creation.
+Consider access control features like [Sign up restrictions](../user/admin_area/settings/sign_up_restrictions.md) and [Authentication options](../topics/authentication/index.md) to harden your GitLab instance and minimize the risk of unwanted user account creation.
Self-hosting GitLab customers and administrators are responsible for the security of their underlying hosts, and for keeping GitLab itself up to date. It is important to [regularly patch GitLab](../policy/maintenance.md), patch your operating system and its software, and harden your hosts in accordance with vendor guidance.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f21db5d8f08..a111a33859e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2247,6 +2247,9 @@ msgstr ""
msgid "Add existing confidential %{issuableType}"
msgstr ""
+msgid "Add existing issue"
+msgstr ""
+
msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
msgstr ""
@@ -11220,6 +11223,9 @@ msgstr ""
msgid "Critical vulnerabilities present"
msgstr ""
+msgid "Crm|Active"
+msgstr ""
+
msgid "Crm|Contact"
msgstr ""
diff --git a/package.json b/package.json
index 2eb3a52b0dd..484759d8dea 100644
--- a/package.json
+++ b/package.json
@@ -198,7 +198,7 @@
"devDependencies": {
"@gitlab/eslint-plugin": "15.0.0",
"@gitlab/stylelint-config": "4.1.0",
- "@graphql-eslint/eslint-plugin": "3.10.6",
+ "@graphql-eslint/eslint-plugin": "3.10.7",
"@testing-library/dom": "^7.16.2",
"@vue/test-utils": "1.3.0",
"@vue/vue2-jest": "^27.0.0",
@@ -211,7 +211,7 @@
"cheerio": "^1.0.0-rc.9",
"commander": "^2.20.3",
"custom-jquery-matchers": "^2.1.0",
- "eslint": "8.19.0",
+ "eslint": "8.21.0",
"eslint-import-resolver-jest": "3.0.2",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-no-jquery": "2.7.0",
diff --git a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
index e16f4ba9352..f95f624d59a 100644
--- a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
@@ -33,7 +33,7 @@ module QA
# wait_until required due to feature_caching. Remove along with feature flag removal.
Page::File::Edit.perform do |file|
Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page,
- retry_on_exception: true) do
+ retry_on_exception: true) do
expect(file).to have_element(:editor_toolbar_button)
end
file.remove_content
diff --git a/qa/qa/support/loglinking.rb b/qa/qa/support/loglinking.rb
index caf381912d3..ceddd35da17 100644
--- a/qa/qa/support/loglinking.rb
+++ b/qa/qa/support/loglinking.rb
@@ -8,18 +8,18 @@ module QA
PRODUCTION_ADDRESS = 'https://gitlab.com'
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
SENTRY_ENVIRONMENTS = {
- staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
+ staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
staging_canary: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
- staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
- pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
- canary: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
- production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny'
+ staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
+ pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
+ canary: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
+ production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny'
}.freeze
KIBANA_ENVIRONMENTS = {
- staging: 'https://nonprod-log.gitlab.net/',
+ staging: 'https://nonprod-log.gitlab.net/',
staging_canary: 'https://nonprod-log.gitlab.net/',
- canary: 'https://log.gprd.gitlab.net/',
- production: 'https://log.gprd.gitlab.net/'
+ canary: 'https://log.gprd.gitlab.net/',
+ production: 'https://log.gprd.gitlab.net/'
}.freeze
def self.failure_metadata(correlation_id)
diff --git a/qa/spec/support/loglinking_spec.rb b/qa/spec/support/loglinking_spec.rb
index e02ae45ee93..d7221d242d5 100644
--- a/qa/spec/support/loglinking_spec.rb
+++ b/qa/spec/support/loglinking_spec.rb
@@ -37,14 +37,14 @@ RSpec.describe QA::Support::Loglinking do
describe '.sentry_url' do
let(:url_hash) do
{
- :staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
- :staging_canary => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
- :staging_ref => 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
- :pre => 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
- :canary => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
- :production => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny',
- :foo => nil,
- nil => nil
+ :staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
+ :staging_canary => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
+ :staging_ref => 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
+ :pre => 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
+ :canary => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
+ :production => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny',
+ :foo => nil,
+ nil => nil
}
end
@@ -60,14 +60,14 @@ RSpec.describe QA::Support::Loglinking do
describe '.kibana_url' do
let(:url_hash) do
{
- :staging => 'https://nonprod-log.gitlab.net/',
- :staging_canary => 'https://nonprod-log.gitlab.net/',
- :staging_ref => nil,
- :pre => nil,
- :canary => 'https://log.gprd.gitlab.net/',
- :production => 'https://log.gprd.gitlab.net/',
- :foo => nil,
- nil => nil
+ :staging => 'https://nonprod-log.gitlab.net/',
+ :staging_canary => 'https://nonprod-log.gitlab.net/',
+ :staging_ref => nil,
+ :pre => nil,
+ :canary => 'https://log.gprd.gitlab.net/',
+ :production => 'https://log.gprd.gitlab.net/',
+ :foo => nil,
+ nil => nil
}
end
diff --git a/spec/features/issues/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb
index a95229d4f1b..818e99f2ec9 100644
--- a/spec/features/issues/related_issues_spec.rb
+++ b/spec/features/issues/related_issues_spec.rb
@@ -232,7 +232,9 @@ RSpec.describe 'Related issues', :js do
it 'add related issue' do
click_button 'Add a related issue'
fill_in 'Paste issue link', with: "#{issue_b.to_reference(project)} "
- click_button 'Add'
+ page.within('.linked-issues-card-body') do
+ click_button 'Add'
+ end
wait_for_requests
@@ -249,7 +251,9 @@ RSpec.describe 'Related issues', :js do
it 'add cross-project related issue' do
click_button 'Add a related issue'
fill_in 'Paste issue link', with: "#{issue_project_b_a.to_reference(project)} "
- click_button 'Add'
+ page.within('.linked-issues-card-body') do
+ click_button 'Add'
+ end
wait_for_requests
@@ -359,7 +363,9 @@ RSpec.describe 'Related issues', :js do
it 'add related issue' do
click_button 'Add a related issue'
fill_in 'Paste issue link', with: "##{issue_d.iid} "
- click_button 'Add'
+ page.within('.linked-issues-card-body') do
+ click_button 'Add'
+ end
wait_for_requests
@@ -375,7 +381,9 @@ RSpec.describe 'Related issues', :js do
it 'add invalid related issue' do
click_button 'Add a related issue'
fill_in 'Paste issue link', with: '#9999999 '
- click_button 'Add'
+ page.within('.linked-issues-card-body') do
+ click_button 'Add'
+ end
wait_for_requests
@@ -390,7 +398,9 @@ RSpec.describe 'Related issues', :js do
it 'add unauthorized related issue' do
click_button 'Add a related issue'
fill_in 'Paste issue link', with: "#{issue_project_unauthorized_a.to_reference(project)} "
- click_button 'Add'
+ page.within('.linked-issues-card-body') do
+ click_button 'Add'
+ end
wait_for_requests
diff --git a/spec/frontend/crm/contact_form_wrapper_spec.js b/spec/frontend/crm/contact_form_wrapper_spec.js
index 5e1743701e4..e49b553e4b5 100644
--- a/spec/frontend/crm/contact_form_wrapper_spec.js
+++ b/spec/frontend/crm/contact_form_wrapper_spec.js
@@ -56,8 +56,9 @@ describe('Customer relations contact form wrapper', () => {
${'edit'} | ${'Edit contact'} | ${'Contact has been updated.'} | ${updateContactMutation} | ${contacts[0].id}
${'create'} | ${'New contact'} | ${'Contact has been added.'} | ${createContactMutation} | ${null}
`('in $mode mode', ({ mode, title, successMessage, mutation, existingId }) => {
+ const isEditMode = mode === 'edit';
+
beforeEach(() => {
- const isEditMode = mode === 'edit';
mountComponent({ isEditMode });
return waitForPromises();
@@ -82,7 +83,7 @@ describe('Customer relations contact form wrapper', () => {
});
it('renders correct fields prop', () => {
- expect(findContactForm().props('fields')).toEqual([
+ const fields = [
{ name: 'firstName', label: 'First name', required: true },
{ name: 'lastName', label: 'Last name', required: true },
{ name: 'email', label: 'Email', required: true },
@@ -98,7 +99,9 @@ describe('Customer relations contact form wrapper', () => {
],
},
{ name: 'description', label: 'Description' },
- ]);
+ ];
+ if (isEditMode) fields.push({ name: 'active', label: 'Active', required: true, bool: true });
+ expect(findContactForm().props('fields')).toEqual(fields);
});
it('renders correct title prop', () => {
diff --git a/spec/frontend/crm/form_spec.js b/spec/frontend/crm/form_spec.js
index d39f0795f5f..f0e9150cada 100644
--- a/spec/frontend/crm/form_spec.js
+++ b/spec/frontend/crm/form_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlFormInput, GlFormSelect, GlFormGroup } from '@gitlab/ui';
+import { GlAlert, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormGroup } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
@@ -78,6 +78,7 @@ describe('Reusable form component', () => {
const findSaveButton = () => wrapper.findByTestId('save-button');
const findForm = () => wrapper.find('form');
const findError = () => wrapper.findComponent(GlAlert);
+ const findFormGroup = (at) => wrapper.findAllComponents(GlFormGroup).at(at);
const mountComponent = (propsData) => {
wrapper = shallowMountExtended(Form, {
@@ -92,7 +93,7 @@ describe('Reusable form component', () => {
});
};
- const mountContact = ({ propsData } = {}) => {
+ const mountContact = ({ propsData, extraFields = [] } = {}) => {
mountComponent({
fields: [
{ name: 'firstName', label: 'First name', required: true },
@@ -108,6 +109,7 @@ describe('Reusable form component', () => {
{ key: 'gid://gitlab/CustomerRelations::Organization/2', value: 'ABC Corp' },
],
},
+ ...extraFields,
],
getQuery: {
query: getGroupContactsQuery,
@@ -136,7 +138,8 @@ describe('Reusable form component', () => {
mutation: updateContactMutation,
existingId: 'gid://gitlab/CustomerRelations::Contact/12',
};
- mountContact({ propsData });
+ const extraFields = [{ name: 'active', label: 'Active', required: true, bool: true }];
+ mountContact({ propsData, extraFields });
};
const mountOrganization = ({ propsData } = {}) => {
@@ -285,18 +288,16 @@ describe('Reusable form component', () => {
});
it.each`
- index | id | componentName | value
- ${0} | ${'firstName'} | ${'GlFormInput'} | ${'Marty'}
- ${1} | ${'lastName'} | ${'GlFormInput'} | ${'McFly'}
- ${2} | ${'email'} | ${'GlFormInput'} | ${'example@gitlab.com'}
- ${4} | ${'description'} | ${'GlFormInput'} | ${undefined}
- ${3} | ${'phone'} | ${'GlFormInput'} | ${undefined}
- ${5} | ${'organizationId'} | ${'GlFormSelect'} | ${'gid://gitlab/CustomerRelations::Organization/2'}
+ index | id | component | value
+ ${0} | ${'firstName'} | ${GlFormInput} | ${'Marty'}
+ ${1} | ${'lastName'} | ${GlFormInput} | ${'McFly'}
+ ${2} | ${'email'} | ${GlFormInput} | ${'example@gitlab.com'}
+ ${4} | ${'description'} | ${GlFormInput} | ${undefined}
+ ${3} | ${'phone'} | ${GlFormInput} | ${undefined}
+ ${5} | ${'organizationId'} | ${GlFormSelect} | ${'gid://gitlab/CustomerRelations::Organization/2'}
`(
- 'should render a $componentName for #$id with the value "$value"',
- ({ index, id, componentName, value }) => {
- const component = componentName === 'GlFormInput' ? GlFormInput : GlFormSelect;
- const findFormGroup = (at) => wrapper.findAllComponents(GlFormGroup).at(at);
+ 'should render the correct component for #$id with the value "$value"',
+ ({ index, id, component, value }) => {
const findFormElement = () => findFormGroup(index).find(component);
expect(findFormElement().attributes('id')).toBe(id);
@@ -304,6 +305,14 @@ describe('Reusable form component', () => {
},
);
+ it('should render a checked GlFormCheckbox for #active', () => {
+ const activeCheckboxIndex = 6;
+ const findFormElement = () => findFormGroup(activeCheckboxIndex).find(GlFormCheckbox);
+
+ expect(findFormElement().attributes('id')).toBe('active');
+ expect(findFormElement().attributes('checked')).toBe('true');
+ });
+
it('should include updated values in update mutation', () => {
wrapper.find('#firstName').vm.$emit('input', 'Michael');
wrapper
@@ -314,6 +323,7 @@ describe('Reusable form component', () => {
expect(handler).toHaveBeenCalledWith('updateContact', {
input: {
+ active: true,
description: null,
email: 'example@gitlab.com',
firstName: 'Michael',
diff --git a/spec/frontend/crm/mock_data.js b/spec/frontend/crm/mock_data.js
index 35bc7fb69b4..3f3f076bf39 100644
--- a/spec/frontend/crm/mock_data.js
+++ b/spec/frontend/crm/mock_data.js
@@ -13,6 +13,7 @@ export const getGroupContactsQueryResponse = {
email: 'example@gitlab.com',
phone: null,
description: null,
+ active: true,
organization: {
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/2',
@@ -27,6 +28,7 @@ export const getGroupContactsQueryResponse = {
email: null,
phone: null,
description: null,
+ active: true,
organization: null,
},
{
@@ -37,6 +39,7 @@ export const getGroupContactsQueryResponse = {
email: 'jd@gitlab.com',
phone: '+44 44 4444 4444',
description: 'Vice President',
+ active: true,
organization: null,
},
],
@@ -58,6 +61,7 @@ export const getGroupOrganizationsQueryResponse = {
name: 'Test Inc',
defaultRate: 100,
description: null,
+ active: true,
},
{
__typename: 'CustomerRelationsOrganization',
@@ -65,6 +69,7 @@ export const getGroupOrganizationsQueryResponse = {
name: 'ABC Company',
defaultRate: 110,
description: 'VIP',
+ active: true,
},
{
__typename: 'CustomerRelationsOrganization',
@@ -72,6 +77,7 @@ export const getGroupOrganizationsQueryResponse = {
name: 'GitLab',
defaultRate: 120,
description: null,
+ active: true,
},
],
},
@@ -91,6 +97,7 @@ export const createContactMutationResponse = {
phone: null,
description: null,
organization: null,
+ active: true,
},
errors: [],
},
@@ -119,6 +126,7 @@ export const updateContactMutationResponse = {
phone: null,
description: null,
organization: null,
+ active: true,
},
errors: [],
},
@@ -143,6 +151,7 @@ export const createOrganizationMutationResponse = {
name: 'A',
defaultRate: null,
description: null,
+ active: true,
},
errors: [],
},
@@ -168,6 +177,7 @@ export const updateOrganizationMutationResponse = {
name: 'A',
defaultRate: null,
description: null,
+ active: true,
},
errors: [],
},
diff --git a/spec/frontend/crm/organization_form_wrapper_spec.js b/spec/frontend/crm/organization_form_wrapper_spec.js
index 1a5a7c6ca5d..9f26b9157e6 100644
--- a/spec/frontend/crm/organization_form_wrapper_spec.js
+++ b/spec/frontend/crm/organization_form_wrapper_spec.js
@@ -49,7 +49,7 @@ describe('Customer relations organization form wrapper', () => {
mountComponent({ isEditMode: true });
const organizationForm = findOrganizationForm();
- expect(organizationForm.props('fields')).toHaveLength(3);
+ expect(organizationForm.props('fields')).toHaveLength(4);
expect(organizationForm.props('title')).toBe('Edit organization');
expect(organizationForm.props('successMessage')).toBe('Organization has been updated.');
expect(organizationForm.props('mutation')).toBe(updateOrganizationMutation);
diff --git a/spec/frontend/ide/components/ide_project_header_spec.js b/spec/frontend/ide/components/ide_project_header_spec.js
index fc39651c661..d0636352a3f 100644
--- a/spec/frontend/ide/components/ide_project_header_spec.js
+++ b/spec/frontend/ide/components/ide_project_header_spec.js
@@ -3,6 +3,7 @@ import IDEProjectHeader from '~/ide/components/ide_project_header.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
const mockProject = {
+ id: 1,
name: 'test proj',
avatar_url: 'https://gitlab.com',
path_with_namespace: 'path/with-namespace',
@@ -30,6 +31,7 @@ describe('IDE project header', () => {
it('renders ProjectAvatar with correct props', () => {
expect(findProjectAvatar().props()).toMatchObject({
+ projectId: mockProject.id,
projectName: mockProject.name,
projectAvatarUrl: mockProject.avatar_url,
});
diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
index 12d5674a705..fd60d7f817f 100644
--- a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
+++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
@@ -15,6 +15,7 @@ import UrlSync from '~/vue_shared/components/url_sync.vue';
const mockOverrides = Array(DEFAULT_PER_PAGE * 3)
.fill(1)
.map((_, index) => ({
+ id: index,
name: `test-proj-${index}`,
avatar_url: `avatar-${index}`,
full_path: `test-proj-${index}`,
@@ -59,6 +60,7 @@ describe('IntegrationOverrides', () => {
const avatar = link.findComponent(ProjectAvatar);
return {
+ id: avatar.props('projectId'),
href: link.attributes('href'),
avatarUrl: avatar.props('projectAvatarUrl'),
avatarName: avatar.props('projectName'),
@@ -109,6 +111,7 @@ describe('IntegrationOverrides', () => {
it('renders overrides as rows in table', () => {
expect(findRowsAsModel()).toEqual(
mockOverrides.map((x) => ({
+ id: x.id,
href: x.full_path,
avatarUrl: x.avatar_url,
avatarName: x.name,
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
index 49dc0de4ec6..b7385eddcc9 100644
--- a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
+++ b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
@@ -18,9 +18,10 @@ import {
describe('RelatedIssuesBlock', () => {
let wrapper;
- const findIssueCountBadgeAddButton = () => wrapper.findByTestId('add-button');
const findToggleButton = () => wrapper.findByTestId('toggle-links');
const findRelatedIssuesBody = () => wrapper.findByTestId('related-issues-body');
+ const findIssueCountBadgeAddButton = () =>
+ wrapper.find('[data-testid="related-issues-plus-button"]');
afterEach(() => {
if (wrapper) {
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index b2932ef3eb9..733d89fe08c 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -346,6 +346,17 @@ describe('init markdown', () => {
},
);
+ it('indents a blank line two spaces to the right', () => {
+ textArea.value = '012\n\n89';
+ textArea.setSelectionRange(4, 4);
+
+ textArea.dispatchEvent(indentEvent);
+
+ expect(textArea.value).toEqual('012\n \n89');
+ expect(textArea.selectionStart).toEqual(6);
+ expect(textArea.selectionEnd).toEqual(6);
+ });
+
it.each`
selectionStart | selectionEnd | expected | expectedSelectionStart | expectedSelectionEnd
${0} | ${0} | ${'234\n 789\n 34'} | ${0} | ${0}
@@ -356,6 +367,7 @@ describe('init markdown', () => {
${14} | ${15} | ${' 234\n 789\n34'} | ${12} | ${13}
${0} | ${15} | ${'234\n789\n34'} | ${0} | ${10}
${3} | ${13} | ${'234\n789\n34'} | ${1} | ${8}
+ ${6} | ${6} | ${' 234\n789\n 34'} | ${6} | ${6}
`(
'outdents the selected lines two spaces to the left',
({
@@ -377,6 +389,17 @@ describe('init markdown', () => {
},
);
+ it('outdent a blank line has no effect', () => {
+ textArea.value = '012\n\n89';
+ textArea.setSelectionRange(4, 4);
+
+ textArea.dispatchEvent(outdentEvent);
+
+ expect(textArea.value).toEqual('012\n\n89');
+ expect(textArea.selectionStart).toEqual(4);
+ expect(textArea.selectionEnd).toEqual(4);
+ });
+
it('does not indent if meta is not set', () => {
const indentNoMetaEvent = new KeyboardEvent('keydown', { key: ']' });
const text = '012\n456\n89';
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
index 79c1b18c9f9..721bdd34a4f 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
@@ -128,7 +128,7 @@ describe('packages_list_row', () => {
findDeleteButton().vm.$emit('click');
await nextTick();
- expect(wrapper.emitted('packageToDelete')).toBeTruthy();
+ expect(wrapper.emitted('packageToDelete')).toHaveLength(1);
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
});
});
diff --git a/spec/frontend/vue_shared/components/project_avatar_spec.js b/spec/frontend/vue_shared/components/project_avatar_spec.js
index 51750808bfd..af828fbca51 100644
--- a/spec/frontend/vue_shared/components/project_avatar_spec.js
+++ b/spec/frontend/vue_shared/components/project_avatar_spec.js
@@ -43,13 +43,39 @@ describe('ProjectAvatar', () => {
});
describe('with `projectId` prop', () => {
- it('renders GlAvatar with specified `entityId` prop', () => {
+ const validatorFunc = ProjectAvatar.props.projectId.validator;
+
+ it('prop validators return true for valid types', () => {
+ expect(validatorFunc(1)).toBe(true);
+ expect(validatorFunc('gid://gitlab/Project/1')).toBe(true);
+ });
+
+ it('prop validators return false for invalid types', () => {
+ expect(validatorFunc('1')).toBe(false);
+ });
+
+ it('renders GlAvatar with `entityId` 0 when `projectId` is not informed', () => {
+ createComponent({ props: { projectId: undefined } });
+
+ const avatar = findGlAvatar();
+ expect(avatar.props('entityId')).toBe(0);
+ });
+
+ it('renders GlAvatar with specified `entityId` when `projectId` is a Number', () => {
const mockProjectId = 1;
createComponent({ props: { projectId: mockProjectId } });
const avatar = findGlAvatar();
expect(avatar.props('entityId')).toBe(mockProjectId);
});
+
+ it('renders GlAvatar with specified `entityId` when `projectId` is a gid String', () => {
+ const mockProjectId = 'gid://gitlab/Project/1';
+ createComponent({ props: { projectId: mockProjectId } });
+
+ const avatar = findGlAvatar();
+ expect(avatar.props('entityId')).toBe(1);
+ });
});
describe('with `projectAvatarUrl` prop', () => {
diff --git a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
index 397ab2254b9..4e0c318c84e 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
@@ -56,6 +56,7 @@ describe('ProjectListItem component', () => {
expect(avatar.exists()).toBe(true);
expect(avatar.props()).toMatchObject({
+ projectId: project.id,
projectAvatarUrl: '',
projectName: project.name_with_namespace,
});
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 2f033559b0c..c0cea29941e 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -4589,24 +4589,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
end
-
- describe '#find_stage_by_name' do
- subject { pipeline.find_stage_by_name!(stage_name) }
-
- context 'when stage exists' do
- it { is_expected.to eq(stage) }
- end
-
- context 'when stage does not exist' do
- let(:stage_name) { 'build' }
-
- it 'raises an ActiveRecord exception' do
- expect do
- subject
- end.to raise_exception(ActiveRecord::RecordNotFound)
- end
- end
- end
end
describe '#full_error_messages' do
diff --git a/spec/models/oauth_access_token_spec.rb b/spec/models/oauth_access_token_spec.rb
index 2b47da1ebe1..2d617e0c7b3 100644
--- a/spec/models/oauth_access_token_spec.rb
+++ b/spec/models/oauth_access_token_spec.rb
@@ -10,27 +10,6 @@ RSpec.describe OauthAccessToken do
let(:token) { create(:oauth_access_token, application_id: app_one.id) }
describe 'scopes' do
- describe '.distinct_resource_owner_counts' do
- let(:tokens) { described_class.all }
-
- before do
- token
- create_list(:oauth_access_token, 2, resource_owner: user, application_id: app_two.id)
- end
-
- it 'returns unique owners' do
- expect(tokens.count).to eq(3)
- expect(tokens.distinct_resource_owner_counts([app_one])).to eq({ app_one.id => 1 })
- expect(tokens.distinct_resource_owner_counts([app_two])).to eq({ app_two.id => 1 })
- expect(tokens.distinct_resource_owner_counts([app_three])).to eq({})
- expect(tokens.distinct_resource_owner_counts([app_one, app_two]))
- .to eq({
- app_one.id => 1,
- app_two.id => 1
- })
- end
- end
-
describe '.latest_per_application' do
let!(:app_two_token1) { create(:oauth_access_token, application: app_two) }
let!(:app_two_token2) { create(:oauth_access_token, application: app_two) }
diff --git a/spec/requests/admin/integrations_controller_spec.rb b/spec/requests/admin/integrations_controller_spec.rb
index cfb40063095..128aada0975 100644
--- a/spec/requests/admin/integrations_controller_spec.rb
+++ b/spec/requests/admin/integrations_controller_spec.rb
@@ -38,6 +38,7 @@ RSpec.describe Admin::IntegrationsController, :enable_admin_mode do
expect(response).to include_pagination_headers
expect(json_response).to contain_exactly(
{
+ 'id' => project.id,
'avatar_url' => project.avatar_url,
'full_name' => project.full_name,
'name' => project.name,
diff --git a/spec/serializers/integrations/project_entity_spec.rb b/spec/serializers/integrations/project_entity_spec.rb
index 1564f7fad63..ac633d1d5c6 100644
--- a/spec/serializers/integrations/project_entity_spec.rb
+++ b/spec/serializers/integrations/project_entity_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe Integrations::ProjectEntity do
it 'contains needed attributes' do
expect(subject).to include(
+ id: project.id,
avatar_url: include('uploads'),
name: project.name,
full_path: project_path(project),
diff --git a/yarn.lock b/yarn.lock
index 3b72a9338a5..8820ef33e9d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1075,10 +1075,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235"
integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g==
-"@graphql-eslint/eslint-plugin@3.10.6":
- version "3.10.6"
- resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.10.6.tgz#4d5748fade6c11d74aeff9a99d6e38d2ed8f6310"
- integrity sha512-rxGSrKVsDHCuZRvP81ElgtCs0sikdhcHqQySiyhir4G+VhiNlPZ7SQJWrXm9JJEAeB0wQ50kabvse5NRk0hqog==
+"@graphql-eslint/eslint-plugin@3.10.7":
+ version "3.10.7"
+ resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.10.7.tgz#9a203e2084371eca933d88b73ce7a6bebbbb9872"
+ integrity sha512-Vp32LMsHTgRNc2q+OrXRNR1i2nlAbVfN0tMTlFHgnzJfnEJDV332cpjiUF9F82IKjNFSde/pF3cuYccu+UUR/g==
dependencies:
"@babel/code-frame" "^7.16.7"
"@graphql-tools/code-file-loader" "^7.2.14"
@@ -1248,15 +1248,20 @@
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==
-"@humanwhocodes/config-array@^0.9.2":
- version "0.9.5"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7"
- integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==
+"@humanwhocodes/config-array@^0.10.4":
+ version "0.10.4"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c"
+ integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==
dependencies:
"@humanwhocodes/object-schema" "^1.2.1"
debug "^4.1.1"
minimatch "^3.0.4"
+"@humanwhocodes/gitignore-to-minimatch@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d"
+ integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==
+
"@humanwhocodes/object-schema@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
@@ -2485,7 +2490,7 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-acorn@^8.0.4, acorn@^8.2.4, acorn@^8.7.1:
+acorn@^8.0.4, acorn@^8.2.4, acorn@^8.8.0:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -5262,13 +5267,14 @@ eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
-eslint@8.19.0:
- version "8.19.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.19.0.tgz#7342a3cbc4fbc5c106a1eefe0fd0b50b6b1a7d28"
- integrity sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==
+eslint@8.21.0:
+ version "8.21.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.21.0.tgz#1940a68d7e0573cef6f50037addee295ff9be9ef"
+ integrity sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==
dependencies:
"@eslint/eslintrc" "^1.3.0"
- "@humanwhocodes/config-array" "^0.9.2"
+ "@humanwhocodes/config-array" "^0.10.4"
+ "@humanwhocodes/gitignore-to-minimatch" "^1.0.2"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
@@ -5278,14 +5284,17 @@ eslint@8.19.0:
eslint-scope "^7.1.1"
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.3.0"
- espree "^9.3.2"
+ espree "^9.3.3"
esquery "^1.4.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
functional-red-black-tree "^1.0.1"
glob-parent "^6.0.1"
globals "^13.15.0"
+ globby "^11.1.0"
+ grapheme-splitter "^1.0.4"
ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
@@ -5303,12 +5312,12 @@ eslint@8.19.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
-espree@^9.0.0, espree@^9.3.2:
- version "9.3.2"
- resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
- integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
+espree@^9.0.0, espree@^9.3.2, espree@^9.3.3:
+ version "9.3.3"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d"
+ integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==
dependencies:
- acorn "^8.7.1"
+ acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
@@ -5678,6 +5687,14 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
find-yarn-workspace-root@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
@@ -6007,6 +6024,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+grapheme-splitter@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
+ integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+
graphlib@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
@@ -7711,6 +7733,13 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
lodash.assign@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@@ -9241,6 +9270,13 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"