Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b37966a868
commit
c35b5c40f4
|
|
@ -124,7 +124,7 @@ workflow:
|
|||
PIPELINE_NAME: 'Scheduled Ruby $RUBY_VERSION $CI_COMMIT_BRANCH branch'
|
||||
# This work around https://gitlab.com/gitlab-org/gitlab/-/issues/332411 which prevents usage of dependency proxy
|
||||
# when pipeline is triggered by a project access token.
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $GITLAB_USER_LOGIN =~ /project_\d+_bot\d*/'
|
||||
- if: '$GITLAB_USER_LOGIN =~ /project_\d+_bot\d*/'
|
||||
variables:
|
||||
<<: [*default-ruby-variables, *default-branch-pipeline-failure-variables]
|
||||
GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ jest predictive:
|
|||
jest predictive as-if-foss:
|
||||
extends:
|
||||
- .jest-base
|
||||
- .frontend:rules:jest:predictive:as-if-foss
|
||||
- .frontend:rules:jest:predictive-as-if-foss
|
||||
- .as-if-foss
|
||||
needs:
|
||||
- "rspec-all frontend_fixture as-if-foss"
|
||||
|
|
|
|||
|
|
@ -406,16 +406,8 @@
|
|||
.frontend-predictive-patterns: &frontend-predictive-patterns
|
||||
- "{,ee/,jh/}{app/assets/javascripts,spec/frontend}/**/*"
|
||||
|
||||
.frontend-patterns-for-as-if-foss: &frontend-patterns-for-as-if-foss
|
||||
- "{package.json,yarn.lock}"
|
||||
- ".browserslistrc"
|
||||
- "babel.config.js"
|
||||
- "jest.config.{base,integration,unit}.js"
|
||||
- ".stylelintrc"
|
||||
- "Dockerfile.assets"
|
||||
- "config/**/*.js"
|
||||
- "vendor/assets/**/*"
|
||||
- "{app/assets,app/components,app/helpers,app/presenters,app/views,locale,public,spec/frontend,storybook,symbol}/**/*"
|
||||
.frontend-predictive-patterns-as-if-foss: &frontend-predictive-patterns-as-if-foss
|
||||
- "{app/assets/javascripts,spec/frontend}/**/*"
|
||||
|
||||
# Frontend view patterns + .qa-patterns
|
||||
.frontend-qa-patterns: &frontend-qa-patterns
|
||||
|
|
@ -961,18 +953,26 @@
|
|||
- <<: *if-default-branch-schedule-nightly # already executed in the 2-hourly schedule
|
||||
when: never
|
||||
- <<: *if-default-branch-refs
|
||||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
- <<: *if-merge-request-labels-run-cs-evaluation
|
||||
- <<: *if-force-ci
|
||||
when: manual
|
||||
# The rest is included to be consistent with .qa:rules:e2e:test-on-gdk
|
||||
# Run tests automatically for MRs that touch QA files
|
||||
- <<: *if-merge-request
|
||||
changes: *gdk-component-patterns
|
||||
# The rest are included to be consistent with .qa:rules:e2e:test-on-gdk
|
||||
changes: *qa-patterns
|
||||
# Otherwise, only run tests after the MR is approved
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
- <<: *if-ruby-branch
|
||||
# We include the job under the matching conditions below, but unlike in .qa:rules:e2e:test-on-gdk we don't need to
|
||||
# set OMNIBUS_GITLAB_BUILD_ON_ALL_OS when testing against GDK
|
||||
- <<: *if-merge-request
|
||||
changes: *gdk-component-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *dependency-patterns
|
||||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
- <<: *if-merge-request-labels-run-cs-evaluation
|
||||
- <<: *if-merge-request
|
||||
changes: *feature-flag-development-config-patterns
|
||||
- <<: *if-merge-request
|
||||
|
|
@ -983,8 +983,6 @@
|
|||
changes: *ci-qa-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *code-qa-patterns
|
||||
- <<: *if-force-ci
|
||||
when: manual
|
||||
|
||||
.build-images:rules:build-assets-image:
|
||||
rules:
|
||||
|
|
@ -1328,26 +1326,19 @@
|
|||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
.frontend:rules:default-frontend-jobs-as-if-foss:
|
||||
.frontend:rules:frontend_fixture-as-if-foss:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *frontend-patterns-for-as-if-foss
|
||||
|
||||
.frontend:rules:frontend_fixture-as-if-foss:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".frontend:rules:default-frontend-jobs-as-if-foss", rules]
|
||||
- <<: *if-merge-request-labels-run-all-jest
|
||||
- <<: *if-merge-request-labels-frontend-and-feature-flag
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *frontend-patterns-for-as-if-foss
|
||||
changes: *frontend-predictive-patterns-as-if-foss
|
||||
|
||||
.frontend:rules:upload-frontend-fixtures:
|
||||
rules:
|
||||
|
|
@ -1425,7 +1416,7 @@
|
|||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
.frontend:rules:jest:predictive:as-if-foss:
|
||||
.frontend:rules:jest:predictive-as-if-foss:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".frontend:rules:predictive-default-rules", rules]
|
||||
|
|
@ -1437,7 +1428,7 @@
|
|||
- <<: *if-fork-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *frontend-patterns-for-as-if-foss
|
||||
changes: *frontend-predictive-patterns-as-if-foss
|
||||
|
||||
.frontend:rules:coverage-frontend:
|
||||
rules:
|
||||
|
|
@ -1742,6 +1733,8 @@
|
|||
- <<: *if-fork-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-cs-evaluation
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
|
||||
.qa:rules:code-suggestions-eval:
|
||||
rules:
|
||||
|
|
@ -2991,7 +2984,6 @@
|
|||
# .build-images:rules:build-qa-image-merge-requests
|
||||
# .build-images:rules:build-assets-image
|
||||
# .frontend:rules:compile-production-assets
|
||||
# .frontend:rules:default-frontend-jobs-as-if-foss
|
||||
# .rails:rules:single-db
|
||||
# .rails:rules:single-db-ci-connection
|
||||
# .rails:rules:single-redis
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -446,7 +446,7 @@ group :development, :test do
|
|||
gem 'spring-commands-rspec', '~> 1.0.4' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
gem 'gitlab-styles', '~> 11.0.0', feature_category: :tooling
|
||||
gem 'haml_lint', '~> 0.53', feature_category: :tooling
|
||||
gem 'haml_lint', '~> 0.57', feature_category: :tooling
|
||||
|
||||
gem 'bundler-audit', '~> 0.9.1', require: false # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@
|
|||
{"name":"guard-compat","version":"1.2.1","platform":"ruby","checksum":"3ad21ab0070107f92edfd82610b5cdc2fb8e368851e72362ada9703443d646fe"},
|
||||
{"name":"guard-rspec","version":"4.7.3","platform":"ruby","checksum":"a47ba03cbd1e3c71e6ae8645cea97e203098a248aede507461a43e906e2f75ca"},
|
||||
{"name":"haml","version":"5.2.2","platform":"ruby","checksum":"6e759246556145642ef832d670fc06f9bd8539159a0e600847a00291dd7aae0c"},
|
||||
{"name":"haml_lint","version":"0.53.0","platform":"ruby","checksum":"223dc1b5abfec7a7bedd2d2c409752e8811a5d53cc71c8ef7be329d05bf91b18"},
|
||||
{"name":"haml_lint","version":"0.57.0","platform":"ruby","checksum":"17620b5f4821c3572487242594ba5d17cf79f8a24df8629db793d53b0c03d37e"},
|
||||
{"name":"hamlit","version":"2.15.0","platform":"java","checksum":"fda165464e59337ab7cda6304a66bfdb607bb7155f25566da19c9ee7b98e03d1"},
|
||||
{"name":"hamlit","version":"2.15.0","platform":"ruby","checksum":"d2e8505362338945fa309c68b2b8be07ebdc181200ec6021223567bf66dac38e"},
|
||||
{"name":"hana","version":"1.3.7","platform":"ruby","checksum":"5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d"},
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ GEM
|
|||
haml (5.2.2)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
haml_lint (0.53.0)
|
||||
haml_lint (0.57.0)
|
||||
haml (>= 5.0)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
|
|
@ -1966,7 +1966,7 @@ DEPENDENCIES
|
|||
grpc (~> 1.60.0)
|
||||
gssapi (~> 1.3.1)
|
||||
guard-rspec
|
||||
haml_lint (~> 0.53)
|
||||
haml_lint (~> 0.57)
|
||||
hamlit (~> 2.15.0)
|
||||
hashie (~> 5.0.0)
|
||||
health_check (~> 3.0)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export function renderGFM(element) {
|
|||
'.js-render-mermaid',
|
||||
'[data-canonical-lang="json"][data-lang-params="table"]',
|
||||
'.gfm-project_member',
|
||||
'.gfm-issue, .gfm-work_item, .gfm-merge_request, .gfm-epic',
|
||||
'.gfm-issue, .gfm-work_item, .gfm-merge_request, .gfm-epic, .gfm-milestone',
|
||||
].map((selector) => Array.from(element.querySelectorAll(selector)));
|
||||
|
||||
syntaxHighlight(highlightEls);
|
||||
|
|
|
|||
|
|
@ -91,12 +91,11 @@ export default {
|
|||
},
|
||||
},
|
||||
i18n: {
|
||||
enableCheckboxLabel: s__('JiraService|Enable Jira issues'),
|
||||
enableCheckboxHelp: s__(
|
||||
'JiraService|Warning: All GitLab users with access to this GitLab project can view all issues from the Jira project you select.',
|
||||
'JiraService|Warning: All users with access to this GitLab project can view all issues from the Jira project you specify.',
|
||||
),
|
||||
projectKeyLabel: s__('JiraService|Jira project key'),
|
||||
projectKeyPlaceholder: s__('JiraService|For example, AB'),
|
||||
projectKeyPlaceholder: s__('JiraService|AB'),
|
||||
requiredFieldFeedback: __('This field is required.'),
|
||||
},
|
||||
};
|
||||
|
|
@ -111,7 +110,7 @@ export default {
|
|||
:disabled="checkboxDisabled"
|
||||
data-testid="jira-issues-enabled-checkbox"
|
||||
>
|
||||
{{ $options.i18n.enableCheckboxLabel }}
|
||||
{{ s__('JiraService|View Jira issues') }}
|
||||
<template #help>
|
||||
{{ $options.i18n.enableCheckboxHelp }}
|
||||
</template>
|
||||
|
|
@ -123,13 +122,19 @@ export default {
|
|||
v-if="multipleProjectKeys && !isIssueCreation"
|
||||
:label="s__('JiraService|Jira project keys')"
|
||||
label-for="service_project_keys"
|
||||
class="gl-max-w-26"
|
||||
:description="
|
||||
s__(
|
||||
'JiraService|Comma-separated list of Jira project keys. Leave blank to include all available keys.',
|
||||
)
|
||||
"
|
||||
data-testid="jira-project-keys"
|
||||
>
|
||||
<gl-form-input
|
||||
id="service_project_keys"
|
||||
v-model="projectKeys"
|
||||
name="service[project_keys]"
|
||||
:placeholder="s__('JiraService|For example, AB,CD')"
|
||||
width="xl"
|
||||
:placeholder="s__('JiraService|AB,CD')"
|
||||
:readonly="isInheriting"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
|
@ -140,7 +145,6 @@ export default {
|
|||
label-for="service_project_key"
|
||||
:invalid-feedback="$options.i18n.requiredFieldFeedback"
|
||||
:state="validProjectKey"
|
||||
class="gl-max-w-26"
|
||||
data-testid="project-key-form-group"
|
||||
>
|
||||
<gl-form-input
|
||||
|
|
@ -148,6 +152,7 @@ export default {
|
|||
v-model="projectKey"
|
||||
name="service[project_key]"
|
||||
data-testid="jira-project-key-field"
|
||||
width="md"
|
||||
:placeholder="$options.i18n.projectKeyPlaceholder"
|
||||
:required="enableJiraIssues"
|
||||
:state="validProjectKey"
|
||||
|
|
@ -168,29 +173,12 @@ export default {
|
|||
</div>
|
||||
|
||||
<template v-if="isIssueCreation">
|
||||
<gl-form-group
|
||||
:label="$options.i18n.projectKeyLabel"
|
||||
label-for="service_project_key"
|
||||
:invalid-feedback="$options.i18n.requiredFieldFeedback"
|
||||
:state="validProjectKey"
|
||||
class="gl-max-w-26"
|
||||
data-testid="project-key-form-group"
|
||||
>
|
||||
<gl-form-input
|
||||
id="service_project_key"
|
||||
v-model="projectKey"
|
||||
name="service[project_key]"
|
||||
data-testid="jira-project-key-field"
|
||||
:placeholder="$options.i18n.projectKeyPlaceholder"
|
||||
:state="validProjectKey"
|
||||
:readonly="isInheriting"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<jira-issue-creation-vulnerabilities
|
||||
:project-key="projectKey"
|
||||
:initial-is-enabled="initialEnableJiraVulnerabilities"
|
||||
:initial-project-key="initialProjectKey"
|
||||
:initial-issue-type-id="initialVulnerabilitiesIssuetype"
|
||||
:is-validated="isValidated"
|
||||
:show-full-feature="showJiraVulnerabilitiesIntegration"
|
||||
class="gl-mt-6"
|
||||
data-testid="jira-for-vulnerabilities"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
<script>
|
||||
import {
|
||||
GlIcon,
|
||||
GlPopover,
|
||||
GlSkeletonLoader,
|
||||
GlProgressBar,
|
||||
GlTooltipDirective,
|
||||
GlBadge,
|
||||
} from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { humanTimeframe, dateInWords } from '~/lib/utils/datetime/date_format_utility';
|
||||
import { parsePikadayDate } from '~/lib/utils/datetime/pikaday_utility';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import {
|
||||
TYPE_MILESTONE,
|
||||
STATUS_UPCOMING,
|
||||
STATUS_ACTIVE,
|
||||
STATUS_EXPIRED,
|
||||
STATUS_CLOSED,
|
||||
issuableStatusText,
|
||||
} from '~/issues/constants';
|
||||
|
||||
import query from '~/issuable/popover/queries/milestone.query.graphql';
|
||||
|
||||
export default {
|
||||
TYPE_MILESTONE,
|
||||
components: {
|
||||
GlIcon,
|
||||
GlBadge,
|
||||
GlPopover,
|
||||
GlProgressBar,
|
||||
GlSkeletonLoader,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
target: {
|
||||
type: HTMLAnchorElement,
|
||||
required: true,
|
||||
},
|
||||
milestoneId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cachedTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
milestone: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$apollo.queries.milestone.loading;
|
||||
},
|
||||
title() {
|
||||
return this.milestone?.title || this.cachedTitle.split('%').slice(-1).pop();
|
||||
},
|
||||
milestoneParentIcon() {
|
||||
return this.milestone?.groupMilestone ? 'group' : 'project';
|
||||
},
|
||||
milestoneParentFullPath() {
|
||||
return this.milestone?.groupMilestone
|
||||
? this.milestone?.group?.fullPath
|
||||
: this.milestone?.project?.fullPath;
|
||||
},
|
||||
status() {
|
||||
const { expired, upcoming, state } = this.milestone;
|
||||
if (state === STATUS_CLOSED) {
|
||||
return {
|
||||
variant: 'danger',
|
||||
text: issuableStatusText[STATUS_CLOSED],
|
||||
};
|
||||
}
|
||||
if (expired) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
text: issuableStatusText[STATUS_EXPIRED],
|
||||
};
|
||||
}
|
||||
if (upcoming) {
|
||||
return {
|
||||
variant: 'muted',
|
||||
text: issuableStatusText[STATUS_UPCOMING],
|
||||
};
|
||||
}
|
||||
return {
|
||||
variant: 'success',
|
||||
text: issuableStatusText[STATUS_ACTIVE],
|
||||
};
|
||||
},
|
||||
milestoneStats() {
|
||||
return this.milestone?.stats || {};
|
||||
},
|
||||
progress() {
|
||||
const { closedIssuesCount = 0, totalIssuesCount = 0 } = this.milestoneStats;
|
||||
if (totalIssuesCount !== 0) {
|
||||
return Math.floor((closedIssuesCount / totalIssuesCount) * 100);
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
showDetails() {
|
||||
return Object.keys(this.milestone).length > 0;
|
||||
},
|
||||
showTimeframe() {
|
||||
return !this.loading && Boolean(this.milestoneTimeframe);
|
||||
},
|
||||
showProgress() {
|
||||
return this.milestoneStats.totalIssuesCount !== 0;
|
||||
},
|
||||
percentageComplete() {
|
||||
return sprintf(__('%{percentage}%% complete'), { percentage: this.progress });
|
||||
},
|
||||
milestoneTimeframe() {
|
||||
const { startDate, dueDate } = this.milestone;
|
||||
const today = new Date();
|
||||
let timeframe = '';
|
||||
if (startDate && dueDate) {
|
||||
timeframe = humanTimeframe(startDate, dueDate);
|
||||
} else if (startDate && !dueDate) {
|
||||
const parsedStartDate = parsePikadayDate(startDate);
|
||||
const startDateInWords = dateInWords(
|
||||
parsedStartDate,
|
||||
true,
|
||||
parsedStartDate.getFullYear === today.getFullYear(),
|
||||
);
|
||||
if (parsedStartDate.getTime() > today.getTime()) {
|
||||
timeframe = sprintf(__('Starts %{startDate}'), { startDate: startDateInWords });
|
||||
} else {
|
||||
timeframe = sprintf(__('Started %{startDate}'), { startDate: startDateInWords });
|
||||
}
|
||||
} else if (!startDate && dueDate) {
|
||||
const parsedDueDate = parsePikadayDate(dueDate);
|
||||
const dueDateInWords = dateInWords(
|
||||
parsedDueDate,
|
||||
true,
|
||||
parsedDueDate.getFullYear === today.getFullYear(),
|
||||
);
|
||||
if (parsedDueDate.getTime() > today.getTime()) {
|
||||
timeframe = sprintf(__('Ends %{dueDate}'), { dueDate: dueDateInWords });
|
||||
} else {
|
||||
timeframe = sprintf(__('Ended %{dueDate}'), { dueDate: dueDateInWords });
|
||||
}
|
||||
}
|
||||
return timeframe;
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
milestone: {
|
||||
query,
|
||||
variables() {
|
||||
return {
|
||||
id: convertToGraphQLId(`Milestone`, this.milestoneId),
|
||||
};
|
||||
},
|
||||
update: (data) => data.milestone,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-popover
|
||||
:target="target"
|
||||
boundary="viewport"
|
||||
placement="top"
|
||||
:css-classes="['gl-min-w-fit-content']"
|
||||
show
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-center gl-gap-2">
|
||||
<gl-badge v-if="!loading && showDetails" :variant="status.variant">{{
|
||||
status.text
|
||||
}}</gl-badge>
|
||||
<span class="gl-text-secondary gl-display-flex" data-testid="milestone-label">
|
||||
<gl-icon name="milestone" class="gl-mr-1" /> {{ __('Milestone') }}
|
||||
</span>
|
||||
<span v-if="showTimeframe" class="gl-text-secondary" data-testid="milestone-timeframe"
|
||||
>· {{ milestoneTimeframe }}</span
|
||||
>
|
||||
</div>
|
||||
<gl-skeleton-loader v-if="loading" :height="15">
|
||||
<rect width="250" height="15" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
<h5 class="gl-my-3 gl-max-w-30">{{ title }}</h5>
|
||||
<div
|
||||
v-if="!loading && showProgress"
|
||||
class="gl-display-flex gl-align-items-center gl-gap-2 gl-mt-2"
|
||||
data-testid="milestone-progress"
|
||||
>
|
||||
<gl-progress-bar :value="progress" variant="primary" class="gl-flex-grow-1 gl-h-3" />
|
||||
<span>{{ percentageComplete }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDetails"
|
||||
class="gl-display-flex gl-align-items-center gl-gap-2 gl-mt-2"
|
||||
data-testid="milestone-path"
|
||||
>
|
||||
<gl-icon :name="milestoneParentIcon" class="gl-mr-1" />
|
||||
<span class="gl-text-secondary">{{ milestoneParentFullPath }}</span>
|
||||
</div>
|
||||
</gl-popover>
|
||||
</template>
|
||||
|
|
@ -3,11 +3,13 @@ import VueApollo from 'vue-apollo';
|
|||
import createDefaultClient from '~/lib/graphql';
|
||||
import IssuePopover from './components/issue_popover.vue';
|
||||
import MRPopover from './components/mr_popover.vue';
|
||||
import MilestonePopover from './components/milestone_popover.vue';
|
||||
|
||||
export const componentsByReferenceTypeMap = {
|
||||
issue: IssuePopover,
|
||||
work_item: IssuePopover,
|
||||
merge_request: MRPopover,
|
||||
milestone: MilestonePopover,
|
||||
};
|
||||
|
||||
let renderFn;
|
||||
|
|
@ -23,8 +25,10 @@ const handleIssuablePopoverMouseOut = ({ target }) => {
|
|||
const popoverMountedAttr = 'data-popover-mounted';
|
||||
|
||||
/**
|
||||
* Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes.
|
||||
* loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover
|
||||
* Adds a Popover component for issuables and work items to the body,
|
||||
* hands over as much data as the target element has in data attributes.
|
||||
* loads based on data-project-path and data-iid more data about an MR
|
||||
* from the API and sets it on the popover
|
||||
*/
|
||||
export const handleIssuablePopoverMount = ({
|
||||
componentsByReferenceType = componentsByReferenceTypeMap,
|
||||
|
|
@ -32,6 +36,8 @@ export const handleIssuablePopoverMount = ({
|
|||
namespacePath,
|
||||
title,
|
||||
iid,
|
||||
milestone,
|
||||
innerText,
|
||||
referenceType,
|
||||
target,
|
||||
}) => {
|
||||
|
|
@ -45,7 +51,8 @@ export const handleIssuablePopoverMount = ({
|
|||
target,
|
||||
namespacePath,
|
||||
iid,
|
||||
cachedTitle: title,
|
||||
milestoneId: milestone,
|
||||
cachedTitle: title || innerText,
|
||||
},
|
||||
apolloProvider,
|
||||
}).$mount();
|
||||
|
|
@ -64,11 +71,14 @@ export default (elements, issuablePopoverMount = handleIssuablePopoverMount) =>
|
|||
const listenerAddedAttr = 'data-popover-listener-added';
|
||||
|
||||
elements.forEach((el) => {
|
||||
const { projectPath, groupPath, iid, referenceType } = el.dataset;
|
||||
const { projectPath, groupPath, iid, referenceType, milestone, project } = el.dataset;
|
||||
const title = el.dataset.mrTitle || el.title;
|
||||
const { innerText } = el;
|
||||
const namespacePath = groupPath || projectPath;
|
||||
const isIssuable = Boolean(namespacePath && title && iid);
|
||||
const isMilestone = Boolean(milestone && project);
|
||||
|
||||
if (!el.getAttribute(listenerAddedAttr) && namespacePath && title && iid && referenceType) {
|
||||
if (!el.getAttribute(listenerAddedAttr) && referenceType && (isIssuable || isMilestone)) {
|
||||
el.addEventListener('mouseenter', ({ target }) => {
|
||||
if (!el.getAttribute(popoverMountedAttr)) {
|
||||
issuablePopoverMount({
|
||||
|
|
@ -76,6 +86,8 @@ export default (elements, issuablePopoverMount = handleIssuablePopoverMount) =>
|
|||
namespacePath,
|
||||
title,
|
||||
iid,
|
||||
milestone,
|
||||
innerText,
|
||||
referenceType,
|
||||
target,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
query milestone($id: MilestoneID!) {
|
||||
milestone(id: $id) {
|
||||
id
|
||||
title
|
||||
expired
|
||||
upcoming
|
||||
startDate
|
||||
dueDate
|
||||
groupMilestone
|
||||
group {
|
||||
id
|
||||
fullPath
|
||||
}
|
||||
projectMilestone
|
||||
project {
|
||||
id
|
||||
fullPath
|
||||
}
|
||||
state
|
||||
stats {
|
||||
closedIssuesCount
|
||||
totalIssuesCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,9 @@ export const STATUS_OPEN = 'opened';
|
|||
export const STATUS_REOPENED = 'reopened';
|
||||
export const STATUS_LOCKED = 'locked';
|
||||
export const STATUS_EMPTY = 'empty';
|
||||
export const STATUS_ACTIVE = 'active';
|
||||
export const STATUS_EXPIRED = 'expired';
|
||||
export const STATUS_UPCOMING = 'upcoming';
|
||||
|
||||
export const TITLE_LENGTH_MAX = 255;
|
||||
|
||||
|
|
@ -27,6 +30,9 @@ export const WORKSPACE_GROUP = 'group';
|
|||
export const WORKSPACE_PROJECT = 'project';
|
||||
|
||||
export const issuableStatusText = {
|
||||
[STATUS_UPCOMING]: __('Upcoming'),
|
||||
[STATUS_ACTIVE]: __('Active'),
|
||||
[STATUS_EXPIRED]: __('Expired'),
|
||||
[STATUS_CLOSED]: __('Closed'),
|
||||
[STATUS_OPEN]: __('Open'),
|
||||
[STATUS_REOPENED]: __('Open'),
|
||||
|
|
|
|||
|
|
@ -152,10 +152,12 @@ export default {
|
|||
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
|
||||
/>
|
||||
|
||||
<gl-sprintf :message="$options.i18n.helpText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="$options.links.NPM_HELP_PATH" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<span class="gl-text-secondary">
|
||||
<gl-sprintf :message="$options.i18n.helpText">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="$options.links.NPM_HELP_PATH" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const PERSISTENT_USER_CALLOUTS = [
|
|||
'.js-namespace-over-storage-users-combined-alert',
|
||||
'.js-joining-a-project-alert',
|
||||
'.js-duo-pro-trial-alert',
|
||||
'.js-duo-chat-ga-alert',
|
||||
];
|
||||
|
||||
const initCallouts = () => {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export default {
|
|||
return {
|
||||
fullPath: this.fullPath,
|
||||
sort: this.sortKey,
|
||||
state: this.state,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
|
|
@ -54,6 +55,13 @@ export default {
|
|||
getStatus(issue) {
|
||||
return issue.state === STATE_CLOSED ? __('Closed') : undefined;
|
||||
},
|
||||
handleClickTab(state) {
|
||||
if (this.state === state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
},
|
||||
handleSort(sortKey) {
|
||||
if (this.sortKey === sortKey) {
|
||||
return;
|
||||
|
|
@ -97,6 +105,7 @@ export default {
|
|||
show-work-item-type-icon
|
||||
:sort-options="$options.sortOptions"
|
||||
:tabs="$options.issuableListTabs"
|
||||
@click-tab="handleClickTab"
|
||||
@dismiss-alert="error = undefined"
|
||||
@sort="handleSort"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#import "ee_else_ce/work_items/list/queries/work_item_widgets.fragment.graphql"
|
||||
|
||||
query getWorkItems($fullPath: ID!, $sort: WorkItemSort) {
|
||||
query getWorkItems($fullPath: ID!, $sort: WorkItemSort, $state: IssuableState) {
|
||||
group(fullPath: $fullPath) {
|
||||
id
|
||||
workItems(sort: $sort) {
|
||||
workItems(sort: $sort, state: $state, types: EPIC) {
|
||||
nodes {
|
||||
id
|
||||
author {
|
||||
|
|
|
|||
|
|
@ -11,45 +11,13 @@ module DependencyProxy
|
|||
|
||||
private
|
||||
|
||||
def auth_user_or_token
|
||||
if defined?(personal_access_token) && personal_access_token && auth_user.is_a?(::User) &&
|
||||
(
|
||||
(auth_user.project_bot? && auth_user.resource_bot_resource.is_a?(::Group)) ||
|
||||
auth_user.human? ||
|
||||
auth_user.service_account?
|
||||
)
|
||||
personal_access_token
|
||||
else
|
||||
auth_user
|
||||
end
|
||||
end
|
||||
|
||||
def verify_dependency_proxy_available!
|
||||
render_404 unless group&.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
# TODO: Split the authorization logic into dedicated methods
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/452145
|
||||
def authorize_read_dependency_proxy!
|
||||
if Feature.enabled?(:packages_dependency_proxy_pass_token_to_policy, group)
|
||||
if auth_user_or_token.is_a?(User)
|
||||
authorize_read_dependency_proxy_for_users!
|
||||
else
|
||||
authorize_read_dependency_proxy_for_tokens!
|
||||
end
|
||||
else
|
||||
authorize_read_dependency_proxy_for_users!
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_read_dependency_proxy_for_users!
|
||||
access_denied! unless can?(auth_user, :read_dependency_proxy, group)
|
||||
end
|
||||
|
||||
def authorize_read_dependency_proxy_for_tokens!
|
||||
access_denied! unless can?(auth_user_or_token, :read_dependency_proxy,
|
||||
group&.dependency_proxy_for_containers_policy_subject)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -19,18 +19,15 @@ module Groups
|
|||
authenticate_with_http_token do |token, _|
|
||||
@authentication_result = EMPTY_AUTH_RESULT
|
||||
|
||||
if Feature.enabled?(:packages_dependency_proxy_pass_token_to_policy, group_from_params)
|
||||
user_or_token = ::DependencyProxy::AuthTokenService.user_or_token_from_jwt(token)
|
||||
sign_in_and_setup_authentication_result(user_or_token)
|
||||
else
|
||||
user_or_token = ::DependencyProxy::AuthTokenService.user_or_deploy_token_from_jwt(token)
|
||||
case user_or_token
|
||||
when User
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_token, nil, :user, [])
|
||||
sign_in(user_or_token) unless user_or_token.project_bot? || user_or_token.service_account?
|
||||
when DeployToken
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_token, nil, :deploy_token, [])
|
||||
end
|
||||
user_or_deploy_token = ::DependencyProxy::AuthTokenService.user_or_deploy_token_from_jwt(token)
|
||||
|
||||
case user_or_deploy_token
|
||||
when User
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_deploy_token, nil, :user, [])
|
||||
sign_in(user_or_deploy_token) unless user_or_deploy_token.project_bot? ||
|
||||
user_or_deploy_token.service_account?
|
||||
when DeployToken
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_deploy_token, nil, :deploy_token, [])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -39,32 +36,11 @@ module Groups
|
|||
|
||||
private
|
||||
|
||||
attr_reader :personal_access_token
|
||||
|
||||
def group_from_params
|
||||
GroupsFinder.new(nil, { search: params[:group_id] }).execute.first
|
||||
end
|
||||
|
||||
def request_bearer_token!
|
||||
# unfortunately, we cannot use https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html#method-i-authentication_request
|
||||
response.headers['WWW-Authenticate'] = ::DependencyProxy::Registry.authenticate_header
|
||||
render plain: '', status: :unauthorized
|
||||
end
|
||||
|
||||
# When we rollout packages_dependency_proxy_pass_token_to_policy,
|
||||
# we can move the body of this method inline, inside authenticate_user_from_jwt_token!
|
||||
def sign_in_and_setup_authentication_result(user_or_token)
|
||||
case user_or_token
|
||||
when User
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_token, nil, :user, [])
|
||||
sign_in(user_or_token)
|
||||
when PersonalAccessToken
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_token.user, nil, :personal_access_token, [])
|
||||
@personal_access_token = user_or_token
|
||||
when DeployToken
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_token, nil, :deploy_token, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,10 +33,9 @@ class JwtController < ApplicationController
|
|||
@authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_only_authentication_abilities)
|
||||
|
||||
authenticate_with_http_basic do |login, password|
|
||||
@raw_token = password
|
||||
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, request: request)
|
||||
|
||||
@raw_token = password if @authentication_result.type == :personal_access_token
|
||||
|
||||
if @authentication_result.failed?
|
||||
log_authentication_failed(login, @authentication_result)
|
||||
render_access_denied
|
||||
|
|
|
|||
|
|
@ -6,24 +6,28 @@ module Mutations
|
|||
graphql_name 'UserPreferencesUpdate'
|
||||
|
||||
NON_NULLABLE_ARGS = [
|
||||
:extensions_marketplace_opt_in_status,
|
||||
:use_web_ide_extension_marketplace,
|
||||
:visibility_pipeline_id_type
|
||||
].freeze
|
||||
|
||||
argument :extensions_marketplace_opt_in_status, Types::ExtensionsMarketplaceOptInStatusEnum,
|
||||
required: false,
|
||||
description: 'Status of the Web IDE Extension Marketplace opt-in for the user.'
|
||||
argument :issues_sort, Types::IssueSortEnum,
|
||||
required: false,
|
||||
description: 'Sort order for issue lists.'
|
||||
required: false,
|
||||
description: 'Sort order for issue lists.'
|
||||
argument :use_web_ide_extension_marketplace, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Whether Web IDE Extension Marketplace is enabled for the user.'
|
||||
required: false,
|
||||
description: 'Whether Web IDE Extension Marketplace is enabled for the user.'
|
||||
argument :visibility_pipeline_id_type, Types::VisibilityPipelineIdTypeEnum,
|
||||
required: false,
|
||||
description: 'Determines whether the pipeline list shows ID or IID.'
|
||||
required: false,
|
||||
description: 'Determines whether the pipeline list shows ID or IID.'
|
||||
|
||||
field :user_preferences,
|
||||
Types::UserPreferencesType,
|
||||
null: true,
|
||||
description: 'User preferences after mutation.'
|
||||
Types::UserPreferencesType,
|
||||
null: true,
|
||||
description: 'User preferences after mutation.'
|
||||
|
||||
def resolve(**attributes)
|
||||
attributes.delete_if { |key, value| NON_NULLABLE_ARGS.include?(key) && value.nil? }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class ExtensionsMarketplaceOptInStatusEnum < BaseEnum
|
||||
graphql_name 'ExtensionsMarketplaceOptInStatus'
|
||||
description 'Values for status of the Web IDE Extension Marketplace opt-in for the user'
|
||||
|
||||
UserPreference.extensions_marketplace_opt_in_statuses.each_key do |field|
|
||||
value field.upcase, value: field, description: "Web IDE Extension Marketplace opt-in status: #{field.upcase}."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,10 @@ module Types
|
|||
class UserPreferencesType < BaseObject
|
||||
graphql_name 'UserPreferences'
|
||||
|
||||
field :extensions_marketplace_opt_in_status, Types::ExtensionsMarketplaceOptInStatusEnum,
|
||||
description: 'Status of the Web IDE Extension Marketplace opt-in for the user.',
|
||||
null: false
|
||||
|
||||
field :issues_sort, Types::IssueSortEnum,
|
||||
description: 'Sort order for issue lists.',
|
||||
null: true
|
||||
|
|
@ -16,7 +20,8 @@ module Types
|
|||
|
||||
field :use_web_ide_extension_marketplace, GraphQL::Types::Boolean,
|
||||
description: 'Whether Web IDE Extension Marketplace is enabled for the user.',
|
||||
null: false
|
||||
null: false,
|
||||
deprecated: { reason: 'Use `extensions_marketplace_opt_in_status` instead', milestone: '16.11' }
|
||||
|
||||
def issues_sort
|
||||
object.issues_sort.to_sym
|
||||
|
|
|
|||
|
|
@ -78,32 +78,18 @@ class ActiveSession
|
|||
timestamp = Time.current
|
||||
expiry = Settings.gitlab['session_expire_delay'] * 60
|
||||
|
||||
active_user_session = if Feature.enabled?(:show_admin_mode_within_active_sessions)
|
||||
new(
|
||||
ip_address: request.remote_ip,
|
||||
browser: client.name,
|
||||
os: client.os_name,
|
||||
device_name: client.device_name,
|
||||
device_type: client.device_type,
|
||||
created_at: user.current_sign_in_at || timestamp,
|
||||
updated_at: timestamp,
|
||||
session_private_id: session_private_id,
|
||||
is_impersonated: request.session[:impersonator_id].present?,
|
||||
admin_mode: Gitlab::Auth::CurrentUserMode.new(user, request.session).admin_mode?
|
||||
)
|
||||
else
|
||||
new(
|
||||
ip_address: request.remote_ip,
|
||||
browser: client.name,
|
||||
os: client.os_name,
|
||||
device_name: client.device_name,
|
||||
device_type: client.device_type,
|
||||
created_at: user.current_sign_in_at || timestamp,
|
||||
updated_at: timestamp,
|
||||
session_private_id: session_private_id,
|
||||
is_impersonated: request.session[:impersonator_id].present?
|
||||
)
|
||||
end
|
||||
active_user_session = new(
|
||||
ip_address: request.remote_ip,
|
||||
browser: client.name,
|
||||
os: client.os_name,
|
||||
device_name: client.device_name,
|
||||
device_type: client.device_type,
|
||||
created_at: user.current_sign_in_at || timestamp,
|
||||
updated_at: timestamp,
|
||||
session_private_id: session_private_id,
|
||||
is_impersonated: request.session[:impersonator_id].present?,
|
||||
admin_mode: Gitlab::Auth::CurrentUserMode.new(user, request.session).admin_mode?
|
||||
)
|
||||
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
redis.pipelined do |pipeline|
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Enums
|
||||
module WebIde
|
||||
module ExtensionsMarketplaceOptInStatus
|
||||
def self.statuses
|
||||
{ unset: 0, enabled: 1, disabled: 2 }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -971,10 +971,6 @@ class Group < Namespace
|
|||
::Packages::Policies::Group.new(self)
|
||||
end
|
||||
|
||||
def dependency_proxy_for_containers_policy_subject
|
||||
::Packages::Policies::DependencyProxy::Group.new(self)
|
||||
end
|
||||
|
||||
def update_two_factor_requirement_for_members
|
||||
hierarchy_members.find_each(&:update_two_factor_requirement)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module Integrations
|
|||
class Jira < BaseIssueTracker
|
||||
include Gitlab::Routing
|
||||
include ApplicationHelper
|
||||
include SafeFormatHelper
|
||||
include ActionView::Helpers::AssetUrlHelper
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include HasAvatar
|
||||
|
|
@ -256,7 +257,7 @@ module Integrations
|
|||
# Currently, Jira issues are only configurable at the project and group levels.
|
||||
unless instance_level?
|
||||
issues_title = if Feature.enabled?(:jira_multiple_project_keys, group || project&.group)
|
||||
s_('JiraService|View Jira issues (optional)')
|
||||
s_('JiraService|Jira issues (optional)')
|
||||
else
|
||||
_('Issues')
|
||||
end
|
||||
|
|
@ -271,9 +272,9 @@ module Integrations
|
|||
if Feature.enabled?(:jira_multiple_project_keys, group || project&.group)
|
||||
sections.push({
|
||||
type: SECTION_TYPE_JIRA_ISSUE_CREATION,
|
||||
title: s_('JiraService|Jira issue creation from vulnerabilities (optional)'),
|
||||
description: s_('JiraService|Create a Jira issue for a vulnerability to track any action taken ' \
|
||||
'to resolve or mitigate a vulnerability.'),
|
||||
title: s_('JiraService|Jira issues for vulnerabilities (optional)'),
|
||||
description: s_('JiraService|Create Jira issues from GitLab to track any action taken ' \
|
||||
'to resolve or mitigate vulnerabilities.'),
|
||||
plan: 'ultimate'
|
||||
})
|
||||
end
|
||||
|
|
@ -745,24 +746,21 @@ module Integrations
|
|||
end
|
||||
|
||||
def jira_issues_section_description
|
||||
jira_issues_link_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe,
|
||||
url: help_page_path('integration/jira/issues'))
|
||||
description = format(
|
||||
s_('JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of ' \
|
||||
'your Jira issues. %{jira_issues_link_start}Learn more.%{link_end}'),
|
||||
jira_issues_link_start: jira_issues_link_start,
|
||||
link_end: '</a>'.html_safe
|
||||
)
|
||||
description = s_('JiraService|View issues from multiple Jira projects in this GitLab project. ' \
|
||||
'Access a read-only list of your Jira issues.')
|
||||
|
||||
if project&.issues_enabled?
|
||||
gitlab_issues_link_start = format('<a href="%{url}">'.html_safe, url: edit_project_path(project,
|
||||
anchor: 'js-shared-permissions'))
|
||||
description += '<br><br>'.html_safe
|
||||
description += format(
|
||||
s_("JiraService|Displaying Jira issues while leaving GitLab issues also enabled might be confusing. " \
|
||||
"Consider %{gitlab_issues_link_start}disabling GitLab issues%{link_end} if they won't otherwise be used."),
|
||||
gitlab_issues_link_start: gitlab_issues_link_start,
|
||||
link_end: '</a>'.html_safe
|
||||
|
||||
gitlab_issues_link = ActionController::Base.helpers.link_to(
|
||||
'',
|
||||
edit_project_path(project, anchor: 'js-shared-permissions')
|
||||
)
|
||||
tag_pair_gitlab_issues = tag_pair(gitlab_issues_link, :link_start, :link_end)
|
||||
description += safe_format(
|
||||
s_('JiraService|If you access Jira issues in GitLab, you might want to ' \
|
||||
'%{link_start}disable GitLab issues%{link_end}.'),
|
||||
tag_pair_gitlab_issues
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# We use this class, in conjunction with the
|
||||
# Group#dependency_proxy_for_containers_policy_subject method,
|
||||
# to specify a custom policy class for DependencyProxy.
|
||||
# A similar pattern was used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90963
|
||||
module Packages
|
||||
module Policies
|
||||
module DependencyProxy
|
||||
class Group
|
||||
attr_reader :group
|
||||
|
||||
delegate :dependency_proxy_feature_available?, :full_path, :licensed_feature_available?,
|
||||
:max_member_access_for_user, :member?, :owned_by?, :public?, :root_ancestor,
|
||||
:root_ancestor_ip_restrictions, to: :group
|
||||
|
||||
def initialize(group)
|
||||
@group = group
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -408,6 +408,7 @@ class User < MainClusterwide::ApplicationRecord
|
|||
:sourcegraph_enabled, :sourcegraph_enabled=,
|
||||
:gitpod_enabled, :gitpod_enabled=,
|
||||
:use_web_ide_extension_marketplace, :use_web_ide_extension_marketplace=,
|
||||
:extensions_marketplace_opt_in_status, :extensions_marketplace_opt_in_status=,
|
||||
:setup_for_company, :setup_for_company=,
|
||||
:project_shortcut_buttons, :project_shortcut_buttons=,
|
||||
:keyboard_shortcuts_enabled, :keyboard_shortcuts_enabled=,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class UserPreference < MainClusterwide::ApplicationRecord
|
|||
attribute :use_web_ide_extension_marketplace, default: false
|
||||
|
||||
enum visibility_pipeline_id_type: { id: 0, iid: 1 }
|
||||
enum extensions_marketplace_opt_in_status: Enums::WebIde::ExtensionsMarketplaceOptInStatus.statuses
|
||||
|
||||
class << self
|
||||
def notes_filters
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ module Users
|
|||
transition_to_jihu_callout: 84,
|
||||
summarize_code_changes: 85, # EE-only
|
||||
duo_pro_trial_alert: 86, # EE-only
|
||||
deployment_details_feedback: 87
|
||||
deployment_details_feedback: 87,
|
||||
duo_chat_ga_alert: 88 # EE-only
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
|
|
|
|||
|
|
@ -462,7 +462,6 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
resource_access_token_create_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
|
||||
end
|
||||
|
||||
# We can remove this when we rollout the feature flag packages_dependency_proxy_pass_token_to_policy
|
||||
def valid_dependency_proxy_deploy_token
|
||||
@user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# The policies defined in GroupPolicy is used in GraphQL requests
|
||||
# With a GraphQL request, the user is always a human User
|
||||
#
|
||||
# With JWT requests, we can be dealing with any of the following:
|
||||
# - a PrAT for a human
|
||||
# - a PrAT for a service account
|
||||
# - a GrAT
|
||||
# - a Group DeployToken
|
||||
#
|
||||
# We use this custom policy class for JWT requests
|
||||
module Packages
|
||||
module Policies
|
||||
module DependencyProxy
|
||||
class GroupPolicy < ::GroupPolicy
|
||||
overrides(:read_dependency_proxy)
|
||||
|
||||
desc "Deploy token with read access to dependency proxy"
|
||||
condition(:read_dependency_proxy_deploy_token) do
|
||||
@user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject.group)
|
||||
end
|
||||
|
||||
desc "Personal access or group access token with read access to dependency proxy"
|
||||
condition(:read_dependency_proxy_personal_access_token) do
|
||||
user_is_personal_access_token? &&
|
||||
(
|
||||
user.user.human? ||
|
||||
user.user.service_account? ||
|
||||
(user.user.project_bot? && user.user.resource_bot_resource.is_a?(::Group))
|
||||
) &&
|
||||
(access_level(for_any_session: true) >= GroupMember::GUEST)
|
||||
end
|
||||
|
||||
condition(:dependency_proxy_disabled, scope: :subject) do
|
||||
!@subject.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
rule { dependency_proxy_disabled }.prevent :read_dependency_proxy
|
||||
|
||||
rule do
|
||||
read_dependency_proxy_personal_access_token | read_dependency_proxy_deploy_token
|
||||
end.enable :read_dependency_proxy
|
||||
|
||||
rule do
|
||||
~read_dependency_proxy_personal_access_token & ~read_dependency_proxy_deploy_token
|
||||
end.prevent :read_dependency_proxy
|
||||
|
||||
def access_level(for_any_session: false)
|
||||
return GroupMember::NO_ACCESS if @user.nil?
|
||||
|
||||
@access_level ||= lookup_access_level!(for_any_session: for_any_session)
|
||||
end
|
||||
|
||||
def lookup_access_level!(_)
|
||||
@subject.max_member_access_for_user(@user.user)
|
||||
end
|
||||
|
||||
def user_is_personal_access_token?
|
||||
user.is_a?(PersonalAccessToken)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Packages::Policies::DependencyProxy::GroupPolicy.prepend_mod_with('Packages::Policies::DependencyProxy::GroupPolicy')
|
||||
|
|
@ -65,8 +65,6 @@ module Auth
|
|||
JSONWebToken::HMACToken.new(self.class.secret).tap do |token|
|
||||
token['user_id'] = current_user.id if current_user
|
||||
token['deploy_token'] = deploy_token.token if deploy_token
|
||||
token['personal_access_token'] = raw_token if personal_access_token_user?
|
||||
token['group_access_token'] = raw_token if group_access_token_user?
|
||||
token.expire_time = self.class.token_expire_at
|
||||
end
|
||||
end
|
||||
|
|
@ -78,13 +76,5 @@ module Auth
|
|||
def raw_token
|
||||
params[:raw_token]
|
||||
end
|
||||
|
||||
def group_access_token_user?
|
||||
raw_token && current_user&.project_bot? && current_user.resource_bot_resource.is_a?(Group)
|
||||
end
|
||||
|
||||
def personal_access_token_user?
|
||||
raw_token && current_user && (current_user.human? || current_user.service_account?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ module DependencyProxy
|
|||
JSONWebToken::HMACToken.decode(token, ::Auth::DependencyProxyAuthenticationService.secret).first
|
||||
end
|
||||
|
||||
# Rename to make it obvious how it's used in Gitlab::Auth::RequestAuthenticator
|
||||
# which is to return an <object>.<id> that is used as a rack-attack discriminator
|
||||
# that way it cannot be confused with `.user_or_token_from_jwt`
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/454518
|
||||
def self.user_or_deploy_token_from_jwt(raw_jwt)
|
||||
token_payload = self.new(raw_jwt).execute
|
||||
|
||||
|
|
@ -27,34 +23,5 @@ module DependencyProxy
|
|||
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
|
||||
nil
|
||||
end
|
||||
|
||||
def self.user_or_token_from_jwt(raw_jwt)
|
||||
token_payload = self.new(raw_jwt).execute
|
||||
|
||||
if token_payload['personal_access_token']
|
||||
get_personal_access_token(token_payload['personal_access_token'])
|
||||
elsif token_payload['group_access_token']
|
||||
# a group access token is a personal access token in disguise
|
||||
get_personal_access_token(token_payload['group_access_token'])
|
||||
elsif token_payload['deploy_token']
|
||||
get_deploy_token(token_payload['deploy_token'])
|
||||
elsif token_payload['user_id']
|
||||
get_user(token_payload['user_id'])
|
||||
end
|
||||
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
|
||||
nil
|
||||
end
|
||||
|
||||
def self.get_user(user_id)
|
||||
User.find(user_id)
|
||||
end
|
||||
|
||||
def self.get_personal_access_token(raw_token)
|
||||
PersonalAccessTokensFinder.new(state: 'active').find_by_token(raw_token)
|
||||
end
|
||||
|
||||
def self.get_deploy_token(raw_token)
|
||||
DeployToken.active.find_by_token(raw_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Merge request approval policy `fallback_behavior`",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fail": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"open",
|
||||
"closed"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
= render_if_exists 'subscriptions/trials/alert', namespace: @group
|
||||
= render_if_exists 'shared/duo_pro_trial_alert', resource: @group
|
||||
= render_if_exists 'shared/duo_chat_ga_alert', resource: @group
|
||||
|
||||
= render 'groups/home_panel'
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
= render 'projects/invite_members_modal', project: @project
|
||||
= render_if_exists "shared/saml_reload_modal", group_or_project: @project
|
||||
|
||||
= dispensable_render_if_exists "projects/transferring_alert", project: @project
|
||||
= dispensable_render_if_exists "projects/importing_alert", project: @project
|
||||
= dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert"
|
||||
= dispensable_render_if_exists "projects/free_user_cap_alert", project: @project
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
- return unless project.git_transfer_in_progress?
|
||||
|
||||
- content_for :page_level_alert do
|
||||
= render Pajamas::AlertComponent.new(variant: :warning,
|
||||
dismissible: false,
|
||||
title: _('Transfer in progress'),
|
||||
alert_options: { class: 'gl-mb-3', data: { testid: "transferring-alert" } }) do |c|
|
||||
- c.with_body do
|
||||
= s_('TransferProject|This project is being transferred. Do not make any changes to the project until the transfer is complete.')
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
- @skip_current_level_breadcrumb = true
|
||||
|
||||
= render_if_exists 'projects/duo_pro_trial_alert', project: @project
|
||||
= render_if_exists 'projects/duo_chat_ga_alert', project: @project
|
||||
= render partial: 'flash_messages', locals: { project: @project }
|
||||
= render 'clusters_deprecation_alert'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
= render_if_exists 'shared/promotions/promote_mobile_devops', project: @project
|
||||
= render_if_exists 'projects/duo_pro_trial_alert', project: @project
|
||||
= render_if_exists 'projects/duo_chat_ga_alert', project: @project
|
||||
= render partial: 'flash_messages', locals: { project: @project }
|
||||
|
||||
= render 'clusters_deprecation_alert'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ module Ci
|
|||
# Therefore, we can deduplicate the sidekiq jobs until the on-going
|
||||
# assignment process has been finished.
|
||||
idempotent!
|
||||
deduplicate :until_executed, if_deduplicated: :reschedule_once
|
||||
deduplicate :until_executed, if_deduplicated: :reschedule_once, including_scheduled: true
|
||||
|
||||
def perform(resource_group_id)
|
||||
::Ci::ResourceGroup.find_by_id(resource_group_id).try do |resource_group|
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ module MergeRequests
|
|||
).execute(merge_request)
|
||||
|
||||
merge_request.update_head_pipeline
|
||||
|
||||
after_perform(merge_request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_perform(_merge_request)
|
||||
# overridden in EE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
MergeRequests::CreatePipelineWorker.prepend_mod
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: packages_dependency_proxy_pass_token_to_policy
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/434291
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141358
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/441588
|
||||
name: duo_chat_ga_alert
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442655
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149329
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/455857
|
||||
milestone: '17.0'
|
||||
group: group::container registry
|
||||
type: gitlab_com_derisk
|
||||
group: group::acquisition
|
||||
default_enabled: false
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
name: show_admin_mode_within_active_sessions
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/438674
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145523
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/444188
|
||||
milestone: '16.10'
|
||||
group: group::anti-abuse
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -861,9 +861,6 @@ Gitlab.ee do
|
|||
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner'] ||= {}
|
||||
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['cron'] ||= "0 0 * * *"
|
||||
Settings.cron_jobs['licenses_reset_submit_license_usage_data_banner']['job_class'] = 'Licenses::ResetSubmitLicenseUsageDataBannerWorker'
|
||||
Settings.cron_jobs['abandoned_trial_emails'] ||= {}
|
||||
Settings.cron_jobs['abandoned_trial_emails']['cron'] ||= "0 1 * * *"
|
||||
Settings.cron_jobs['abandoned_trial_emails']['job_class'] = 'Emails::AbandonedTrialEmailsCronWorker'
|
||||
Settings.cron_jobs['package_metadata_licenses_sync_worker'] ||= {}
|
||||
Settings.cron_jobs['package_metadata_licenses_sync_worker']['cron'] ||= "*/5 * * * *"
|
||||
Settings.cron_jobs['package_metadata_licenses_sync_worker']['job_class'] = 'PackageMetadata::LicensesSyncWorker'
|
||||
|
|
|
|||
|
|
@ -731,6 +731,8 @@
|
|||
- 1
|
||||
- - security_scan_result_policies_sync_project
|
||||
- 1
|
||||
- - security_scan_result_policies_unblock_fail_open_approval_rules
|
||||
- 1
|
||||
- - security_scans
|
||||
- 2
|
||||
- - security_scans_purge_by_job_id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFallbackBehaviorToScanResultPolicyReads < Gitlab::Database::Migration[2.2]
|
||||
enable_lock_retries!
|
||||
milestone '16.11'
|
||||
|
||||
def change
|
||||
add_column :scan_result_policies, :fallback_behavior, :jsonb, null: false, default: {}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddExtensionsMarketplaceOptInStatusToUserPreferences < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.11'
|
||||
|
||||
def change
|
||||
add_column :user_preferences, :extensions_marketplace_opt_in_status, :smallint, default: 0, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
9b0545358dc8af7ce87a87497598fe1cec6159638c925f87afeba1b40d4bb49c
|
||||
|
|
@ -0,0 +1 @@
|
|||
2f045332b7600514f8adeea441afca3c5cd8eddd5a7fab261e48fc373046ead1
|
||||
|
|
@ -15581,6 +15581,7 @@ CREATE TABLE scan_result_policies (
|
|||
project_approval_settings jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
commits smallint,
|
||||
send_bot_message jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
fallback_behavior jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
CONSTRAINT age_value_null_or_positive CHECK (((age_value IS NULL) OR (age_value >= 0))),
|
||||
CONSTRAINT check_scan_result_policies_rule_idx_positive CHECK (((rule_idx IS NULL) OR (rule_idx >= 0)))
|
||||
);
|
||||
|
|
@ -17109,6 +17110,7 @@ CREATE TABLE user_preferences (
|
|||
time_display_format smallint DEFAULT 0 NOT NULL,
|
||||
home_organization_id bigint,
|
||||
use_web_ide_extension_marketplace boolean DEFAULT false NOT NULL,
|
||||
extensions_marketplace_opt_in_status smallint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)),
|
||||
CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)),
|
||||
CONSTRAINT check_b22446f91a CHECK ((render_whitespace_in_code IS NOT NULL)),
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ authentication are supported by Admin Mode. Admin Mode status is stored in the c
|
|||
### Check if your session has Admin Mode enabled
|
||||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/438674) in GitLab 16.10 [with a flag](../../administration/feature_flags.md) named `show_admin_mode_within_active_sessions`. Disabled by default.
|
||||
- [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/444188) in GitLab 16.10.
|
||||
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/438674) in GitLab 17.0. Feature flag `show_admin_mode_within_active_sessions` removed.
|
||||
|
||||
Go to your list of active sessions:
|
||||
|
||||
|
|
|
|||
|
|
@ -8953,6 +8953,7 @@ Input type: `UserPreferencesUpdateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationuserpreferencesupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationuserpreferencesupdateextensionsmarketplaceoptinstatus"></a>`extensionsMarketplaceOptInStatus` | [`ExtensionsMarketplaceOptInStatus`](#extensionsmarketplaceoptinstatus) | Status of the Web IDE Extension Marketplace opt-in for the user. |
|
||||
| <a id="mutationuserpreferencesupdateissuessort"></a>`issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. |
|
||||
| <a id="mutationuserpreferencesupdateusewebideextensionmarketplace"></a>`useWebIdeExtensionMarketplace` | [`Boolean`](#boolean) | Whether Web IDE Extension Marketplace is enabled for the user. |
|
||||
| <a id="mutationuserpreferencesupdatevisibilitypipelineidtype"></a>`visibilityPipelineIdType` | [`VisibilityPipelineIdType`](#visibilitypipelineidtype) | Determines whether the pipeline list shows ID or IID. |
|
||||
|
|
@ -30228,8 +30229,9 @@ fields relate to interactions between the two entities.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="userpreferencesextensionsmarketplaceoptinstatus"></a>`extensionsMarketplaceOptInStatus` | [`ExtensionsMarketplaceOptInStatus!`](#extensionsmarketplaceoptinstatus) | Status of the Web IDE Extension Marketplace opt-in for the user. |
|
||||
| <a id="userpreferencesissuessort"></a>`issuesSort` | [`IssueSort`](#issuesort) | Sort order for issue lists. |
|
||||
| <a id="userpreferencesusewebideextensionmarketplace"></a>`useWebIdeExtensionMarketplace` | [`Boolean!`](#boolean) | Whether Web IDE Extension Marketplace is enabled for the user. |
|
||||
| <a id="userpreferencesusewebideextensionmarketplace"></a>`useWebIdeExtensionMarketplace` **{warning-solid}** | [`Boolean!`](#boolean) | **Deprecated** in GitLab 16.11. Use `extensions_marketplace_opt_in_status` instead. |
|
||||
| <a id="userpreferencesvisibilitypipelineidtype"></a>`visibilityPipelineIdType` | [`VisibilityPipelineIdType`](#visibilitypipelineidtype) | Determines whether the pipeline list shows ID or IID. |
|
||||
|
||||
### `UserStatus`
|
||||
|
|
@ -32671,6 +32673,16 @@ Event action.
|
|||
| <a id="eventactionreopened"></a>`REOPENED` | Reopened action. |
|
||||
| <a id="eventactionupdated"></a>`UPDATED` | Updated action. |
|
||||
|
||||
### `ExtensionsMarketplaceOptInStatus`
|
||||
|
||||
Values for status of the Web IDE Extension Marketplace opt-in for the user.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="extensionsmarketplaceoptinstatusdisabled"></a>`DISABLED` | Web IDE Extension Marketplace opt-in status: DISABLED. |
|
||||
| <a id="extensionsmarketplaceoptinstatusenabled"></a>`ENABLED` | Web IDE Extension Marketplace opt-in status: ENABLED. |
|
||||
| <a id="extensionsmarketplaceoptinstatusunset"></a>`UNSET` | Web IDE Extension Marketplace opt-in status: UNSET. |
|
||||
|
||||
### `FindingReportsComparerStatus`
|
||||
|
||||
Report comparison status.
|
||||
|
|
@ -34069,6 +34081,7 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumcluster_security_warning"></a>`CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. |
|
||||
| <a id="usercalloutfeaturenameenumdeployment_details_feedback"></a>`DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. |
|
||||
| <a id="usercalloutfeaturenameenumduo_chat_callout"></a>`DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. |
|
||||
| <a id="usercalloutfeaturenameenumduo_chat_ga_alert"></a>`DUO_CHAT_GA_ALERT` | Callout feature name for duo_chat_ga_alert. |
|
||||
| <a id="usercalloutfeaturenameenumduo_pro_trial_alert"></a>`DUO_PRO_TRIAL_ALERT` | Callout feature name for duo_pro_trial_alert. |
|
||||
| <a id="usercalloutfeaturenameenumfeature_flags_new_version"></a>`FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. |
|
||||
| <a id="usercalloutfeaturenameenumgcp_signup_offer"></a>`GCP_SIGNUP_OFFER` | Callout feature name for gcp_signup_offer. |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
status: proposed
|
||||
creation-date: "2024-03-29"
|
||||
authors: [ "@sean_carroll" ]
|
||||
coach: "@jessieay"
|
||||
approvers: [ "@susie.bee", "@m_gill" ]
|
||||
owning-stage: "~devops::ai-powered"
|
||||
participating-stages: []
|
||||
---
|
||||
|
||||
<!-- Blueprints often contain forward-looking statements -->
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
|
||||
# Self-Hosted Model Deployment
|
||||
|
||||
This Blueprint describes support for customer self-deployments of Mistral LLMs as a backend for GitLab Duo features, as an alternative to the default Vertex or Anthropic models offered on GitLab Dedicated and .com. This initiative supports both internet connected and air-gapped GitLab deployments.
|
||||
|
||||
## Motivation
|
||||
|
||||
Self-Hosted LLM models allow customers to manage the end-to-end transmission of requests to enterprise-hosted LLM backends for [GitLab Duo features](../../../user/ai_features.md), and keep all requests within their enterprise network. GitLab provides as a default LLM backends of Google Vertex and Anthropic, hosted externally to GitLab. GitLab Duo feature developers are able to access other LLM choices via the AI Gateway. More details on model and region information can be [found here](https://gitlab.com/groups/gitlab-org/-/epics/13024#current-feature-outline).
|
||||
|
||||
### Goals
|
||||
|
||||
Self-Managed models serve sophisticated customers capable of managing their own LLM infrastructure. GitLab provides the option to connect supported models to LLM features. Model-specific prompts and GitLab Duo feature support is provided by the self-hosted models feature.
|
||||
|
||||
- Choice of LLM models
|
||||
- Ability to keep all data and request/response logs within their own domain
|
||||
- Ability to select specific GitLab Duo Features for their users
|
||||
- Non-reliance on the .com AI Gateway
|
||||
|
||||
### Non-Goals
|
||||
|
||||
Other features that are goals of the Custom Models group and which may have some future overlap are explicitly out of scope for the current iteration of this blueprint. These include:
|
||||
|
||||
- Local Models
|
||||
- RAG
|
||||
- Fine Tuning
|
||||
- GitLab managed hosting of open source models, other than the current supported third party models.
|
||||
- Bring Your Own API Key (BYOK)
|
||||
|
||||
## Proposal
|
||||
|
||||
GitLab will provide support for specific LLMs hosted in a customer's infrastructure. The customer will self-host the AI Gateway, and self-host one or more LLMs from a predefined list. Customers will then configure their GitLab instance for specific models by LLM feature. A different model can be chosen for each GitLab Duo feature.
|
||||
|
||||
This feature is accessible at the instance-level and is intended for use in GitLab Self-Managed instances.
|
||||
|
||||
Self-Hosted Model Deployment is a [GitLab Duo Enterprise Add-on](https://about.gitlab.com/pricing/).
|
||||
|
||||
## Design and implementation details
|
||||
|
||||
### Component Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
a1 --> c1
|
||||
a2 --> b1
|
||||
b1 --> c1
|
||||
b3 --> b1
|
||||
b4 --> b1
|
||||
c1 --> c2
|
||||
c2 --> c3
|
||||
c3 --> d1
|
||||
d1 --> d2
|
||||
|
||||
subgraph "User"
|
||||
a1[IDE Request]
|
||||
a2[Web / CLI Request]
|
||||
end
|
||||
|
||||
subgraph "Self-Managed GitLab"
|
||||
b1[GitLab Duo Feature] <--> b2[Model & Feature-specific<br/>Prompt Retrieval]
|
||||
b3[GitLab Duo Feature<br/>Configuration]
|
||||
b4[LLM Serving Config]
|
||||
end
|
||||
|
||||
subgraph "Self-Hosted AI Gateway"
|
||||
c1[Inbound API interface]
|
||||
c2[Model routing]
|
||||
c3[Model API interface]
|
||||
end
|
||||
|
||||
subgraph "Self-Hosted LLM"
|
||||
d1[LoadBalancer]
|
||||
d2[GPU-based backend]
|
||||
end
|
||||
```
|
||||
|
||||
#### Diagram Notes
|
||||
|
||||
- **User request**: A GitLab Duo Feature is accessed from one of three possible starting points (Web UI, IDE or Git CLI). The IDE communicates directly with the AI Gateway.
|
||||
- **LLM Serving Config**: The existence of a customer-hosted model along with its connectivity information is declared in GitLab Rails and exposed to the AI Gateway with an API.
|
||||
- **GitLab Duo Feature Configuration**: For each supported GitLab Duo feature, a user may select a supported model and the associated prompts are automatically loaded.
|
||||
- **Prompt Retrieval**: GitLab Rails chooses and processes the correct prompt(s) based on the GitLab Duo Feature and model being used
|
||||
- **Model Routing**: The AI Gateway routes the request to the correct external AI endpoint. The current default for GitLab Duo features is either Vertex or Anthropic. If a Self-Managed model is used, the AI Gateway must route to the correct customer-hosted model's endpoint. The customer-hosted model server details are the `LLM Serving Config` and retrieved from GitLab Rails as an API call. They may be cached in the AI Gateway.
|
||||
- **Model API interface**: Each model serving has its own endpoint signature. The AI Gateway needs to be able to communicate using the right signature. We will support commonly supported model serving formats such as the OpenAI API spec.
|
||||
|
||||
### Configuration
|
||||
|
||||
Configuration is set at the GitLab instance-level; for each GitLab Duo feature a drop-down list of options will be presented. The following options will be available:
|
||||
|
||||
- Self-Hosted Model 1
|
||||
- Self-Hosted Model n
|
||||
- Feature Inactive
|
||||
|
||||
In the initial implementation a single self-hosted Model will be supported, but this will be expanded to a number of GitLab-defined models.
|
||||
|
||||
### AI Gateway Deployment
|
||||
|
||||
Customers will be required to deploy a local instance of the AI Gateway in their own infrastructure. The initial means to do this is via Docker container, as described [in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/452489).
|
||||
|
||||
Self-hosted Runway will be the preferred delivery mechanism for deploying the AI Gateway. Future options, in order of preference are:
|
||||
|
||||
- Runway [discussion](https://gitlab.com/gitlab-com/gl-security/security-assurance/fedramp/fedramp-certification/-/issues/452#note_1832261170)
|
||||
- Kubernetes deployment [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/452490)
|
||||
- Omnibus packaging [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/8467)
|
||||
|
||||
It should be noted that deployment by Docker container is a temporary measure only, and will be superceeded by the three options listed above.
|
||||
|
||||
### Prompt Support
|
||||
|
||||
For each supported model and supported GitLab Duo feature, prompts will be developed and evaluated by GitLab. They will be baked into the Rails Monolith source code.
|
||||
|
||||
When the standard prompts are migrated into either the AI Gateway or a prompt template repository (direction is to be determined), the prompts supporting self-hosted models will also be migrated.
|
||||
|
||||
### Supported LLMs
|
||||
|
||||
- [Mistral-7B-v0.1](https://huggingface.co/mistralai/Mistral-7B-v0.1)
|
||||
- [Mixtral 8x22B](https://huggingface.co/mistral-community/Mixtral-8x22B-v0.1)
|
||||
|
||||
Installation instructions will be added to the Developer documentation. [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/452509)
|
||||
|
||||
_This list will expand in the near future, but the overall architecture will be the same_
|
||||
|
||||
### GitLab Duo Feature Support
|
||||
|
||||
| Feature | Default Model | [Mistral AI 7B v0.1](https://huggingface.co/mistralai/Mistral-7B-v0.1) | [Mixtral 8x22B](https://huggingface.co/mistral-community/Mixtral-8x22B-v0.1) |
|
||||
|---------------------|------------------|----------------------------------------------------------------|---------------------|
|
||||
| GitLab Duo Chat | Anthropic Claude-2 <br/> Vertex AI Codey textembedding-gecko | Not planned | Not planned |
|
||||
| Code Completion | Vertex AI Codey code-gecko | ✅ | ✅ |
|
||||
| Code Generation | Anthropic Claude-2 | ✅ | ✅ |
|
||||
| Git Suggestions | Vertex AI Codey codechat-bison | Not planned | Not planned |
|
||||
| Discussion Summary | Vertex AI Codey text-bison | Not planned | Not planned |
|
||||
| Issue Description Generation | Anthropic Claude-2 | Not planned | Not planned |
|
||||
| Test Generation | Anthropic Claude-2 | Not planned | Not planned |
|
||||
| Merge request template population | Vertex AI Codey text-bison | Not planned | Not planned |
|
||||
| Suggested Reviewers | GitLab In-House Model | Not planned | Not planned |
|
||||
| Merge request summary | Vertex AI Codey text-bison | Not planned | Not planned |
|
||||
| Code review summary | Vertex AI Codey text-bison | Not planned | Not planned |
|
||||
| Vulnerability explanation | Vertex AI Codey text-bison Anthropic <br/>Claude-2 if degraded performance | Not planned | Not planned |
|
||||
| Vulnerability resolution | Vertex AI Codey code-bison | Not planned | Not planned |
|
||||
| Code explanation | Vertex AI Codey codechat-bison | Not planned | Not planned |
|
||||
| Root cause analysis | Vertex AI Codey text-bison | Not planned | Not planned |
|
||||
| Value stream forecasting | GitLab In-House Model | Not planned | Not planned |
|
||||
|
||||
The `Suggested Reviewers` and `Value stream forecasting` models are Convolutional Neural Networks (CNNs) developed in-house by GitLab.
|
||||
|
||||
#### LLM-hosting
|
||||
|
||||
Customers will self-manage LLM hosting. For Mistral, GitLab recommends following the [Mistral Self-Deployment documentation](https://docs.mistral.ai/self-deployment/overview/)
|
||||
|
||||
#### GitLab Duo License Management
|
||||
|
||||
The Self-Managed GitLab Rails will self-issue a token (same process as for .com) that the local AI Gateway can verify, to guarantee that cross-service communication is secure. [Details](https://gitlab.com/gitlab-org/gitlab/-/issues/444216)
|
||||
|
||||
### System Architectures
|
||||
|
||||
At this time a single system architecture only is supported. See the Out of Scope section for discussion on alternatives.
|
||||
|
||||
#### Self-Managed GitLab with self-hosted AI Gateway
|
||||
|
||||
This system architecture supports both a internet-connected GitLab and AI Gateway, or can be run in an air-gapped environment. Customers install a self-managed AI Gateway within their own infrastructure. The long-term vision for such installations is via Runway, but until that is available a Docker-based install will be supported.
|
||||
|
||||
Self-Managed customers who deploy a self-managed AI Gateway will only be able to access self-hosted models at this time. Future work around [Bring Your Own Key](https://gitlab.com/groups/gitlab-org/-/epics/12973) may change that in the future.
|
||||
|
||||
### Development Environment
|
||||
|
||||
Engineering documentation will be produced on how to develop this feature, with work in progress on:
|
||||
|
||||
- [Include AI Gateway in GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/-/issues/2025)
|
||||
- [Developer setup for Self-Hosted Models](https://gitlab.com/gitlab-org/gitlab/-/issues/452509)
|
||||
- [Centralized Evaluation Framework](https://gitlab.com/gitlab-org/modelops/ai-model-validation-and-research/ai-evaluation/prompt-library/-/tree/main)
|
||||
|
||||
### Out of scope
|
||||
|
||||
- It would be possible to support customer self-hosted models within a customer's infrastructure for dedicated or .com customers, but this is not within scope at this time.
|
||||
- Support for models other than those listed in the Supported LLMs section above.
|
||||
- Support for modified models.
|
||||
|
||||
#### Out of scope System Architectures
|
||||
|
||||
There are no plans to support these system architectures at this time, this could change if there was sufficient customer demand.
|
||||
|
||||
##### Self-Managed GitLab with .com AI Gateway
|
||||
|
||||
In this out-of-scope architecture a self-managed customer continues to use the .com hosted AI gateway, but points back to self-managed models.
|
||||
|
||||
##### .com GitLab with .com AI Gateway
|
||||
|
||||
In this out-of-scope architecture .com customers point to self-managed models. This topology might be desired if there were better quality of results for a given feature by a specific model, or if customers could improve response latency by using their own model-serving infrastructure.
|
||||
|
||||
##### GitLab Dedicated
|
||||
|
||||
Support will not be provided for Dedicated customers to use a self-hosted AI Gateway and self-hosted models. Dedicated customers who use GitLab Duo features can access them via the .com AI Gateway. If there is customer demand for self-managed models for Dedicated customers, this can be considered in the future.
|
||||
|
||||
##### Externally hosted models
|
||||
|
||||
It is expected that customers will self-host models.
|
||||
|
|
@ -175,7 +175,7 @@ Here, `<number>` is the number of commits to undo.
|
|||
For example, if you want to undo only the latest commit:
|
||||
|
||||
```shell
|
||||
git rest HEAD~1
|
||||
git reset HEAD~1
|
||||
```
|
||||
|
||||
The commit is reset and any changes remain in the local repository.
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ the following sections and tables provide an alternative.
|
|||
| `rules` | `array` of rules | true | | List of rules that the policy applies. |
|
||||
| `actions` | `array` of actions | false | | List of actions that the policy enforces. |
|
||||
| `approval_settings` | `object` | false | | Project settings that the policy overrides. |
|
||||
| `fallback_behavior` | `object` | false | | Settings that affect invalid or unenforceable rules. |
|
||||
|
||||
## `scan_finding` rule type
|
||||
|
||||
|
|
@ -239,6 +240,18 @@ The settings set in the policy overwrite settings in the project.
|
|||
| `require_password_to_approve` | `boolean` | false | `true`, `false` | `Any merge request` | When enabled, there will be password confirmation on approvals. Password confirmation adds an extra layer of security. |
|
||||
| `prevent_pushing_and_force_pushing` | `boolean` | false | `true`, `false` | All | When enabled, prevents users from pushing and force pushing to a protected branch if that branch is included in the security policy. This ensures users do not bypass the merge request process to add vulnerable code to a branch. |
|
||||
|
||||
## `fallback_behavior`
|
||||
|
||||
> - The `fallback_behavior` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/451784) in GitLab 16.11 [with a flag](../../../administration/feature_flags.md) named `security_scan_result_policies_unblock_fail_open_approval_rules`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_scan_result_policies_unblock_fail_open_approval_rules`.
|
||||
On GitLab.com and GitLab Dedicated, this feature is not available.
|
||||
|
||||
| Field | Type | Required | Possible values | Description |
|
||||
|--------|----------|----------|--------------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| `fail` | `string` | false | `open` or `closed` | `closed` (default): Invalid or unenforceable rules of a policy do not require approval. `open`: Invalid or unenforceable rules of a policy require approval. |
|
||||
|
||||
## Example security merge request approval policies project
|
||||
|
||||
You can use this example in a `.gitlab/security-policies/policy.yml` file stored in a
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ The following video provides an overview of these policies.
|
|||
|
||||
## Prerequisites to creating a new license approval policy
|
||||
|
||||
License approval policies rely on the output of a dependency scanning job to verify that requirements have been met. If dependency scanning has not been properly configured, and therefore no dependency scanning jobs ran related to an open MR, the policy has no data with which to verify the requirements. When security policies are missing data for evaluation, they fail closed and assume the merge request could contain vulnerabilities.
|
||||
License approval policies rely on the output of a dependency scanning job to verify that requirements have been met. If dependency scanning has not been properly configured, and therefore no dependency scanning jobs ran related to an open MR, the policy has no data with which to verify the requirements. When security policies are missing data for evaluation, by default they fail closed and assume the merge request could contain vulnerabilities. You can opt out of the default behavior with the `fallback_behavior` property and set policies to fail open. A policy that fails open has all invalid and unenforceable rules unblocked.
|
||||
|
||||
To ensure enforcement of your policies, you should enable dependency scanning on your target development projects. You can achieve this a few different ways:
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ epics:
|
|||
| Merge Request | Review requested | Participants, Watchers, Subscribers, Custom notification level with this event selected, and the old reviewer. |
|
||||
| Merge Request | Reopened | Subscribers and participants. |
|
||||
| Merge Request | Title or description changed | Any new mentions by username. |
|
||||
| Merge Request | Added as approver | Custom notification level with this event selected. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12855) in GitLab 16.7. |
|
||||
| Pipeline | Failed | The author of the pipeline. |
|
||||
| Pipeline | Fixed | The author of the pipeline. Enabled by default. |
|
||||
| Pipeline | Successful | The author of the pipeline, with Custom notification level for successful pipelines. If the pipeline failed previously, a "Fixed pipeline" message is sent for the first successful pipeline after the failure, and then a "Successful pipeline" message for any further successful pipelines. |
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ GitLab forwards the spam to Akismet.
|
|||
|
||||
### Snippet limitations
|
||||
|
||||
- There are no limits as to how many snippets you can create.
|
||||
- Binary files are not supported.
|
||||
- Creating or deleting branches is not supported. Only the default branch is used.
|
||||
- Git tags are not supported in snippet repositories.
|
||||
|
|
@ -258,8 +259,7 @@ GitLab forwards the spam to Akismet.
|
|||
than 10 files results in an error.
|
||||
- Revisions are not visible to the user on the GitLab UI, but [an issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/39271)
|
||||
for updates.
|
||||
- The default [maximum size for a snippet](../administration/snippets/index.md)
|
||||
is 50 MB.
|
||||
- The default [maximum size for a snippet](../administration/snippets/index.md) and current (as of 2024-04-17) is 50 MB.
|
||||
- Git LFS is not supported.
|
||||
|
||||
### Reduce snippets repository size
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ module Gitlab
|
|||
ConcurrentRubyThreadIsUsedError = Class.new(StandardError)
|
||||
|
||||
def allowlisted?(absolute_path, allowlist)
|
||||
path = absolute_path.downcase
|
||||
|
||||
allowlist.map(&:downcase).any? do |allowed_path|
|
||||
path.start_with?(allowed_path)
|
||||
allowlist.any? do |allowed_path|
|
||||
absolute_path.start_with?(allowed_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ RSpec.describe Gitlab::Utils, feature_category: :shared do
|
|||
it 'returns false if path is not allowed' do
|
||||
expect(allowlisted?('/test/test', allowed_paths)).to be(false)
|
||||
end
|
||||
|
||||
it 'returns false if path is in different case' do
|
||||
expect(allowlisted?('/Foo/bar', allowed_paths)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.decode_path' do
|
||||
|
|
|
|||
|
|
@ -1271,10 +1271,6 @@ ee:
|
|||
- :project_id
|
||||
- :created_at
|
||||
- :updated_at
|
||||
- :auto_fix_container_scanning
|
||||
- :auto_fix_dast
|
||||
- :auto_fix_dependency_scanning
|
||||
- :auto_fix_sast
|
||||
project:
|
||||
- :requirements_enabled
|
||||
- :requirements_access_level
|
||||
|
|
|
|||
|
|
@ -1068,6 +1068,9 @@ msgstr ""
|
|||
msgid "%{percentageUsed}%% used"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{percentage}%% complete"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{percentage}%% issues closed"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19008,6 +19011,21 @@ msgstr ""
|
|||
msgid "Due to inactivity, this project is scheduled to be deleted on %{deletion_date}. %{link_start}Why is this scheduled?%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChatGAAlert|Access Chat in the IDE"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChatGAAlert|Access a broad range of GitLab Duo features with your personal chat assistant in the UI of GitLab.com. You can also use Chat to access Code explanation, Code refactoring, and Test generation in your preferred IDE. Later this year, a paid add-on subscription will be required to access GitLab Duo Chat."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChatGAAlert|Dismiss Duo Chat GA banner"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChatGAAlert|GitLab Duo Chat is generally available today"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChatGAAlert|Use GitLab Duo Chat"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|Ask a question about GitLab"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19731,12 +19749,18 @@ msgstr ""
|
|||
msgid "End time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ended %{dueDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Endpoint name '%{endpoint}' of component '%{component}' must not start with '%{prefix}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ends"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ends %{dueDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ends on"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28830,13 +28854,19 @@ msgstr ""
|
|||
msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|AB"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|AB,CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|API token for Jira Cloud or password for Jira Data Center and Jira Server"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|API token or password"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|An error occurred while fetching issue list"
|
||||
msgid "JiraService|An error occurred while fetching the Jira issue list"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Authentication type"
|
||||
|
|
@ -28851,13 +28881,19 @@ msgstr ""
|
|||
msgid "JiraService|Basic"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Create Jira issues of this type from vulnerabilities."
|
||||
msgid "JiraService|Comma-separated list of Jira project keys. Leave blank to include all available keys."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Create a Jira issue for a vulnerability to track any action taken to resolve or mitigate a vulnerability."
|
||||
msgid "JiraService|Create Jira issues for vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Displaying Jira issues while leaving GitLab issues also enabled might be confusing. Consider %{gitlab_issues_link_start}disabling GitLab issues%{link_end} if they won't otherwise be used."
|
||||
msgid "JiraService|Create Jira issues from GitLab to track any action taken to resolve or mitigate vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Create Jira issues of this type."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Create only Jira issues for vulnerabilities in this project even if you've enabled GitLab issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Email for Jira Cloud or username for Jira Data Center and Jira Server"
|
||||
|
|
@ -28866,22 +28902,22 @@ msgstr ""
|
|||
msgid "JiraService|Email or username"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Enable Jira issue creation from vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Enable Jira issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Enable Jira transitions"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Enter a Jira project key to generate issue types."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Events for %{noteable_model_name} are disabled."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Failed to load Jira issue. View the issue in Jira, or reload the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Fetch issue types for this Jira project"
|
||||
msgid "JiraService|Fetch issue types again for the new project key."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Fetch issue types for this project key"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|For Jira Cloud, the authentication type must be %{basic}"
|
||||
|
|
@ -28890,19 +28926,13 @@ msgstr ""
|
|||
msgid "JiraService|For example, 12, 24"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|For example, AB"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|For example, AB,CD"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|IDs must be a list of numbers that can be split with , or ;"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|If different from the Web URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Issues created from vulnerabilities in this project will be Jira issues, even if GitLab issues are enabled."
|
||||
msgid "JiraService|If you access Jira issues in GitLab, you might want to %{link_start}disable GitLab issues%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira API URL"
|
||||
|
|
@ -28914,9 +28944,6 @@ msgstr ""
|
|||
msgid "JiraService|Jira comments are created when an issue is referenced in a merge request."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira issue creation from vulnerabilities (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira issue prefix"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28929,6 +28956,12 @@ msgstr ""
|
|||
msgid "JiraService|Jira issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira issues (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira issues for vulnerabilities (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Jira personal access token"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28956,12 +28989,6 @@ msgstr ""
|
|||
msgid "JiraService|Open Jira"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Project key changed, refresh list"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Project key is required to generate issue types"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Recommended. Only available for Jira Data Center and Jira Server."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28995,10 +29022,13 @@ msgstr ""
|
|||
msgid "JiraService|Using Jira for issue tracking?"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|View Jira issues (optional)"
|
||||
msgid "JiraService|View Jira issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Warning: All GitLab users with access to this GitLab project can view all issues from the Jira project you select."
|
||||
msgid "JiraService|View issues from multiple Jira projects in this GitLab project. Access a read-only list of your Jira issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Warning: All users with access to this GitLab project can view all issues from the Jira project you specify."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Web URL"
|
||||
|
|
@ -29007,9 +29037,6 @@ msgstr ""
|
|||
msgid "JiraService|When a Jira issue is mentioned in a commit or merge request, a remote link and comment (if enabled) will be created."
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of your Jira issues. %{jira_issues_link_start}Learn more.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "JiraService|You must configure Jira before enabling this integration. %{jira_doc_link_start}Learn more.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31352,15 +31379,15 @@ msgstr ""
|
|||
msgid "MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|%{count} of %{total} permissions selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|%{requirement} has to be enabled in order to enable %{permission}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Add at least one custom permission to the base role."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Are you sure you want to delete this custom role? Before you delete this custom role, make sure no group member has this role."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31436,6 +31463,9 @@ msgstr ""
|
|||
msgid "MemberRole|ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Learn more about %{linkStart}available custom permissions%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Manage roles"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31454,6 +31484,9 @@ msgstr ""
|
|||
msgid "MemberRole|No description"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Permission"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Permissions"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31472,6 +31505,9 @@ msgstr ""
|
|||
msgid "MemberRole|Select a %{linkStart}pre-existing static role%{linkEnd} to predefine a set of permissions."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Select at least one permission."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Standard roles"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49605,6 +49641,9 @@ msgstr ""
|
|||
msgid "Started"
|
||||
msgstr ""
|
||||
|
||||
msgid "Started %{startDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Started %{startsIn}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -49620,6 +49659,9 @@ msgstr ""
|
|||
msgid "Starts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts %{startDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts %{startsIn}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54071,9 +54113,6 @@ msgstr ""
|
|||
msgid "Transfer group to another parent group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Transfer in progress"
|
||||
msgstr ""
|
||||
|
||||
msgid "Transfer project"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54128,9 +54167,6 @@ msgstr ""
|
|||
msgid "TransferProject|Root namespace can't be updated if the project has NPM packages scoped to the current root level namespace."
|
||||
msgstr ""
|
||||
|
||||
msgid "TransferProject|This project is being transferred. Do not make any changes to the project until the transfer is complete."
|
||||
msgstr ""
|
||||
|
||||
msgid "TransferProject|You don't have permission to transfer projects into that namespace."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@
|
|||
"swagger-cli": "^4.0.4",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"timezone-mock": "^1.0.8",
|
||||
"vite": "^5.1.6",
|
||||
"vite": "^5.2.9",
|
||||
"vite-plugin-ruby": "^5.0.0",
|
||||
"vue-loader-vue3": "npm:vue-loader@17",
|
||||
"vue-test-utils-compat": "0.0.14",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ module Gitlab
|
|||
def initialize(log_output = $stderr)
|
||||
# https://github.com/mperham/sidekiq/wiki/Advanced-Options#concurrency
|
||||
# https://ruby.social/@getajobmike/109326475545816363
|
||||
@concurrency = 20
|
||||
@max_concurrency = 20
|
||||
@min_concurrency = 0
|
||||
# TODO: to be set to 20 once max_concurrency and min_concurrency is removed https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2760
|
||||
@concurrency = 0
|
||||
@environment = ENV['RAILS_ENV'] || 'development'
|
||||
@metrics_dir = ENV["prometheus_multiproc_dir"] || File.absolute_path("tmp/prometheus_multiproc_dir/sidekiq")
|
||||
@pid = nil
|
||||
|
|
@ -88,6 +91,12 @@ module Gitlab
|
|||
'No queues found, you must select at least one queue'
|
||||
end
|
||||
|
||||
if routing_rules.empty?
|
||||
# setting min_concurrency equal to max_concurrency so that the concurrency eventually
|
||||
# is set to 20 (default value) instead of based on the number of queues, which is only 2+1 in this case.
|
||||
@min_concurrency = @min_concurrency == 0 ? @max_concurrency : @min_concurrency
|
||||
end
|
||||
|
||||
if @list_queues
|
||||
puts queue_groups.map(&:sort) # rubocop:disable Rails/Output
|
||||
|
||||
|
|
@ -111,6 +120,8 @@ module Gitlab
|
|||
queue_groups,
|
||||
env: @environment,
|
||||
directory: @rails_path,
|
||||
max_concurrency: @max_concurrency,
|
||||
min_concurrency: @min_concurrency,
|
||||
concurrency: @concurrency,
|
||||
dryrun: @dryrun,
|
||||
timeout: @soft_timeout_seconds
|
||||
|
|
@ -203,6 +214,14 @@ module Gitlab
|
|||
@concurrency = int.to_i
|
||||
end
|
||||
|
||||
opt.on('-m', '--max-concurrency INT', 'Maximum threads to use with Sidekiq (default: 20, 0 to disable)') do |int|
|
||||
@max_concurrency = int.to_i
|
||||
end
|
||||
|
||||
opt.on('--min-concurrency INT', 'Minimum threads to use with Sidekiq (default: 0)') do |int|
|
||||
@min_concurrency = int.to_i
|
||||
end
|
||||
|
||||
opt.on('-e', '--environment ENV', 'The application environment') do |env|
|
||||
@environment = env
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ module Gitlab
|
|||
#
|
||||
# Returns an Array containing the waiter threads (from Process.detach) of
|
||||
# the started processes.
|
||||
def self.start(queues, env: :development, directory: Dir.pwd, concurrency: 20, timeout: DEFAULT_SOFT_TIMEOUT_SECONDS, dryrun: false)
|
||||
def self.start(queues, env: :development, directory: Dir.pwd, max_concurrency: 20, min_concurrency: 0, concurrency: 0, timeout: DEFAULT_SOFT_TIMEOUT_SECONDS, dryrun: false)
|
||||
queues.map.with_index do |pair, index|
|
||||
start_sidekiq(pair, env: env,
|
||||
directory: directory,
|
||||
max_concurrency: max_concurrency,
|
||||
min_concurrency: min_concurrency,
|
||||
concurrency: concurrency,
|
||||
worker_id: index,
|
||||
timeout: timeout,
|
||||
|
|
@ -50,11 +52,13 @@ module Gitlab
|
|||
# Starts a Sidekiq process that processes _only_ the given queues.
|
||||
#
|
||||
# Returns the PID of the started process.
|
||||
def self.start_sidekiq(queues, env:, directory:, concurrency:, worker_id:, timeout:, dryrun:)
|
||||
# rubocop: disable Metrics/ParameterLists -- max_concurrency and min_concurrency will be removed in 17.0
|
||||
def self.start_sidekiq(queues, env:, directory:, max_concurrency:, min_concurrency:, concurrency:, worker_id:, timeout:, dryrun:)
|
||||
# rubocop: enable Metrics/ParameterLists
|
||||
counts = count_by_queue(queues)
|
||||
|
||||
cmd = %w[bundle exec sidekiq]
|
||||
cmd << "-c#{concurrency}"
|
||||
cmd << "-c#{self.concurrency(queues, min_concurrency, max_concurrency, concurrency)}"
|
||||
cmd << "-e#{env}"
|
||||
cmd << "-t#{timeout}"
|
||||
cmd << "-gqueues:#{proc_details(counts)}"
|
||||
|
|
@ -99,5 +103,15 @@ module Gitlab
|
|||
end
|
||||
end.join(',')
|
||||
end
|
||||
|
||||
def self.concurrency(queues, min_concurrency, max_concurrency, concurrency)
|
||||
return concurrency if concurrency > 0
|
||||
|
||||
concurrency_from_queues = queues.length + 1
|
||||
max = max_concurrency > 0 ? max_concurrency : concurrency_from_queues
|
||||
min = [min_concurrency, max].min
|
||||
|
||||
concurrency_from_queues.clamp(min, max)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
let(:cli) { described_class.new('/dev/null') }
|
||||
let(:timeout) { Gitlab::SidekiqCluster::DEFAULT_SOFT_TIMEOUT_SECONDS }
|
||||
let(:default_options) do
|
||||
{ env: 'test', directory: Dir.pwd, dryrun: false, timeout: timeout, concurrency: 20 }
|
||||
{ env: 'test', directory: Dir.pwd, max_concurrency: 20, min_concurrency: 0, dryrun: false, timeout: timeout, concurrency: 0 }
|
||||
end
|
||||
|
||||
let(:sidekiq_exporter_enabled) { false }
|
||||
|
|
@ -120,6 +120,28 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
end
|
||||
end
|
||||
|
||||
context 'with --max-concurrency flag' do
|
||||
it 'starts Sidekiq workers for specified queues with a max concurrency' do
|
||||
expected_queues = [%w[foo bar baz], %w[solo]]
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with(expected_queues, default_options.merge(max_concurrency: 2))
|
||||
.and_return([])
|
||||
|
||||
cli.run(%w[foo,bar,baz solo -m 2])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --min-concurrency flag' do
|
||||
it 'starts Sidekiq workers for specified queues with a min concurrency' do
|
||||
expected_queues = [%w[foo bar baz], %w[solo]]
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with(expected_queues, default_options.merge(min_concurrency: 2))
|
||||
.and_return([])
|
||||
|
||||
cli.run(%w[foo,bar,baz solo --min-concurrency 2])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with --concurrency flag' do
|
||||
it 'starts Sidekiq workers for specified queues with the fixed concurrency' do
|
||||
expected_queues = [%w[foo bar baz], %w[solo]]
|
||||
|
|
@ -181,7 +203,8 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
expect { cli.run(%w[foo]) }.not_to raise_error
|
||||
end
|
||||
|
||||
it "starts Sidekiq workers with DEFAULT_QUEUES" do
|
||||
it "starts Sidekiq workers with DEFAULT_QUEUES and min_concurrency = max_concurrency" do
|
||||
default_options[:min_concurrency] = default_options[:max_concurrency]
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with([described_class::DEFAULT_QUEUES], default_options)
|
||||
.and_return([])
|
||||
|
|
@ -191,6 +214,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
|
||||
context 'with multi argument queues' do
|
||||
it 'starts with multiple DEFAULT_QUEUES' do
|
||||
default_options[:min_concurrency] = default_options[:max_concurrency]
|
||||
expected_queues = [%w[default mailers], %w[default mailers]]
|
||||
|
||||
expect(Gitlab::SidekiqCluster)
|
||||
|
|
@ -206,7 +230,8 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
stub_config(sidekiq: { routing_rules: [] })
|
||||
end
|
||||
|
||||
it "starts Sidekiq workers with DEFAULT_QUEUES" do
|
||||
it "starts Sidekiq workers with DEFAULT_QUEUES and min_concurrency = max_concurrency" do
|
||||
default_options[:min_concurrency] = default_options[:max_concurrency]
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with([described_class::DEFAULT_QUEUES], default_options)
|
||||
.and_return([])
|
||||
|
|
@ -215,7 +240,8 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
end
|
||||
|
||||
context "with 4 wildcard * as argument" do
|
||||
it "starts 4 Sidekiq workers all with DEFAULT_QUEUES" do
|
||||
it "starts 4 Sidekiq workers all with DEFAULT_QUEUES and min_concurrency = max_concurrency" do
|
||||
default_options[:min_concurrency] = default_options[:max_concurrency]
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with([described_class::DEFAULT_QUEUES] * 4, default_options)
|
||||
.and_return([])
|
||||
|
|
@ -223,6 +249,18 @@ RSpec.describe Gitlab::SidekiqCluster::CLI, feature_category: :gitlab_cli, stub_
|
|||
cli.run(%w[* * * *])
|
||||
end
|
||||
end
|
||||
|
||||
context "with min-concurrency flag" do
|
||||
it "starts Sidekiq workers with DEFAULT_QUEUES and min_concurrency as specified" do
|
||||
options = default_options.dup
|
||||
options[:min_concurrency] = 10
|
||||
expect(Gitlab::SidekiqCluster).to receive(:start)
|
||||
.with([described_class::DEFAULT_QUEUES] * 4, options)
|
||||
.and_return([])
|
||||
|
||||
cli.run(%w[* * * * --min-concurrency 10])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::DependencyProxyAuthController, feature_category: :container_registry do
|
||||
RSpec.describe Groups::DependencyProxyAuthController do
|
||||
include DependencyProxyHelpers
|
||||
|
||||
describe 'GET #authenticate' do
|
||||
|
|
@ -33,57 +33,21 @@ RSpec.describe Groups::DependencyProxyAuthController, feature_category: :contain
|
|||
end
|
||||
|
||||
context 'group bot user' do
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
let_it_be(:user) { create(:user, :project_bot) }
|
||||
let_it_be(:user) { create(:user, :project_bot) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy enabled' do
|
||||
let_it_be(:bot_user) { create(:user, :project_bot) }
|
||||
let_it_be(:user) { create(:personal_access_token, user: bot_user) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'service account user' do
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
let_it_be(:user) { create(:user, :service_account) }
|
||||
let_it_be(:user) { create(:user, :service_account) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy enabled' do
|
||||
let_it_be(:service_account_user) { create(:user, :service_account) }
|
||||
let_it_be(:user) { create(:personal_access_token, user: service_account_user) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token) }
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy enabled' do
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -62,8 +62,6 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
|
||||
context 'with invalid group access token' do
|
||||
let_it_be(:user) { create(:user, :project_bot) }
|
||||
let_it_be(:token) { create(:personal_access_token, user: user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
|
||||
let_it_be(:jwt) { build_jwt(token) }
|
||||
|
||||
context 'not under the group' do
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
|
|
@ -84,6 +82,8 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
end
|
||||
|
||||
context 'with insufficient scopes' do
|
||||
let_it_be(:pat) { create(:personal_access_token, user: user, scopes: [Gitlab::Auth::READ_API_SCOPE]) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
|
||||
context 'packages_dependency_proxy_containers_scope_check disabled' do
|
||||
|
|
@ -193,19 +193,7 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
token.update_column(:scopes, Gitlab::Auth::REGISTRY_SCOPES)
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'sends Workhorse instructions'
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy enabled' do
|
||||
let_it_be(:jwt) { build_jwt(token) }
|
||||
|
||||
it_behaves_like 'sends Workhorse instructions'
|
||||
end
|
||||
it_behaves_like 'sends Workhorse instructions'
|
||||
end
|
||||
|
||||
context 'with a deploy token' do
|
||||
|
|
@ -305,15 +293,6 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
|
||||
|
||||
context 'when packages_dependency_proxy_pass_token_to_policy is disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_containers_scope_check: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
|
||||
end
|
||||
|
||||
context 'with workhorse response' do
|
||||
let(:pull_response) { { status: :success, manifest: nil, from_cache: false } }
|
||||
|
||||
|
|
@ -345,14 +324,6 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
|
||||
context 'when packages_dependency_proxy_pass_token_to_policy is disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_containers_scope_check: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
end
|
||||
|
||||
context 'pulling from a subgroup' do
|
||||
let_it_be_with_reload(:parent_group) { create(:group) }
|
||||
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
|
||||
|
|
@ -373,21 +344,8 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
group.add_guest(user)
|
||||
end
|
||||
|
||||
context 'when packages_dependency_proxy_pass_token_to_policy is disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
|
||||
end
|
||||
|
||||
context 'when packages_dependency_proxy_pass_token_to_policy is enabled' do
|
||||
let_it_be(:jwt) { build_jwt(token) }
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
|
||||
end
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest', false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -409,14 +367,6 @@ RSpec.describe Groups::DependencyProxyForContainersController, feature_category:
|
|||
it_behaves_like 'without a token'
|
||||
it_behaves_like 'without permission'
|
||||
|
||||
context 'when packages_dependency_proxy_pass_token_to_policy is disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_containers_scope_check: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'a valid user' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
|
|
|
|||
|
|
@ -82,41 +82,23 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state, fe
|
|||
end
|
||||
end
|
||||
|
||||
context 'with feature flag show_admin_mode_within_active_sessions disabled' do
|
||||
before do
|
||||
stub_feature_flags(show_admin_mode_within_active_sessions: false)
|
||||
end
|
||||
it 'admin sees if the session is with admin mode', :enable_admin_mode do
|
||||
Capybara::Session.new(:admin_session)
|
||||
|
||||
it 'admin sees if the session is with admin mode', :enable_admin_mode do
|
||||
Capybara::Session.new(:admin_session)
|
||||
|
||||
using_session :admin_session do
|
||||
gitlab_sign_in(admin)
|
||||
visit user_settings_active_sessions_path
|
||||
expect(page).not_to have_content('with Admin Mode')
|
||||
end
|
||||
using_session :admin_session do
|
||||
gitlab_sign_in(admin)
|
||||
visit user_settings_active_sessions_path
|
||||
expect(page).to have_content('with Admin Mode')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag show_admin_mode_within_active_sessions enabled' do
|
||||
it 'admin sees if the session is with admin mode', :enable_admin_mode do
|
||||
Capybara::Session.new(:admin_session)
|
||||
it 'does not display admin mode text in case its not' do
|
||||
Capybara::Session.new(:admin_session)
|
||||
|
||||
using_session :admin_session do
|
||||
gitlab_sign_in(admin)
|
||||
visit user_settings_active_sessions_path
|
||||
expect(page).to have_content('with Admin Mode')
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not display admin mode text in case its not' do
|
||||
Capybara::Session.new(:admin_session)
|
||||
|
||||
using_session :admin_session do
|
||||
gitlab_sign_in(admin)
|
||||
visit user_settings_active_sessions_path
|
||||
expect(page).not_to have_content('with Admin Mode')
|
||||
end
|
||||
using_session :admin_session do
|
||||
gitlab_sign_in(admin)
|
||||
visit user_settings_active_sessions_path
|
||||
expect(page).not_to have_content('with Admin Mode')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ describe('JiraIssuesFields', () => {
|
|||
const findEnableCheckboxDisabled = () =>
|
||||
findEnableCheckbox().find('[type=checkbox]').attributes('disabled');
|
||||
const findProjectKey = () => wrapper.findComponent(GlFormInput);
|
||||
const findProjectKeys = () => wrapper.findByTestId('jira-project-keys');
|
||||
const findProjectKeyFormGroup = () => wrapper.findByTestId('project-key-form-group');
|
||||
const findJiraForVulnerabilities = () => wrapper.findByTestId('jira-for-vulnerabilities');
|
||||
const setEnableCheckbox = (isEnabled = true) => findEnableCheckbox().vm.$emit('input', isEnabled);
|
||||
|
|
@ -107,6 +108,41 @@ describe('JiraIssuesFields', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when jira_multiple_project_keys is not enabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
mountFn: shallowMountExtended,
|
||||
props: {
|
||||
initialEnableJiraIssues: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render "Jira project keys" input', () => {
|
||||
expect(findProjectKeys().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when jira_multiple_project_keys is enabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
mountFn: shallowMountExtended,
|
||||
props: {
|
||||
initialEnableJiraIssues: true,
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
jiraMultipleProjectKeys: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders "Jira project keys" input', () => {
|
||||
expect(findProjectKeys().attributes('label')).toBe('Jira project keys');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vulnerabilities creation', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,231 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlIcon, GlSkeletonLoader, GlProgressBar, GlBadge } from '@gitlab/ui';
|
||||
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
|
||||
import milestoneQuery from '~/issuable/popover/queries/milestone.query.graphql';
|
||||
import MilestonePopover from '~/issuable/popover/components/milestone_popover.vue';
|
||||
|
||||
describe('Milestone Popover', () => {
|
||||
const mockGroup = {
|
||||
id: 'gid://gitlab/Group/1',
|
||||
fullPath: 'gitlab-org',
|
||||
__typename: 'Group',
|
||||
};
|
||||
const mockProject = {
|
||||
id: 'gid://gitlab/Project/1',
|
||||
fullPath: 'gitlab-org/gitlab-test',
|
||||
__typename: 'Project',
|
||||
};
|
||||
const mockStats = {
|
||||
closedIssuesCount: 2,
|
||||
totalIssuesCount: 3,
|
||||
__typename: 'MilestoneStats',
|
||||
};
|
||||
|
||||
const mockMilestoneResponse = {
|
||||
data: {
|
||||
milestone: {
|
||||
id: 'gid://gitlab/Milestone/65',
|
||||
title: '16.11',
|
||||
expired: false,
|
||||
upcoming: false,
|
||||
createdAt: '2024-04-08T07:40:06Z',
|
||||
startDate: '2024-04-01',
|
||||
dueDate: '2024-04-30',
|
||||
groupMilestone: false,
|
||||
group: null,
|
||||
projectMilestone: true,
|
||||
project: mockProject,
|
||||
state: 'active',
|
||||
stats: mockStats,
|
||||
__typename: 'Milestone',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockMilestone = mockMilestoneResponse.data.milestone;
|
||||
let wrapper;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const mountComponent = ({
|
||||
queryResponse = jest.fn().mockResolvedValue(mockMilestoneResponse),
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(MilestonePopover, {
|
||||
apolloProvider: createMockApollo([[milestoneQuery, queryResponse]]),
|
||||
propsData: {
|
||||
target: document.createElement('a'),
|
||||
milestoneId: '65',
|
||||
cachedTitle: '%16.11',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findStateBadge = () => wrapper.findComponent(GlBadge);
|
||||
const findMilestoneTimeframe = () => wrapper.findByTestId('milestone-timeframe');
|
||||
const findMilestoneProgress = () => wrapper.findByTestId('milestone-progress');
|
||||
|
||||
describe('while popover is loading', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('shows icon and text', () => {
|
||||
const milestoneEl = wrapper.findByTestId('milestone-label');
|
||||
const milestoneIcon = milestoneEl.findComponent(GlIcon);
|
||||
|
||||
expect(milestoneEl.exists()).toBe(true);
|
||||
expect(milestoneIcon.exists()).toBe(true);
|
||||
expect(milestoneIcon.props('name')).toBe('milestone');
|
||||
expect(milestoneEl.text()).toBe('Milestone');
|
||||
});
|
||||
|
||||
it('shows skeleton-loader', () => {
|
||||
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows cached title', () => {
|
||||
expect(wrapper.find('h5').text()).toBe('16.11');
|
||||
});
|
||||
|
||||
it('does not show state badge or dates', () => {
|
||||
expect(findStateBadge().exists()).toBe(false);
|
||||
expect(findMilestoneTimeframe().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when popover contents are loaded', () => {
|
||||
// Set current date to 10th April 2024
|
||||
useFakeDate(2024, 3, 10);
|
||||
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it.each`
|
||||
expired | upcoming | state | expectedVariant | expectedText
|
||||
${false} | ${false} | ${'closed'} | ${'danger'} | ${'Closed'}
|
||||
${true} | ${false} | ${'active'} | ${'warning'} | ${'Expired'}
|
||||
${false} | ${true} | ${'active'} | ${'muted'} | ${'Upcoming'}
|
||||
${false} | ${false} | ${'active'} | ${'success'} | ${'Active'}
|
||||
`(
|
||||
'shows state badge with variant $expectedVariant and text $expectedText',
|
||||
async ({ expired, upcoming, state, expectedVariant, expectedText }) => {
|
||||
mountComponent({
|
||||
queryResponse: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
milestone: {
|
||||
...mockMilestone,
|
||||
expired,
|
||||
upcoming,
|
||||
state,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findStateBadge().props('variant')).toBe(expectedVariant);
|
||||
expect(findStateBadge().text()).toBe(expectedText);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
startDate | dueDate | expectedText
|
||||
${'2024-04-01'} | ${'2024-04-30'} | ${'Apr 1 – Apr 30, 2024'}
|
||||
${'2024-03-01'} | ${null} | ${'Started Mar 1, 2024'}
|
||||
${'2024-04-20'} | ${null} | ${'Starts Apr 20, 2024'}
|
||||
${null} | ${'2024-04-20'} | ${'Ends Apr 20, 2024'}
|
||||
${null} | ${'2024-02-20'} | ${'Ended Feb 20, 2024'}
|
||||
`(
|
||||
'shows timeframe text when startDate is $startDate and dueDate is $dueDate',
|
||||
async ({ startDate, dueDate, expectedText }) => {
|
||||
mountComponent({
|
||||
queryResponse: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
milestone: {
|
||||
...mockMilestone,
|
||||
startDate,
|
||||
dueDate,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findMilestoneTimeframe().text()).toBe(`· ${expectedText}`);
|
||||
},
|
||||
);
|
||||
|
||||
it('shows progress bar and percentage completion', () => {
|
||||
const progressEl = findMilestoneProgress();
|
||||
const progressBar = progressEl.findComponent(GlProgressBar);
|
||||
expect(progressBar.attributes()).toMatchObject({
|
||||
value: '66',
|
||||
variant: 'primary',
|
||||
});
|
||||
expect(progressEl.find('span').text()).toBe('66% complete');
|
||||
});
|
||||
|
||||
it('does not show progress when there are no issues associated with the milestone', async () => {
|
||||
mountComponent({
|
||||
queryResponse: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
milestone: {
|
||||
...mockMilestone,
|
||||
stats: {
|
||||
closedIssuesCount: 0,
|
||||
totalIssuesCount: 0,
|
||||
__typename: 'MilestoneStats',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findMilestoneProgress().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it.each`
|
||||
groupMilestone | projectMilestone | group | project | iconName | fullPath
|
||||
${false} | ${true} | ${null} | ${mockProject} | ${'project'} | ${mockProject.fullPath}
|
||||
${true} | ${false} | ${mockGroup} | ${null} | ${'group'} | ${mockGroup.fullPath}
|
||||
`(
|
||||
'shows milestone parent icon as $iconName and full path',
|
||||
async ({ groupMilestone, group, projectMilestone, project, iconName, fullPath }) => {
|
||||
mountComponent({
|
||||
queryResponse: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
milestone: {
|
||||
...mockMilestone,
|
||||
groupMilestone,
|
||||
projectMilestone,
|
||||
group,
|
||||
project,
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
const pathEl = wrapper.findByTestId('milestone-path');
|
||||
const parentTypeIcon = pathEl.findComponent(GlIcon);
|
||||
|
||||
expect(parentTypeIcon.props('name')).toBe(iconName);
|
||||
expect(pathEl.find('span').text()).toBe(fullPath);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -33,13 +33,17 @@ exports[`NpmInstallation renders all the messages 1`] = `
|
|||
trackingaction="copy_npm_setup_command"
|
||||
trackinglabel="code_instruction"
|
||||
/>
|
||||
You may also need to setup authentication using an auth token.
|
||||
<gl-link-stub
|
||||
href="/help/user/packages/npm_registry/index"
|
||||
target="_blank"
|
||||
<span
|
||||
class="gl-text-secondary"
|
||||
>
|
||||
See the documentation
|
||||
</gl-link-stub>
|
||||
to find out more.
|
||||
You may also need to setup authentication using an auth token.
|
||||
<gl-link-stub
|
||||
href="/help/user/packages/npm_registry/index"
|
||||
target="_blank"
|
||||
>
|
||||
See the documentation
|
||||
</gl-link-stub>
|
||||
to find out more.
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
setSortPreferenceMutationResponse,
|
||||
setSortPreferenceMutationResponseWithErrors,
|
||||
} from 'jest/issues/list/mock_data';
|
||||
import { STATUS_OPEN } from '~/issues/constants';
|
||||
import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
|
||||
import { CREATED_DESC, UPDATED_DESC } from '~/issues/list/constants';
|
||||
import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
|
||||
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
|
||||
|
|
@ -93,7 +93,11 @@ describe('WorkItemsListApp component', () => {
|
|||
it('fetches work items', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(defaultQueryHandler).toHaveBeenCalledWith({ fullPath: 'full/path', sort: CREATED_DESC });
|
||||
expect(defaultQueryHandler).toHaveBeenCalledWith({
|
||||
fullPath: 'full/path',
|
||||
sort: CREATED_DESC,
|
||||
state: STATUS_OPEN,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is an error fetching work items', () => {
|
||||
|
|
@ -118,6 +122,19 @@ describe('WorkItemsListApp component', () => {
|
|||
});
|
||||
|
||||
describe('events', () => {
|
||||
describe('when "click-tab" event is emitted by IssuableList', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
await waitForPromises();
|
||||
|
||||
findIssuableList().vm.$emit('click-tab', STATUS_CLOSED);
|
||||
});
|
||||
|
||||
it('updates ui to the new tab', () => {
|
||||
expect(findIssuableList().props('currentTab')).toBe(STATUS_CLOSED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "sort" event is emitted by IssuableList', () => {
|
||||
it.each(Object.keys(urlSortParams))(
|
||||
'updates to the new sort when payload is `%s`',
|
||||
|
|
@ -132,10 +149,9 @@ describe('WorkItemsListApp component', () => {
|
|||
findIssuableList().vm.$emit('sort', sortKey);
|
||||
await waitForPromises();
|
||||
|
||||
expect(defaultQueryHandler).toHaveBeenCalledWith({
|
||||
fullPath: 'full/path',
|
||||
sort: sortKey,
|
||||
});
|
||||
expect(defaultQueryHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ sort: sortKey }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::ExtensionsMarketplaceOptInStatusEnum, feature_category: :web_ide do
|
||||
specify { expect(described_class.graphql_name).to eq('ExtensionsMarketplaceOptInStatus') }
|
||||
|
||||
it 'exposes all the existing extensions_marketplace_opt_in_status values' do
|
||||
expect(described_class.values.keys).to contain_exactly('UNSET', 'ENABLED', 'DISABLED')
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,7 @@ RSpec.describe Types::UserPreferencesType, feature_category: :user_profile do
|
|||
issues_sort
|
||||
visibility_pipeline_id_type
|
||||
use_web_ide_extension_marketplace
|
||||
extensions_marketplace_opt_in_status
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
|||
|
|
@ -1013,10 +1013,6 @@ SystemNoteMetadata:
|
|||
- updated_at
|
||||
ProjectSecuritySetting:
|
||||
- project_id
|
||||
- auto_fix_container_scanning
|
||||
- auto_fix_dast
|
||||
- auto_fix_dependency_scanning
|
||||
- auto_fix_sast
|
||||
- created_at
|
||||
- updated_at
|
||||
IssuableSla:
|
||||
|
|
|
|||
|
|
@ -73,6 +73,19 @@ RSpec.describe UserPreference, feature_category: :user_profile do
|
|||
it { is_expected.to define_enum_for(:visibility_pipeline_id_type).with_values(id: 0, iid: 1) }
|
||||
end
|
||||
|
||||
describe 'extensions_marketplace_opt_in_status' do
|
||||
it 'is set to 0 by default' do
|
||||
pref = described_class.new
|
||||
|
||||
expect(pref.extensions_marketplace_opt_in_status).to eq('unset')
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected
|
||||
.to define_enum_for(:extensions_marketplace_opt_in_status).with_values(unset: 0, enabled: 1, disabled: 2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'user belongs to the home organization' do
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
|
||||
|
|
|
|||
|
|
@ -3030,66 +3030,28 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
describe '.filter_items' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
let(:user) { double }
|
||||
|
||||
it 'filters by active users by default' do
|
||||
expect(described_class).to receive(:active_without_ghosts).and_return([user])
|
||||
|
||||
expect(described_class.filter_items(nil)).to include user
|
||||
where(:scope, :filter_name) do
|
||||
:active_without_ghosts | nil
|
||||
:admins | 'admins'
|
||||
:blocked | 'blocked'
|
||||
:banned | 'banned'
|
||||
:blocked_pending_approval | 'blocked_pending_approval'
|
||||
:deactivated | 'deactivated'
|
||||
:without_two_factor | 'two_factor_disabled'
|
||||
:with_two_factor | 'two_factor_enabled'
|
||||
:without_projects | 'wop'
|
||||
:trusted | 'trusted'
|
||||
:external | 'external'
|
||||
end
|
||||
|
||||
it 'filters by admins' do
|
||||
expect(described_class).to receive(:admins).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('admins')).to include user
|
||||
end
|
||||
|
||||
it 'filters by blocked' do
|
||||
expect(described_class).to receive(:blocked).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('blocked')).to include user
|
||||
end
|
||||
|
||||
it 'filters by banned' do
|
||||
expect(described_class).to receive(:banned).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('banned')).to include user
|
||||
end
|
||||
|
||||
it 'filters by blocked pending approval' do
|
||||
expect(described_class).to receive(:blocked_pending_approval).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('blocked_pending_approval')).to include user
|
||||
end
|
||||
|
||||
it 'filters by deactivated' do
|
||||
expect(described_class).to receive(:deactivated).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('deactivated')).to include user
|
||||
end
|
||||
|
||||
it 'filters by two_factor_disabled' do
|
||||
expect(described_class).to receive(:without_two_factor).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('two_factor_disabled')).to include user
|
||||
end
|
||||
|
||||
it 'filters by two_factor_enabled' do
|
||||
expect(described_class).to receive(:with_two_factor).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('two_factor_enabled')).to include user
|
||||
end
|
||||
|
||||
it 'filters by wop' do
|
||||
expect(described_class).to receive(:without_projects).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('wop')).to include user
|
||||
end
|
||||
|
||||
it 'filters by trusted' do
|
||||
expect(described_class).to receive(:trusted).and_return([user])
|
||||
|
||||
expect(described_class.filter_items('trusted')).to include user
|
||||
with_them do
|
||||
it 'uses a certain scope for the given filter name' do
|
||||
expect(described_class).to receive(scope).and_return([user])
|
||||
expect(described_class.filter_items(filter_name)).to include user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -4050,22 +4012,36 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
|
||||
context 'crowd synchronized user' do
|
||||
describe '#crowd_user?' do
|
||||
it 'is true if provider is crowd' do
|
||||
user = create(:omniauth_user, provider: 'crowd')
|
||||
shared_examples_for 'User#crowd_user?' do
|
||||
subject { user.crowd_user? }
|
||||
|
||||
expect(user.crowd_user?).to be_truthy
|
||||
context 'when provider is not crowd' do
|
||||
let(:user) { create(:omniauth_user, provider: 'other-provider') }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when provider is crowd' do
|
||||
let(:user) { create(:omniauth_user, provider: 'crowd') }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when extern_uid is not provided' do
|
||||
let(:user) { create(:omniauth_user, extern_uid: nil) }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
it 'is false for other providers' do
|
||||
user = create(:omniauth_user, provider: 'other-provider')
|
||||
it_behaves_like 'User#crowd_user?'
|
||||
|
||||
expect(user.crowd_user?).to be_falsey
|
||||
end
|
||||
|
||||
it 'is false if no extern_uid is provided' do
|
||||
user = create(:omniauth_user, extern_uid: nil)
|
||||
|
||||
expect(user.crowd_user?).to be_falsey
|
||||
context 'when identities are loaded' do
|
||||
it_behaves_like 'User#crowd_user?' do
|
||||
before do
|
||||
user.identities.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8536,4 +8512,32 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
it_behaves_like 'it does not add account pending deletion error message'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ldap_sync_time' do
|
||||
let(:user) { build(:user) }
|
||||
|
||||
it 'is equal to one hour' do
|
||||
expect(user.ldap_sync_time).to eq(1.hour)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_leave_project?' do
|
||||
let_it_be(:user) { create :user, :with_namespace }
|
||||
let_it_be(:user_namespace_project) { create(:project, namespace: user.namespace) }
|
||||
let_it_be(:user_member_project) { create(:project, :in_group, developers: [user]) }
|
||||
|
||||
subject { user.can_leave_project?(project) }
|
||||
|
||||
context "when the project is in the user's namespace" do
|
||||
let(:project) { user_namespace_project }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when the user is a member of the project' do
|
||||
let(:project) { user_member_project }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1114,7 +1114,6 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
|
|||
end
|
||||
end
|
||||
|
||||
# This block can be removed when packages_dependency_proxy_pass_token_to_policy is rolled out
|
||||
describe 'dependency proxy' do
|
||||
RSpec.shared_examples 'disabling admin_package feature flag' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Policies::DependencyProxy::GroupPolicy, feature_category: :system_access do
|
||||
subject { described_class.new(auth_token, group.dependency_proxy_for_containers_policy_subject) }
|
||||
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:non_group_member) { create(:user) }
|
||||
let_it_be(:group, refind: true) { create(:group, :private, :owner_subgroup_creation_only) }
|
||||
let_it_be(:current_user) { guest }
|
||||
|
||||
before do
|
||||
group.add_guest(guest)
|
||||
end
|
||||
|
||||
describe 'dependency proxy' do
|
||||
shared_examples 'disallows dependency proxy read access' do
|
||||
it { is_expected.to be_disallowed(:read_dependency_proxy) }
|
||||
end
|
||||
|
||||
shared_examples 'allows dependency proxy read access' do
|
||||
it { is_expected.to be_allowed(:read_dependency_proxy) }
|
||||
end
|
||||
|
||||
context 'with feature disabled' do
|
||||
let_it_be(:auth_token) { create(:personal_access_token, user: current_user) }
|
||||
|
||||
before do
|
||||
stub_config(dependency_proxy: { enabled: false })
|
||||
end
|
||||
|
||||
it_behaves_like 'disallows dependency proxy read access'
|
||||
end
|
||||
|
||||
context 'with feature enabled' do
|
||||
let_it_be(:auth_token) { create(:personal_access_token, user: current_user) }
|
||||
|
||||
before do
|
||||
stub_config(dependency_proxy: { enabled: true }, registry: { enabled: true })
|
||||
end
|
||||
|
||||
context 'with a human user personal access token' do
|
||||
subject { described_class.new(auth_token, group.dependency_proxy_for_containers_policy_subject) }
|
||||
|
||||
context 'when not a member of the group' do
|
||||
let_it_be(:current_user) { non_group_member }
|
||||
let_it_be(:auth_token) { create(:personal_access_token, user: current_user) }
|
||||
|
||||
it_behaves_like 'disallows dependency proxy read access'
|
||||
end
|
||||
|
||||
context 'when a member of the group' do
|
||||
let_it_be(:current_user) { guest }
|
||||
let_it_be(:auth_token) { create(:personal_access_token, user: current_user) }
|
||||
|
||||
it_behaves_like 'allows dependency proxy read access'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a deploy token user' do
|
||||
before do
|
||||
create(:group_deploy_token, group: group, deploy_token: auth_token)
|
||||
end
|
||||
|
||||
context 'with insufficient scopes' do
|
||||
let_it_be(:auth_token) { create(:deploy_token, :group, user: current_user) }
|
||||
|
||||
it_behaves_like 'disallows dependency proxy read access'
|
||||
end
|
||||
|
||||
context 'with sufficient scopes' do
|
||||
let_it_be(:auth_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
|
||||
it_behaves_like 'allows dependency proxy read access'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group access token user' do
|
||||
let_it_be(:bot_user) { create(:user, :project_bot) }
|
||||
let_it_be(:auth_token) do
|
||||
create(:personal_access_token, user: bot_user, scopes: [Gitlab::Auth::READ_API_SCOPE])
|
||||
end
|
||||
|
||||
context 'when not a member of the group' do
|
||||
it_behaves_like 'disallows dependency proxy read access'
|
||||
end
|
||||
|
||||
context 'when a member of the group' do
|
||||
before do
|
||||
group.add_guest(bot_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'allows dependency proxy read access'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,6 +11,7 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi
|
|||
|
||||
let(:input) do
|
||||
{
|
||||
'extensionsMarketplaceOptInStatus' => 'ENABLED',
|
||||
'issuesSort' => sort_value,
|
||||
'visibilityPipelineIdType' => 'IID',
|
||||
'useWebIdeExtensionMarketplace' => true
|
||||
|
|
@ -25,11 +26,13 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi
|
|||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(mutation_response['userPreferences']['extensionsMarketplaceOptInStatus']).to eq('ENABLED')
|
||||
expect(mutation_response['userPreferences']['issuesSort']).to eq(sort_value)
|
||||
expect(mutation_response['userPreferences']['visibilityPipelineIdType']).to eq('IID')
|
||||
expect(mutation_response['userPreferences']['useWebIdeExtensionMarketplace']).to eq(true)
|
||||
|
||||
expect(current_user.user_preference.persisted?).to eq(true)
|
||||
expect(current_user.user_preference.extensions_marketplace_opt_in_status).to eq('enabled')
|
||||
expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s)
|
||||
expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid')
|
||||
expect(current_user.user_preference.use_web_ide_extension_marketplace).to eq(true)
|
||||
|
|
@ -39,6 +42,7 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi
|
|||
context 'when user has existing preference' do
|
||||
let(:init_user_preference) do
|
||||
{
|
||||
extensions_marketplace_opt_in_status: 'enabled',
|
||||
issues_sort: Types::IssueSortEnum.values['TITLE_DESC'].value,
|
||||
visibility_pipeline_id_type: 'id',
|
||||
use_web_ide_extension_marketplace: true
|
||||
|
|
@ -65,6 +69,7 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi
|
|||
context 'when input has nil attributes' do
|
||||
let(:input) do
|
||||
{
|
||||
'extensionsMarketplaceOptInStatus' => nil,
|
||||
'issuesSort' => nil,
|
||||
'visibilityPipelineIdType' => nil,
|
||||
'useWebIdeExtensionMarketplace' => nil
|
||||
|
|
@ -80,6 +85,7 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi
|
|||
# These are nullable and are exepcted to change
|
||||
issues_sort: nil,
|
||||
# These should not have changed
|
||||
extensions_marketplace_opt_in_status: init_user_preference[:extensions_marketplace_opt_in_status],
|
||||
visibility_pipeline_id_type: init_user_preference[:visibility_pipeline_id_type],
|
||||
use_web_ide_extension_marketplace: init_user_preference[:use_web_ide_extension_marketplace]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ RSpec.describe JwtController, feature_category: :system_access do
|
|||
context 'project with enabled CI' do
|
||||
subject! { get '/jwt/auth', params: parameters, headers: headers }
|
||||
|
||||
it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build)).permit!) }
|
||||
it { expect(service_class).to have_received(:new).with(project, user, ActionController::Parameters.new(parameters.merge(auth_type: :build, raw_token: build.token)).permit!) }
|
||||
|
||||
it_behaves_like 'user logging'
|
||||
end
|
||||
|
|
@ -119,7 +119,7 @@ RSpec.describe JwtController, feature_category: :system_access do
|
|||
.with(
|
||||
nil,
|
||||
nil,
|
||||
ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token)).permit!
|
||||
ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token, auth_type: :deploy_token, raw_token: deploy_token.token)).permit!
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ RSpec.describe JwtController, feature_category: :system_access do
|
|||
|
||||
subject! { get '/jwt/auth', params: parameters, headers: headers }
|
||||
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap)).permit!) }
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)).permit!) }
|
||||
|
||||
it_behaves_like 'rejecting a blocked user'
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ RSpec.describe JwtController, feature_category: :system_access do
|
|||
ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
|
||||
end
|
||||
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
|
||||
|
||||
it_behaves_like 'user logging'
|
||||
end
|
||||
|
|
@ -197,7 +197,7 @@ RSpec.describe JwtController, feature_category: :system_access do
|
|||
ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
|
||||
end
|
||||
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
|
||||
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap, raw_token: user.password)) }
|
||||
end
|
||||
|
||||
context 'when user has 2FA enabled' do
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:params) { {} }
|
||||
|
||||
let(:authentication_abilities) { [] }
|
||||
let(:authentication_abilities) { nil }
|
||||
let(:service) { described_class.new(nil, user, params) }
|
||||
|
||||
before do
|
||||
|
|
@ -48,57 +48,31 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
|
|||
end
|
||||
|
||||
context 'with a deploy token' do
|
||||
let(:user) { nil }
|
||||
let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:params) { { deploy_token: deploy_token } }
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'deploy_token'
|
||||
|
||||
context 'with packages_dependency_proxy_containers_scope_check disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_containers_scope_check: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'deploy_token'
|
||||
end
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'deploy_token'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a human user' do
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a personal access token user' do
|
||||
let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:params) { { raw_token: token.token } }
|
||||
context 'all other user types' do
|
||||
User::USER_TYPES.except(:human, :project_bot).each_value do |user_type|
|
||||
context "with user_type #{user_type}" do
|
||||
before do
|
||||
user.update!(user_type: user_type)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'personal_access_token'
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group access token' do
|
||||
let_it_be(:user) { create(:user, :project_bot) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be_with_reload(:token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:params) { { raw_token: token.token } }
|
||||
|
||||
before_all do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
context 'with insufficient authentication abilities' do
|
||||
it_behaves_like 'returning', status: 403, message: 'access forbidden'
|
||||
|
|
@ -108,7 +82,7 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
|
|||
stub_feature_flags(packages_dependency_proxy_containers_scope_check: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'group_access_token'
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -118,15 +92,7 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
|
|||
|
||||
subject { service.execute(authentication_abilities: authentication_abilities) }
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'group_access_token'
|
||||
|
||||
context 'with packages_dependency_proxy_pass_token_to_policy disabled' do
|
||||
before do
|
||||
stub_feature_flags(packages_dependency_proxy_pass_token_to_policy: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
end
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
|
||||
context 'revoked' do
|
||||
before do
|
||||
|
|
@ -146,18 +112,6 @@ RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :de
|
|||
end
|
||||
end
|
||||
|
||||
context 'all other user types' do
|
||||
User::USER_TYPES.except(:human, :project_bot).each_value do |user_type|
|
||||
context "with user_type #{user_type}" do
|
||||
before do
|
||||
user.update!(user_type: user_type)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning a token with an encoded field', 'user_id'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode(token)
|
||||
DependencyProxy::AuthTokenService.new(token).execute
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,36 +5,8 @@ RSpec.describe DependencyProxy::AuthTokenService, feature_category: :dependency_
|
|||
include DependencyProxyHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:group_access_token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:deploy_token) { create(:deploy_token) }
|
||||
|
||||
shared_examples 'handling token errors' do
|
||||
context 'with a decoding error' do
|
||||
before do
|
||||
allow(JWT).to receive(:decode).and_raise(JWT::DecodeError)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an immature signature error' do
|
||||
before do
|
||||
allow(JWT).to receive(:decode).and_raise(JWT::ImmatureSignature)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an expired signature error' do
|
||||
it 'returns nil' do
|
||||
travel_to(Time.zone.now + Auth::DependencyProxyAuthenticationService.token_expire_at + 1.minute) do
|
||||
expect(subject).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.user_or_deploy_token_from_jwt' do
|
||||
subject { described_class.user_or_deploy_token_from_jwt(token.encoded) }
|
||||
|
||||
|
|
@ -100,84 +72,4 @@ RSpec.describe DependencyProxy::AuthTokenService, feature_category: :dependency_
|
|||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.user_or_token_from_jwt' do
|
||||
subject { described_class.user_or_token_from_jwt(token.encoded) }
|
||||
|
||||
context 'with a user' do
|
||||
let_it_be(:token) { build_jwt(user) }
|
||||
|
||||
it { is_expected.to eq(user) }
|
||||
|
||||
context 'with an invalid user id' do
|
||||
let_it_be(:token) { build_jwt { |jwt| jwt['user_id'] = 'this_is_not_a_user_id' } }
|
||||
|
||||
it 'raises an not found error' do
|
||||
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'handling token errors'
|
||||
end
|
||||
|
||||
context 'with a personal access token' do
|
||||
let_it_be(:token) { build_jwt(personal_access_token) }
|
||||
|
||||
it { is_expected.to eq(personal_access_token) }
|
||||
|
||||
context 'with an inactive token' do
|
||||
before do
|
||||
personal_access_token.revoke!
|
||||
end
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an invalid token' do
|
||||
let_it_be(:token) { build_jwt { |jwt| jwt['personal_access_token'] = 'this_is_not_a_token' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a group access token' do
|
||||
let_it_be(:token) { build_jwt(group_access_token) }
|
||||
|
||||
it { is_expected.to eq(group_access_token) }
|
||||
|
||||
context 'with an inactive token' do
|
||||
before do
|
||||
group_access_token.revoke!
|
||||
end
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an invalid token' do
|
||||
let_it_be(:token) { build_jwt { |jwt| jwt['group_access_token'] = 'this_is_not_a_token' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a deploy token' do
|
||||
let_it_be(:token) { build_jwt(deploy_token) }
|
||||
|
||||
it { is_expected.to eq(deploy_token) }
|
||||
|
||||
context 'with an invalid token' do
|
||||
let_it_be(:token) { build_jwt { |jwt| jwt['deploy_token'] = 'this_is_not_a_token' } }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
it_behaves_like 'handling token errors'
|
||||
end
|
||||
|
||||
context 'with an empty token payload' do
|
||||
let_it_be(:token) { build_jwt(nil) }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
|
|||
"ENABLE_SIDEKIQ_CLUSTER" => "1",
|
||||
"SIDEKIQ_WORKER_ID" => "0"
|
||||
},
|
||||
"bundle", "exec", "sidekiq", "-c20", "-eproduction", "-t25", "-gqueues:foo", "-rfoo/bar", "-qfoo,1", process_options
|
||||
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:foo", "-rfoo/bar", "-qfoo,1", process_options
|
||||
).and_return(1)
|
||||
expect(Process).to receive(:detach).ordered.with(1)
|
||||
|
||||
|
|
@ -27,21 +27,23 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
|
|||
"ENABLE_SIDEKIQ_CLUSTER" => "1",
|
||||
"SIDEKIQ_WORKER_ID" => "1"
|
||||
},
|
||||
"bundle", "exec", "sidekiq", "-c20", "-eproduction", "-t25", "-gqueues:bar,baz", "-rfoo/bar", "-qbar,1", "-qbaz,1", process_options
|
||||
"bundle", "exec", "sidekiq", "-c10", "-eproduction", "-t25", "-gqueues:bar,baz", "-rfoo/bar", "-qbar,1", "-qbaz,1", process_options
|
||||
).and_return(2)
|
||||
expect(Process).to receive(:detach).ordered.with(2)
|
||||
|
||||
described_class.start([%w[foo], %w[bar baz]], env: :production, directory: 'foo/bar', concurrency: 20)
|
||||
described_class.start([%w[foo], %w[bar baz]], env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 10)
|
||||
end
|
||||
|
||||
it 'starts Sidekiq with the given queues and sensible default options' do
|
||||
expected_options = {
|
||||
env: :development,
|
||||
directory: an_instance_of(String),
|
||||
max_concurrency: 20,
|
||||
min_concurrency: 0,
|
||||
worker_id: an_instance_of(Integer),
|
||||
timeout: 25,
|
||||
dryrun: false,
|
||||
concurrency: 20
|
||||
concurrency: 0
|
||||
}
|
||||
|
||||
expect(described_class).to receive(:start_sidekiq).ordered.with(%w[foo bar baz], expected_options)
|
||||
|
|
@ -54,7 +56,7 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
|
|||
describe '.start_sidekiq' do
|
||||
let(:first_worker_id) { 0 }
|
||||
let(:options) do
|
||||
{ env: :production, directory: 'foo/bar', worker_id: first_worker_id, timeout: 10, dryrun: false, concurrency: 20 }
|
||||
{ env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 0, worker_id: first_worker_id, timeout: 10, dryrun: false, concurrency: 0 }
|
||||
end
|
||||
|
||||
let(:env) { { "ENABLE_SIDEKIQ_CLUSTER" => "1", "SIDEKIQ_WORKER_ID" => first_worker_id.to_s } }
|
||||
|
|
@ -97,4 +99,32 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
|
|||
expect(described_class.count_by_queue(queues)).to eq(%w[foo] => 2, %w[bar baz] => 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.concurrency' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:queue_count, :min, :max, :fixed_concurrency, :expected) do
|
||||
# without fixed concurrency
|
||||
2 | 0 | 0 | 0 | 3 # No min or max specified
|
||||
2 | 0 | 9 | 0 | 3 # No min specified, value < max
|
||||
2 | 1 | 4 | 0 | 3 # Value between min and max
|
||||
2 | 4 | 5 | 0 | 4 # Value below range
|
||||
5 | 2 | 3 | 0 | 3 # Value above range
|
||||
2 | 1 | 1 | 0 | 1 # Value above explicit setting (min == max)
|
||||
0 | 3 | 3 | 0 | 3 # Value below explicit setting (min == max)
|
||||
1 | 4 | 3 | 0 | 3 # Min greater than max
|
||||
|
||||
# with fixed concurrency, expected always equal to fixed_concurrency
|
||||
1 | 0 | 20 | 20 | 20
|
||||
1 | 0 | 20 | 10 | 10
|
||||
1 | 20 | 20 | 10 | 10
|
||||
5 | 0 | 0 | 10 | 10
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:queues) { Array.new(queue_count) }
|
||||
|
||||
it { expect(described_class.concurrency(queues, min, max, fixed_concurrency)).to eq(expected) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,14 +32,13 @@ module DependencyProxyHelpers
|
|||
.to_return(status: status, body: body)
|
||||
end
|
||||
|
||||
def build_jwt(user_or_token = nil, expire_time: nil)
|
||||
def build_jwt(user = nil, expire_time: nil)
|
||||
JSONWebToken::HMACToken.new(::Auth::DependencyProxyAuthenticationService.secret).tap do |jwt|
|
||||
if block_given?
|
||||
yield(jwt)
|
||||
else
|
||||
jwt['user_id'] = user_or_token.id if user_or_token.is_a?(User)
|
||||
jwt['personal_access_token'] = user_or_token.token if user_or_token.is_a?(PersonalAccessToken)
|
||||
jwt['deploy_token'] = user_or_token.token if user_or_token.is_a?(DeployToken)
|
||||
jwt['user_id'] = user.id if user.is_a?(User)
|
||||
jwt['deploy_token'] = user.token if user.is_a?(DeployToken)
|
||||
jwt.expire_time = expire_time || jwt.issued_at + 1.minute
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'layouts/project', feature_category: :groups_and_projects do
|
||||
let(:invite_member) { true }
|
||||
let(:project) { build_stubbed(:project) }
|
||||
|
||||
before do
|
||||
allow(view).to receive(:can_admin_project_member?).and_return(invite_member)
|
||||
assign(:project, project)
|
||||
assign(:project, build_stubbed(:project))
|
||||
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(build_stubbed(:user)))
|
||||
end
|
||||
|
||||
|
|
@ -27,24 +26,4 @@ RSpec.describe 'layouts/project', feature_category: :groups_and_projects do
|
|||
|
||||
it { is_expected.not_to have_selector('.js-invite-members-modal') }
|
||||
end
|
||||
|
||||
context 'with no transfer in progress' do
|
||||
before do
|
||||
allow(project).to receive(:git_transfer_in_progress?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not render the alert' do
|
||||
is_expected.not_to have_css('[data-testid="transferring-alert"]')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with transfer in progress' do
|
||||
before do
|
||||
allow(project).to receive(:git_transfer_in_progress?).and_return(true)
|
||||
end
|
||||
|
||||
it 'renders the alert' do
|
||||
is_expected.to have_css('[data-testid="transferring-alert"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker, featur
|
|||
expect(described_class.get_deduplication_options).to include({ if_deduplicated: :reschedule_once })
|
||||
end
|
||||
|
||||
it 'has an option to deduplicate scheduled jobs' do
|
||||
expect(described_class.get_deduplication_options).to include({ including_scheduled: true })
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
subject { worker.perform(resource_group_id) }
|
||||
|
||||
|
|
|
|||
428
yarn.lock
428
yarn.lock
|
|
@ -1092,120 +1092,125 @@
|
|||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||
|
||||
"@esbuild/android-arm64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz#276c5f99604054d3dbb733577e09adae944baa90"
|
||||
integrity sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==
|
||||
"@esbuild/aix-ppc64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
|
||||
integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
|
||||
|
||||
"@esbuild/android-arm@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.5.tgz#4a3cbf14758166abaae8ba9c01a80e68342a4eec"
|
||||
integrity sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==
|
||||
"@esbuild/android-arm64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
|
||||
integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
|
||||
|
||||
"@esbuild/android-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.5.tgz#21a3d11cd4613d2d3c5ccb9e746c254eb9265b0a"
|
||||
integrity sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==
|
||||
"@esbuild/android-arm@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
|
||||
integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
|
||||
|
||||
"@esbuild/darwin-arm64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz#714cb839f467d6a67b151ee8255886498e2b9bf6"
|
||||
integrity sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==
|
||||
"@esbuild/android-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
|
||||
integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
|
||||
|
||||
"@esbuild/darwin-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz#2c553e97a6d2b4ae76a884e35e6cbab85a990bbf"
|
||||
integrity sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==
|
||||
"@esbuild/darwin-arm64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
|
||||
integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz#d554f556718adb31917a0da24277bf84b6ee87f3"
|
||||
integrity sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==
|
||||
"@esbuild/darwin-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
|
||||
integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
|
||||
|
||||
"@esbuild/freebsd-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz#288f7358a3bb15d99e73c65c9adaa3dabb497432"
|
||||
integrity sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==
|
||||
"@esbuild/freebsd-arm64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
|
||||
integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
|
||||
|
||||
"@esbuild/linux-arm64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz#95933ae86325c93cb6b5e8333d22120ecfdc901b"
|
||||
integrity sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==
|
||||
"@esbuild/freebsd-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
|
||||
integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
|
||||
|
||||
"@esbuild/linux-arm@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz#0acef93aa3e0579e46d33b666627bddb06636664"
|
||||
integrity sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==
|
||||
"@esbuild/linux-arm64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
|
||||
integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
|
||||
|
||||
"@esbuild/linux-ia32@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz#b6e5c9e80b42131cbd6b1ddaa48c92835f1ed67f"
|
||||
integrity sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==
|
||||
"@esbuild/linux-arm@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
|
||||
integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
|
||||
|
||||
"@esbuild/linux-ia32@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
|
||||
integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
|
||||
|
||||
"@esbuild/linux-loong64@0.14.54":
|
||||
version "0.14.54"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
|
||||
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
|
||||
|
||||
"@esbuild/linux-loong64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz#e5f0cf95a180158b01ff5f417da796a1c09dfbea"
|
||||
integrity sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==
|
||||
"@esbuild/linux-loong64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
|
||||
integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
|
||||
|
||||
"@esbuild/linux-mips64el@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz#ae36fb86c7d5f641f3a0c8472e83dcb6ea36a408"
|
||||
integrity sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==
|
||||
"@esbuild/linux-mips64el@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
|
||||
integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
|
||||
|
||||
"@esbuild/linux-ppc64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz#7960cb1666f0340ddd9eef7b26dcea3835d472d0"
|
||||
integrity sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==
|
||||
"@esbuild/linux-ppc64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
|
||||
integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
|
||||
|
||||
"@esbuild/linux-riscv64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz#32207df26af60a3a9feea1783fc21b9817bade19"
|
||||
integrity sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==
|
||||
"@esbuild/linux-riscv64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
|
||||
integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
|
||||
|
||||
"@esbuild/linux-s390x@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz#b38d5681db89a3723862dfa792812397b1510a7d"
|
||||
integrity sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==
|
||||
"@esbuild/linux-s390x@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
|
||||
integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
|
||||
|
||||
"@esbuild/linux-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz#46feba2ad041a241379d150f415b472fe3885075"
|
||||
integrity sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==
|
||||
"@esbuild/linux-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
|
||||
integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
|
||||
|
||||
"@esbuild/netbsd-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz#3b5c1fb068f26bfc681d31f682adf1bea4ef0702"
|
||||
integrity sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==
|
||||
"@esbuild/netbsd-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
|
||||
integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
|
||||
|
||||
"@esbuild/openbsd-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz#ca6830316ca68056c5c88a875f103ad3235e00db"
|
||||
integrity sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==
|
||||
"@esbuild/openbsd-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
|
||||
integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
|
||||
|
||||
"@esbuild/sunos-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz#9efc4eb9539a7be7d5a05ada52ee43cda0d8e2dd"
|
||||
integrity sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==
|
||||
"@esbuild/sunos-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
|
||||
integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
|
||||
|
||||
"@esbuild/win32-arm64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz#29f8184afa7a02a956ebda4ed638099f4b8ff198"
|
||||
integrity sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==
|
||||
"@esbuild/win32-arm64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
|
||||
integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
|
||||
|
||||
"@esbuild/win32-ia32@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz#f3de07afb292ecad651ae4bb8727789de2d95b05"
|
||||
integrity sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==
|
||||
"@esbuild/win32-ia32@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
|
||||
integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
|
||||
|
||||
"@esbuild/win32-x64@0.19.5":
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz#faad84c41ba12e3a0acb52571df9bff37bee75f6"
|
||||
integrity sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==
|
||||
"@esbuild/win32-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
|
||||
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
|
|
@ -1967,65 +1972,85 @@
|
|||
estree-walker "^2.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.4.1.tgz#f276b0fa322270aa42d1f56c982db6ef8d6a4393"
|
||||
integrity sha512-Ss4suS/sd+6xLRu+MLCkED2mUrAyqHmmvZB+zpzZ9Znn9S8wCkTQCJaQ8P8aHofnvG5L16u9MVnJjCqioPErwQ==
|
||||
"@rollup/rollup-android-arm-eabi@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz#bddf05c3387d02fac04b6b86b3a779337edfed75"
|
||||
integrity sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.4.1.tgz#f0492f00d18e1067785f8e820e137c00528c5e62"
|
||||
integrity sha512-sRSkGTvGsARwWd7TzC8LKRf8FiPn7257vd/edzmvG4RIr9x68KBN0/Ek48CkuUJ5Pj/Dp9vKWv6PEupjKWjTYA==
|
||||
"@rollup/rollup-android-arm64@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz#b26bd09de58704c0a45e3375b76796f6eda825e4"
|
||||
integrity sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.4.1.tgz#40443db7f4559171d797581e0618ec1a4c8dcee9"
|
||||
integrity sha512-nz0AiGrrXyaWpsmBXUGOBiRDU0wyfSXbFuF98pPvIO8O6auQsPG6riWsfQqmCCC5FNd8zKQ4JhgugRNAkBJ8mQ==
|
||||
"@rollup/rollup-darwin-arm64@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz#c5f3fd1aa285b6d33dda6e3f3ca395f8c37fd5ca"
|
||||
integrity sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.4.1.tgz#2868f37a9f9c2c22c091b6209f6ce7454437edf9"
|
||||
integrity sha512-Ogqvf4/Ve/faMaiPRvzsJEqajbqs00LO+8vtrPBVvLgdw4wBg6ZDXdkDAZO+4MLnrc8mhGV6VJAzYScZdPLtJg==
|
||||
"@rollup/rollup-darwin-x64@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz#8e4673734d7dc9d68f6d48e81246055cda0e840f"
|
||||
integrity sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.4.1.tgz#d78d7ad358d24058166ab5599de3dcb5ab951add"
|
||||
integrity sha512-9zc2tqlr6HfO+hx9+wktUlWTRdje7Ub15iJqKcqg5uJZ+iKqmd2CMxlgPpXi7+bU7bjfDIuvCvnGk7wewFEhCg==
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz#53ed38eb13b58ababdb55a7f66f0538a7f85dcba"
|
||||
integrity sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.4.1.tgz#5d07588b40a04f5b6fbd9e0169c8dc32c1c2ed21"
|
||||
integrity sha512-phLb1fN3rq2o1j1v+nKxXUTSJnAhzhU0hLrl7Qzb0fLpwkGMHDem+o6d+ZI8+/BlTXfMU4kVWGvy6g9k/B8L6Q==
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz#0706ee38330e267a5c9326956820f009cfb21fcd"
|
||||
integrity sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.4.1.tgz#d452e88a02755f449f6e98d4ce424d655ef42cfe"
|
||||
integrity sha512-M2sDtw4tf57VPSjbTAN/lz1doWUqO2CbQuX3L9K6GWIR5uw9j+ROKCvvUNBY8WUbMxwaoc8mH9HmmBKsLht7+w==
|
||||
"@rollup/rollup-linux-arm64-gnu@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz#426fce7b8b242ac5abd48a10a5020f5a468c6cb4"
|
||||
integrity sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.4.1.tgz#e8e8e87ab098784383a5ced4aa4bbfa7b2c92a4e"
|
||||
integrity sha512-mHIlRLX+hx+30cD6c4BaBOsSqdnCE4ok7/KDvjHYAHoSuveoMMxIisZFvcLhUnyZcPBXDGZTuBoalcuh43UfQQ==
|
||||
"@rollup/rollup-linux-arm64-musl@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz#65bf944530d759b50d7ffd00dfbdf4125a43406f"
|
||||
integrity sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.4.1.tgz#3e5da42626672e2d620ed12746158b0cf6143b23"
|
||||
integrity sha512-tB+RZuDi3zxFx7vDrjTNGVLu2KNyzYv+UY8jz7e4TMEoAj7iEt8Qk6xVu6mo3pgjnsHj6jnq3uuRsHp97DLwOA==
|
||||
"@rollup/rollup-linux-powerpc64le-gnu@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz#494ba3b31095e9a45df9c3f646d21400fb631a95"
|
||||
integrity sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.4.1.tgz#0f0d0c6b75c53643fab8238c76889a95bca3b9cc"
|
||||
integrity sha512-Hdn39PzOQowK/HZzYpCuZdJC91PE6EaGbTe2VCA9oq2u18evkisQfws0Smh9QQGNNRa/T7MOuGNQoLeXhhE3PQ==
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz#8b88ed0a40724cce04aa15374ebe5ba4092d679f"
|
||||
integrity sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.4.1.tgz#8bb9e8fbf0fdf96fe3bebcee23f5cfdbbd9a4a0a"
|
||||
integrity sha512-tLpKb1Elm9fM8c5w3nl4N1eLTP4bCqTYw9tqUBxX8/hsxqHO3dxc2qPbZ9PNkdK4tg4iLEYn0pOUnVByRd2CbA==
|
||||
"@rollup/rollup-linux-s390x-gnu@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz#09c9e5ec57a0f6ec3551272c860bb9a04b96d70f"
|
||||
integrity sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.4.1.tgz#8311b77e6cce322865ba12ada8c3779369610d18"
|
||||
integrity sha512-eAhItDX9yQtZVM3yvXS/VR3qPqcnXvnLyx1pLXl4JzyNMBNO3KC986t/iAg2zcMzpAp9JSvxB5VZGnBiNoA98w==
|
||||
"@rollup/rollup-linux-x64-gnu@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz#197f27fd481ad9c861021d5cbbf21793922a631c"
|
||||
integrity sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz#5cc0522f4942f2df625e9bfb6fb02c6580ffbce6"
|
||||
integrity sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz#a648122389d23a7543b261fba082e65fefefe4f6"
|
||||
integrity sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz#34727b5c7953c35fc6e1ae4f770ad3a2025f8e03"
|
||||
integrity sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.14.3":
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz#5b2fb4d8cd44c05deef8a7b0e6deb9ccb8939d18"
|
||||
integrity sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==
|
||||
|
||||
"@sentry-internal/feedback@7.102.0":
|
||||
version "7.102.0"
|
||||
|
|
@ -2977,10 +3002,10 @@
|
|||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/estree@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
|
||||
integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
|
||||
"@types/estree@1.0.5", "@types/estree@^1.0.0":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
"@types/events@*":
|
||||
version "1.2.0"
|
||||
|
|
@ -6568,33 +6593,34 @@ esbuild@^0.14.14:
|
|||
esbuild-windows-64 "0.14.54"
|
||||
esbuild-windows-arm64 "0.14.54"
|
||||
|
||||
esbuild@^0.19.3:
|
||||
version "0.19.5"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.5.tgz#53a0e19dfbf61ba6c827d51a80813cf071239a8c"
|
||||
integrity sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==
|
||||
esbuild@^0.20.1:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
|
||||
integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
|
||||
optionalDependencies:
|
||||
"@esbuild/android-arm" "0.19.5"
|
||||
"@esbuild/android-arm64" "0.19.5"
|
||||
"@esbuild/android-x64" "0.19.5"
|
||||
"@esbuild/darwin-arm64" "0.19.5"
|
||||
"@esbuild/darwin-x64" "0.19.5"
|
||||
"@esbuild/freebsd-arm64" "0.19.5"
|
||||
"@esbuild/freebsd-x64" "0.19.5"
|
||||
"@esbuild/linux-arm" "0.19.5"
|
||||
"@esbuild/linux-arm64" "0.19.5"
|
||||
"@esbuild/linux-ia32" "0.19.5"
|
||||
"@esbuild/linux-loong64" "0.19.5"
|
||||
"@esbuild/linux-mips64el" "0.19.5"
|
||||
"@esbuild/linux-ppc64" "0.19.5"
|
||||
"@esbuild/linux-riscv64" "0.19.5"
|
||||
"@esbuild/linux-s390x" "0.19.5"
|
||||
"@esbuild/linux-x64" "0.19.5"
|
||||
"@esbuild/netbsd-x64" "0.19.5"
|
||||
"@esbuild/openbsd-x64" "0.19.5"
|
||||
"@esbuild/sunos-x64" "0.19.5"
|
||||
"@esbuild/win32-arm64" "0.19.5"
|
||||
"@esbuild/win32-ia32" "0.19.5"
|
||||
"@esbuild/win32-x64" "0.19.5"
|
||||
"@esbuild/aix-ppc64" "0.20.2"
|
||||
"@esbuild/android-arm" "0.20.2"
|
||||
"@esbuild/android-arm64" "0.20.2"
|
||||
"@esbuild/android-x64" "0.20.2"
|
||||
"@esbuild/darwin-arm64" "0.20.2"
|
||||
"@esbuild/darwin-x64" "0.20.2"
|
||||
"@esbuild/freebsd-arm64" "0.20.2"
|
||||
"@esbuild/freebsd-x64" "0.20.2"
|
||||
"@esbuild/linux-arm" "0.20.2"
|
||||
"@esbuild/linux-arm64" "0.20.2"
|
||||
"@esbuild/linux-ia32" "0.20.2"
|
||||
"@esbuild/linux-loong64" "0.20.2"
|
||||
"@esbuild/linux-mips64el" "0.20.2"
|
||||
"@esbuild/linux-ppc64" "0.20.2"
|
||||
"@esbuild/linux-riscv64" "0.20.2"
|
||||
"@esbuild/linux-s390x" "0.20.2"
|
||||
"@esbuild/linux-x64" "0.20.2"
|
||||
"@esbuild/netbsd-x64" "0.20.2"
|
||||
"@esbuild/openbsd-x64" "0.20.2"
|
||||
"@esbuild/sunos-x64" "0.20.2"
|
||||
"@esbuild/win32-arm64" "0.20.2"
|
||||
"@esbuild/win32-ia32" "0.20.2"
|
||||
"@esbuild/win32-x64" "0.20.2"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
|
|
@ -11486,14 +11512,14 @@ postcss@^7.0.14, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6:
|
|||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.23, postcss@^8.4.32, postcss@^8.4.35:
|
||||
version "8.4.35"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7"
|
||||
integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==
|
||||
postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.23, postcss@^8.4.32, postcss@^8.4.35, postcss@^8.4.38:
|
||||
version "8.4.38"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
prebuild-install@^7.1.1:
|
||||
version "7.1.2"
|
||||
|
|
@ -12304,23 +12330,29 @@ robust-predicates@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a"
|
||||
integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==
|
||||
|
||||
rollup@^4.2.0:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.4.1.tgz#2f85169f23d13dabb3d9b846d753965757353820"
|
||||
integrity sha512-idZzrUpWSblPJX66i+GzrpjKE3vbYrlWirUHteoAbjKReZwa0cohAErOYA5efoMmNCdvG9yrJS+w9Kl6csaH4w==
|
||||
rollup@^4.13.0:
|
||||
version "4.14.3"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.3.tgz#bcbb7784b35826d3164346fa6d5aac95190d8ba9"
|
||||
integrity sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.5"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.4.1"
|
||||
"@rollup/rollup-android-arm64" "4.4.1"
|
||||
"@rollup/rollup-darwin-arm64" "4.4.1"
|
||||
"@rollup/rollup-darwin-x64" "4.4.1"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.4.1"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.4.1"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.4.1"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.4.1"
|
||||
"@rollup/rollup-linux-x64-musl" "4.4.1"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.4.1"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.4.1"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.4.1"
|
||||
"@rollup/rollup-android-arm-eabi" "4.14.3"
|
||||
"@rollup/rollup-android-arm64" "4.14.3"
|
||||
"@rollup/rollup-darwin-arm64" "4.14.3"
|
||||
"@rollup/rollup-darwin-x64" "4.14.3"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.14.3"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.14.3"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.14.3"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.14.3"
|
||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.14.3"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.14.3"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.14.3"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.14.3"
|
||||
"@rollup/rollup-linux-x64-musl" "4.14.3"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.14.3"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.14.3"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.14.3"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
rope-sequence@^1.3.0:
|
||||
|
|
@ -12836,10 +12868,10 @@ source-list-map@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||
|
||||
source-map-resolve@^0.5.0:
|
||||
version "0.5.3"
|
||||
|
|
@ -14271,14 +14303,14 @@ vite-plugin-ruby@^5.0.0:
|
|||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
|
||||
vite@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.1.6.tgz#706dae5fab9e97f57578469eef1405fc483943e4"
|
||||
integrity sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==
|
||||
vite@^5.2.9:
|
||||
version "5.2.9"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.9.tgz#cd9a356c6ff5f7456c09c5ce74068ffa8df743d9"
|
||||
integrity sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==
|
||||
dependencies:
|
||||
esbuild "^0.19.3"
|
||||
postcss "^8.4.35"
|
||||
rollup "^4.2.0"
|
||||
esbuild "^0.20.1"
|
||||
postcss "^8.4.38"
|
||||
rollup "^4.13.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue