Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-04-17 21:09:22 +00:00
parent b37966a868
commit c35b5c40f4
90 changed files with 1579 additions and 1139 deletions

View File

@ -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: ""

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"},

View File

@ -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)

View File

@ -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);

View File

@ -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"

View File

@ -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"
>&middot; {{ 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>

View File

@ -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,
});

View File

@ -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
}
}
}

View File

@ -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'),

View File

@ -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>

View File

@ -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 = () => {

View File

@ -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"
>

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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? }

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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=,

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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"
]
}
}
}

View File

@ -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'

View File

@ -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

View File

@ -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.')

View File

@ -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'

View File

@ -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'

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
9b0545358dc8af7ce87a87497598fe1cec6159638c925f87afeba1b40d4bb49c

View File

@ -0,0 +1 @@
2f045332b7600514f8adeea441afca3c5cd8eddd5a7fab261e48fc373046ead1

View File

@ -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)),

View File

@ -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:

View File

@ -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. |

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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. |

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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();

View File

@ -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);
},
);
});
});

View File

@ -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>
`;

View File

@ -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 }),
);
},
);

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]
})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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"