Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ca1dd21a37
commit
344ece1971
|
|
@ -82,7 +82,7 @@ eslint:
|
|||
- |
|
||||
function eslint_script() {
|
||||
yarn_install_script
|
||||
yarn run lint:eslint:all --format gitlab
|
||||
./tooling/ci/changed_files.rb eslint
|
||||
}
|
||||
|
||||
run_with_custom_exit_code eslint_script
|
||||
|
|
|
|||
|
|
@ -28,9 +28,13 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
alertInfoMessage() {
|
||||
return sprintf(this.$options.i18n.alertInfoMessage, {
|
||||
accessTokenType: this.accessTokenType,
|
||||
});
|
||||
return sprintf(
|
||||
this.$options.i18n.alertInfoMessage,
|
||||
{
|
||||
accessTokenType: this.accessTokenType,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
alertDangerTitle() {
|
||||
return n__(
|
||||
|
|
@ -50,7 +54,13 @@ export default {
|
|||
};
|
||||
},
|
||||
label() {
|
||||
return sprintf(this.$options.i18n.label, { accessTokenType: this.accessTokenType });
|
||||
return sprintf(
|
||||
this.$options.i18n.label,
|
||||
{
|
||||
accessTokenType: this.accessTokenType,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
isNameOrScopesSet() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { issuableInitialDataById, isLegacyIssueType } from './show/utils/issuabl
|
|||
const feedback = {};
|
||||
|
||||
if (gon.features?.workItemViewForIssues) {
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/463598';
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/523713';
|
||||
feedback.feedbackIssueText = __('Provide feedback on the experience');
|
||||
feedback.content = __(
|
||||
'We’ve introduced some improvements to the issue page such as real time updates, additional features, and a refreshed design. Have questions or thoughts on the changes?',
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ export const getSortOptions = ({
|
|||
hasIssueWeightsFeature,
|
||||
hasManualSort = true,
|
||||
hasMergedDate = false,
|
||||
hasDueDate = true,
|
||||
} = {}) => {
|
||||
const sortOptions = [
|
||||
{
|
||||
|
|
@ -171,7 +172,7 @@ export const getSortOptions = ({
|
|||
descending: MILESTONE_DUE_DESC,
|
||||
},
|
||||
},
|
||||
{
|
||||
hasDueDate && {
|
||||
id: 6,
|
||||
title: __('Due date'),
|
||||
sortDirection: {
|
||||
|
|
|
|||
|
|
@ -480,7 +480,11 @@ export default {
|
|||
);
|
||||
},
|
||||
sortOptions() {
|
||||
return getSortOptions({ hasManualSort: false, hasMergedDate: this.state === STATUS_MERGED });
|
||||
return getSortOptions({
|
||||
hasManualSort: false,
|
||||
hasMergedDate: this.state === STATUS_MERGED,
|
||||
hasDueDate: false,
|
||||
});
|
||||
},
|
||||
tabCounts() {
|
||||
const { openedMergeRequests, closedMergeRequests, mergedMergeRequests, allMergeRequests } =
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ initWorkItemsRoot();
|
|||
const feedback = {};
|
||||
|
||||
if (gon.features.workItemViewForIssues) {
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/463598';
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/523713';
|
||||
feedback.feedbackIssueText = __('Provide feedback on the experience');
|
||||
feedback.content = __(
|
||||
'We’ve introduced some improvements to the issue page such as real time updates, additional features, and a refreshed design. Have questions or thoughts on the changes?',
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ new IssuableTemplateSelectors({ warnTemplateOverride: true, editor: mountMarkdow
|
|||
const feedback = {};
|
||||
|
||||
if (gon.features.workItemViewForIssues) {
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/463598';
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/523713';
|
||||
feedback.feedbackIssueText = __('Provide feedback on the experience');
|
||||
feedback.content = __(
|
||||
'We’ve introduced some improvements to the issue page such as real time updates, additional features, and a refreshed design. Have questions or thoughts on the changes?',
|
||||
|
|
|
|||
|
|
@ -308,20 +308,30 @@ export default {
|
|||
},
|
||||
confidentialItem() {
|
||||
return {
|
||||
text: this.isConfidential
|
||||
? this.$options.i18n.disableConfidentiality
|
||||
: this.$options.i18n.enableConfidentiality,
|
||||
extraAttrs: {
|
||||
disabled: this.isParentConfidential,
|
||||
},
|
||||
text: this.confidentialItemText,
|
||||
extraAttrs: { disabled: this.isParentConfidential },
|
||||
};
|
||||
},
|
||||
confidentialItemText() {
|
||||
return this.isConfidential
|
||||
? this.$options.i18n.disableConfidentiality
|
||||
: this.$options.i18n.enableConfidentiality;
|
||||
},
|
||||
confidentialItemIcon() {
|
||||
return this.isConfidential ? 'eye' : 'eye-slash';
|
||||
},
|
||||
confidentialItemIconVariant() {
|
||||
return this.isParentConfidential ? 'current' : 'subtle';
|
||||
},
|
||||
confidentialTooltip() {
|
||||
return this.isParentConfidential ? this.$options.i18n.confidentialParentTooltip : '';
|
||||
},
|
||||
lockDiscussionText() {
|
||||
return this.isDiscussionLocked ? __('Unlock discussion') : __('Lock discussion');
|
||||
},
|
||||
lockDiscussionIcon() {
|
||||
return this.isDiscussionLocked ? 'lock-open' : 'lock';
|
||||
},
|
||||
objectiveWorkItemTypeId() {
|
||||
return this.workItemTypes.find((type) => type.name === WORK_ITEM_TYPE_NAME_OBJECTIVE).id;
|
||||
},
|
||||
|
|
@ -545,12 +555,20 @@ export default {
|
|||
<template #list-item>
|
||||
<gl-toggle
|
||||
:value="subscribedToNotifications"
|
||||
:label="$options.i18n.notifications"
|
||||
label-position="left"
|
||||
data-testid="notifications-toggle"
|
||||
class="work-item-dropdown-toggle gl-justify-between"
|
||||
@change="toggleNotifications($event)"
|
||||
/>
|
||||
>
|
||||
<template #label>
|
||||
<span :title="$options.i18n.notifications" class="gl-flex gl-gap-3 gl-pt-1">
|
||||
<gl-icon name="notifications" variant="subtle" />
|
||||
<span class="gl-max-w-[154px] gl-truncate">{{
|
||||
$options.i18n.notifications
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
</gl-toggle>
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
|
|
@ -575,7 +593,10 @@ export default {
|
|||
data-testid="new-related-work-item"
|
||||
@action="isCreateWorkItemModalVisible = true"
|
||||
>
|
||||
<template #list-item>{{ newRelatedItemLabel }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="plus" class="gl-mr-2" variant="subtle" />
|
||||
{{ newRelatedItemLabel }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -583,7 +604,10 @@ export default {
|
|||
data-testid="promote-action"
|
||||
@action="promoteToObjective"
|
||||
>
|
||||
<template #list-item>{{ __('Promote to objective') }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="level-up" class="gl-mr-2" variant="subtle" />
|
||||
{{ __('Promote to objective') }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -591,7 +615,10 @@ export default {
|
|||
data-testid="change-type-action"
|
||||
@action="showChangeTypeModal"
|
||||
>
|
||||
<template #list-item>{{ $options.i18n.changeWorkItemType }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="issue-type-issue" class="gl-mr-2" variant="subtle" />
|
||||
{{ $options.i18n.changeWorkItemType }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -599,7 +626,10 @@ export default {
|
|||
data-testid="move-action"
|
||||
@action="isMoveWorkItemModalVisible = true"
|
||||
>
|
||||
<template #list-item>{{ __('Move') }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="long-arrow" class="gl-mr-2" variant="subtle" />
|
||||
{{ __('Move') }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -608,7 +638,8 @@ export default {
|
|||
@action="toggleDiscussionLock"
|
||||
>
|
||||
<template #list-item>
|
||||
<gl-loading-icon v-if="isLockDiscussionUpdating" class="gl-mr-1" inline />
|
||||
<gl-loading-icon v-if="isLockDiscussionUpdating" class="gl-mr-2" inline />
|
||||
<gl-icon :name="lockDiscussionIcon" class="gl-mr-2" variant="subtle" />
|
||||
{{ lockDiscussionText }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
|
@ -619,7 +650,16 @@ export default {
|
|||
:item="confidentialItem"
|
||||
data-testid="confidentiality-toggle-action"
|
||||
@action="handleToggleWorkItemConfidentiality"
|
||||
/>
|
||||
>
|
||||
<template #list-item>
|
||||
<gl-icon
|
||||
:name="confidentialItemIcon"
|
||||
class="gl-mr-2"
|
||||
:variant="confidentialItemIconVariant"
|
||||
/>
|
||||
{{ confidentialItemText }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
data-testid="copy-reference-action"
|
||||
|
|
@ -627,7 +667,10 @@ export default {
|
|||
class="shortcut-copy-reference"
|
||||
@action="copyToClipboard(workItemReference, $options.i18n.referenceCopied)"
|
||||
>
|
||||
<template #list-item>{{ $options.i18n.copyReference }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="copy-to-clipboard" class="gl-mr-2" variant="subtle" />
|
||||
{{ $options.i18n.copyReference }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -636,7 +679,10 @@ export default {
|
|||
:data-clipboard-text="workItemCreateNoteEmail"
|
||||
@action="copyToClipboard(workItemCreateNoteEmail, $options.i18n.emailAddressCopied)"
|
||||
>
|
||||
<template #list-item>{{ i18n.copyCreateNoteEmail }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="copy-to-clipboard" class="gl-mr-2" variant="subtle" />
|
||||
{{ i18n.copyCreateNoteEmail }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-dropdown-divider />
|
||||
|
|
@ -646,7 +692,10 @@ export default {
|
|||
data-testid="report-abuse-action"
|
||||
@action="handleToggleReportAbuseModal"
|
||||
>
|
||||
<template #list-item>{{ $options.i18n.reportAbuse }}</template>
|
||||
<template #list-item>
|
||||
<gl-icon name="review-warning" class="gl-mr-2" variant="subtle" />
|
||||
{{ $options.i18n.reportAbuse }}
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -662,7 +711,10 @@ export default {
|
|||
@action="handleDelete"
|
||||
>
|
||||
<template #list-item>
|
||||
<span>{{ i18n.deleteWorkItem }}</span>
|
||||
<span>
|
||||
<gl-icon name="remove" class="gl-mr-2" variant="current" />
|
||||
{{ i18n.deleteWorkItem }}
|
||||
</span>
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
</template>
|
||||
|
|
@ -686,10 +738,21 @@ export default {
|
|||
<template #list-item>
|
||||
<gl-toggle
|
||||
:value="truncationEnabled"
|
||||
:label="s__('WorkItem|Truncate descriptions')"
|
||||
label-position="left"
|
||||
class="work-item-dropdown-toggle gl-justify-between"
|
||||
/>
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
:title="s__('WorkItem|Truncate descriptions')"
|
||||
class="gl-flex gl-gap-3 gl-pt-1"
|
||||
>
|
||||
<gl-icon name="text-description" variant="subtle" />
|
||||
<span class="gl-max-w-[154px] gl-truncate">{{
|
||||
s__('WorkItem|Truncate descriptions')
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
</gl-toggle>
|
||||
</template>
|
||||
</gl-disclosure-dropdown-item>
|
||||
<gl-disclosure-dropdown-item
|
||||
|
|
@ -699,7 +762,10 @@ export default {
|
|||
>
|
||||
<template #list-item>
|
||||
<div class="gl-flex gl-items-center gl-justify-between">
|
||||
<span>{{ toggleSidebarLabel }}</span>
|
||||
<span>
|
||||
<gl-icon name="sidebar-right" class="gl-mr-2" variant="subtle" />
|
||||
{{ toggleSidebarLabel }}
|
||||
</span>
|
||||
<kbd v-if="toggleSidebarKeys" class="flat">{{ toggleSidebarKeys }}</kbd>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { GlButton, GlDisclosureDropdownItem, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
|
||||
import {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
GlDisclosureDropdownItem,
|
||||
GlLoadingIcon,
|
||||
GlModal,
|
||||
GlLink,
|
||||
} from '@gitlab/ui';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import Tracking from '~/tracking';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
|
@ -22,6 +29,7 @@ import workItemOpenChildCountQuery from '../graphql/open_child_count.query.graph
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
GlDisclosureDropdownItem,
|
||||
GlLoadingIcon,
|
||||
|
|
@ -169,6 +177,9 @@ export default {
|
|||
}
|
||||
return sprintfWorkItem(baseText, this.workItemType);
|
||||
},
|
||||
toggleWorkItemStateIcon() {
|
||||
return this.isWorkItemOpen ? 'issue-close' : 'issue-open-m';
|
||||
},
|
||||
tracking() {
|
||||
return {
|
||||
category: TRACKING_CATEGORY_SHOW,
|
||||
|
|
@ -291,6 +302,7 @@ export default {
|
|||
{{ toggleInProgressText }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<gl-icon :name="toggleWorkItemStateIcon" class="gl-mr-2" variant="subtle" />
|
||||
{{ toggleWorkItemStateText }}
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {}
|
|||
const feedback = {};
|
||||
|
||||
if (gon.features.workItemViewForIssues) {
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/463598';
|
||||
feedback.feedbackIssue = 'https://gitlab.com/gitlab-org/gitlab/-/issues/523713';
|
||||
feedback.feedbackIssueText = __('Provide feedback on the experience');
|
||||
feedback.content = __(
|
||||
'We’ve introduced some improvements to the issue page such as real time updates, additional features, and a refreshed design. Have questions or thoughts on the changes?',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
- project = local_assigns[:project].nil? || local_assigns[:project]
|
||||
- assigned = false unless local_assigns[:assigned] == true
|
||||
|
||||
%li
|
||||
.gl-flex.gl-gap-3
|
||||
.gl-w-6
|
||||
- if assigned
|
||||
= sprite_icon('status-success', variant: 'success', css_class: 'gl-mt-3')
|
||||
= render Pajamas::AvatarComponent.new(project, size: 32, avatar_options: { aria: { hidden: "true" } })
|
||||
.gl-flex.gl-flex-col.gl-gap-1.gl-grow
|
||||
%h3.gl-text-base.gl-mt-1.gl-mb-0
|
||||
= project.full_name
|
||||
%p.gl-text-sm.gl-text-subtle.gl-mb-0
|
||||
= project.description
|
||||
= yield
|
||||
|
|
@ -7,50 +7,35 @@
|
|||
#js-admin-runner-edit{ data: {runner_id: @runner.id, runner_path: admin_runner_path(@runner) } }
|
||||
|
||||
- if @runner.project_type?
|
||||
.gl-overflow-auto
|
||||
%h4.gl-text-lg.gl-my-5= _('Restrict projects for this runner')
|
||||
= render ::Layouts::SettingsSectionComponent.new(_('Restrict projects for this runner'), options: { class: 'gl-mt-7 gl-pt-6 gl-border-t' }) do |c|
|
||||
- c.with_body do
|
||||
.gl-flex.gl-flex-col.gl-gap-5
|
||||
= render ::Layouts::CrudComponent.new(_('Assigned projects')) do |c|
|
||||
- c.with_body do
|
||||
- if @runner.runner_projects.any?
|
||||
%ul.content-list{ data: { testid: 'assigned-projects' } }
|
||||
- @runner.runner_projects.each do |runner_project|
|
||||
- project = runner_project.project
|
||||
- if project
|
||||
= render "project", project: project, assigned: true do
|
||||
= render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, href: admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, button_options: { class: 'gl-self-center' }) do
|
||||
= _('Disable')
|
||||
|
||||
- if @runner.runner_projects.any?
|
||||
%table.table{ data: { testid: 'assigned-projects' } }
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Assigned projects')
|
||||
- @runner.runner_projects.each do |runner_project|
|
||||
- project = runner_project.project
|
||||
- if project
|
||||
%tr
|
||||
%td
|
||||
= render Pajamas::AlertComponent.new(variant: :danger,
|
||||
dismissible: false,
|
||||
title: project.full_name) do |c|
|
||||
- c.with_actions do
|
||||
= render Pajamas::ButtonComponent.new(variant: :confirm, href: admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete) do
|
||||
= _('Disable')
|
||||
= render ::Layouts::CrudComponent.new(s_('Runners|Select projects to assign to this runner')) do |c|
|
||||
- c.with_body do
|
||||
= form_tag edit_admin_runner_path(@runner), id: 'runner-projects-search', class: 'gl-w-full gl-p-5', method: :get do
|
||||
.input-group
|
||||
= search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
|
||||
.input-group-append
|
||||
= render Pajamas::ButtonComponent.new(type: 'submit', variant: :default, icon: 'search', button_options: { 'aria-label': _('Search') })
|
||||
|
||||
%table.table{ data: { testid: 'unassigned-projects' } }
|
||||
%thead
|
||||
%tr
|
||||
%th= s_('Runners|Select projects to assign to this runner')
|
||||
%th
|
||||
%ul.content-list.gl-border-t.gl-border-t-section{ data: { testid: 'unassigned-projects' } }
|
||||
- @projects.each do |project|
|
||||
= render "project", project: project do
|
||||
= gitlab_ui_form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post, html: { class: 'gl-self-center' } do |f|
|
||||
= f.hidden_field :runner_id, value: @runner.id
|
||||
= render Pajamas::ButtonComponent.new(size: :small, type: :submit) do
|
||||
= _('Enable')
|
||||
|
||||
%tr
|
||||
%td
|
||||
= form_tag edit_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
|
||||
.input-group
|
||||
= search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
|
||||
.input-group-append
|
||||
= render Pajamas::ButtonComponent.new(type: 'submit', variant: :default) do
|
||||
= _('Search')
|
||||
|
||||
%td
|
||||
- @projects.each do |project|
|
||||
%tr
|
||||
%td
|
||||
= project.full_name
|
||||
%td
|
||||
.gl-float-right
|
||||
= gitlab_ui_form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f|
|
||||
= f.hidden_field :runner_id, value: @runner.id
|
||||
= render Pajamas::ButtonComponent.new(size: :small, type: :submit) do
|
||||
= _('Enable')
|
||||
= paginate_without_count @projects
|
||||
- c.with_pagination do
|
||||
= paginate_without_count @projects
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
- title: "Rename `setPreReceiveSecretDetection` GraphQL mutation to `setSecretPushProtection`"
|
||||
removal_milestone: "18.0"
|
||||
announcement_milestone: "17.7"
|
||||
breaking_change: true
|
||||
breaking_change: false
|
||||
window: 3
|
||||
reporter: abellucci
|
||||
stage: application_security_testing
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropUnwantedSequenceForProjectIdForeignKey < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.11'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
drop_sequence(
|
||||
:project_compliance_framework_settings,
|
||||
:project_id,
|
||||
:project_compliance_framework_settings_project_id_seq
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# We don't want to restore the sequence as it was a design flaw
|
||||
# Having a sequence on a foreign key column is an incident waiting to happen.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/526909
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
b79f0ecfad2c8ac31210d60ed100ba2b778bd2adf2aeaee17f233e1b09e9e438
|
||||
|
|
@ -20608,15 +20608,6 @@ CREATE SEQUENCE project_compliance_framework_settings_id_seq
|
|||
|
||||
ALTER SEQUENCE project_compliance_framework_settings_id_seq OWNED BY project_compliance_framework_settings.id;
|
||||
|
||||
CREATE SEQUENCE project_compliance_framework_settings_project_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE project_compliance_framework_settings_project_id_seq OWNED BY project_compliance_framework_settings.project_id;
|
||||
|
||||
CREATE TABLE project_compliance_standards_adherence (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -27390,8 +27381,6 @@ ALTER TABLE ONLY project_ci_cd_settings ALTER COLUMN id SET DEFAULT nextval('pro
|
|||
|
||||
ALTER TABLE ONLY project_ci_feature_usages ALTER COLUMN id SET DEFAULT nextval('project_ci_feature_usages_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY project_compliance_framework_settings ALTER COLUMN project_id SET DEFAULT nextval('project_compliance_framework_settings_project_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY project_compliance_framework_settings ALTER COLUMN id SET DEFAULT nextval('project_compliance_framework_settings_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY project_compliance_standards_adherence ALTER COLUMN id SET DEFAULT nextval('project_compliance_standards_adherence_id_seq'::regclass);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ title: Applications API
|
|||
{{< details >}}
|
||||
|
||||
- Tier: Free, Premium, Ultimate
|
||||
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
|
||||
- Offering: GitLab Self-Managed, GitLab Dedicated
|
||||
|
||||
{{< /details >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Application Security Testing
|
||||
group: Composition Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Access the Dependencies API to retrieve project dependency information, including package details, versions, vulnerabilities, and licenses for supported package managers.
|
||||
title: Dependencies API
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ for Conan recipes.
|
|||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | string | yes | The project ID or full project path. |
|
||||
|
||||
## Ping
|
||||
## Verify availability of a Conan repository
|
||||
|
||||
Ping the GitLab Conan repository to verify availability:
|
||||
Verifies the availability of the GitLab Conan repository.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/ping
|
||||
|
|
@ -86,9 +86,9 @@ Example response:
|
|||
""
|
||||
```
|
||||
|
||||
## Search
|
||||
## Search for a Conan package
|
||||
|
||||
Search the instance for Conan packages by name:
|
||||
Searches the instance for a Conan package with a specified name.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/search
|
||||
|
|
@ -118,9 +118,9 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Authenticate
|
||||
## Create an authentication token
|
||||
|
||||
Returns a JWT to be used for Conan requests in a Bearer header:
|
||||
Creates a JSON Web Token (JWT) for use as a Bearer header in other requests.
|
||||
|
||||
```shell
|
||||
"Authorization: Bearer <token>
|
||||
|
|
@ -142,9 +142,9 @@ Example response:
|
|||
eyJhbGciOiJIUzI1NiIiheR5cCI6IkpXVCJ9.eyJhY2Nlc3NfdG9rZW4iOjMyMTQyMzAsqaVzZXJfaWQiOjQwNTkyNTQsImp0aSI6IjdlNzBiZTNjLWFlNWQtNDEyOC1hMmIyLWZiOThhZWM0MWM2OSIsImlhd3r1MTYxNjYyMzQzNSwibmJmIjoxNjE2NjIzNDMwLCJleHAiOjE2MTY2MjcwMzV9.QF0Q3ZIB2GW5zNKyMSIe0HIFOITjEsZEioR-27Rtu7E
|
||||
```
|
||||
|
||||
## Check Credentials
|
||||
## Verify authentication credentials
|
||||
|
||||
Checks the validity of Basic Auth credentials or a Conan JWT generated from [`/authenticate`](#authenticate).
|
||||
Verifies the validity of Basic Auth credentials or a Conan JWT generated from the [`/authenticate`](#create-an-authentication-token) endpoint.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/users/check_credentials
|
||||
|
|
@ -160,10 +160,10 @@ Example response:
|
|||
ok
|
||||
```
|
||||
|
||||
## Recipe Snapshot
|
||||
## Get a recipe snapshot
|
||||
|
||||
This returns the snapshot of the recipe files for the specified Conan recipe. The snapshot is a list
|
||||
of filenames with their associated md5 hash.
|
||||
Gets a snapshot of the files for a specified Conan recipe. The snapshot is a list of filenames
|
||||
with their associated MD5 hash.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel
|
||||
|
|
@ -190,10 +190,10 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Package Snapshot
|
||||
## Get a package snapshot
|
||||
|
||||
This returns the snapshot of the package files for the specified Conan recipe with the specified
|
||||
Conan reference. The snapshot is a list of filenames with their associated md5 hash.
|
||||
Gets a snapshot of the files for a specified Conan package and reference. The snapshot is a list of filenames
|
||||
with their associated MD5 hash.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/packages/:conan_package_reference
|
||||
|
|
@ -221,9 +221,9 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Recipe Manifest
|
||||
## Get a recipe manifest
|
||||
|
||||
The manifest is a list of recipe filenames with their associated download URLs.
|
||||
Gets a manifest that includes a list of files and associated download URLs for a specified recipe.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/digest
|
||||
|
|
@ -253,9 +253,9 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Package Manifest
|
||||
## Get a package manifest
|
||||
|
||||
The manifest is a list of package filenames with their associated download URLs.
|
||||
Gets a manifest that includes a list of files and associated download URLs for a specified package.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/packages/:conan_package_reference/digest
|
||||
|
|
@ -286,10 +286,10 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Recipe Download URLs
|
||||
## List all recipe download URLs
|
||||
|
||||
Recipe download URLs return a list of recipe filenames with their associated download URLs.
|
||||
This attribute is the same payload as the [recipe manifest](#recipe-manifest) endpoint.
|
||||
Lists all files and associated download URLs for a specified recipe.
|
||||
Returns the same payload as the [recipe manifest](#get-a-recipe-manifest) endpoint.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/download_urls
|
||||
|
|
@ -319,10 +319,10 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Package Download URLs
|
||||
## List all package download URLs
|
||||
|
||||
Package download URLs return a list of package filenames with their associated download URLs.
|
||||
This URL is the same payload as the [package manifest](#package-manifest) endpoint.
|
||||
Lists all files and associated download URLs for a specified package.
|
||||
Returns the same payload as the [package manifest](#get-a-package-manifest) endpoint.
|
||||
|
||||
```plaintext
|
||||
GET <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls
|
||||
|
|
@ -353,9 +353,10 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Recipe Upload URLs
|
||||
## List all recipe upload URLs
|
||||
|
||||
Given a list of recipe filenames and file sizes, a list of URLs to upload each file is returned.
|
||||
Lists the upload URLs for a specified collection of recipe files. The request must include a JSON object
|
||||
with the name and size of the individual files.
|
||||
|
||||
```plaintext
|
||||
POST <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/upload_urls
|
||||
|
|
@ -370,6 +371,8 @@ POST <route-prefix>/conans/:package_name/:package_version/:package_username/:pac
|
|||
|
||||
Example request JSON payload:
|
||||
|
||||
The payload must include both the name and size of the file.
|
||||
|
||||
```json
|
||||
{
|
||||
"conanfile.py": 410,
|
||||
|
|
@ -397,9 +400,10 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Package Upload URLs
|
||||
## List all package upload URLs
|
||||
|
||||
Given a list of package filenames and file sizes, a list of URLs to upload each file is returned.
|
||||
Lists the upload URLs for a specified collection of package files. The request must include a JSON object
|
||||
with the name and size of the individual files.
|
||||
|
||||
```plaintext
|
||||
POST <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls
|
||||
|
|
@ -415,6 +419,8 @@ POST <route-prefix>/conans/:package_name/:package_version/:package_username/:pac
|
|||
|
||||
Example request JSON payload:
|
||||
|
||||
The payload must include both the name and size of the file.
|
||||
|
||||
```json
|
||||
{
|
||||
"conan_package.tgz": 5412,
|
||||
|
|
@ -444,11 +450,10 @@ Example response:
|
|||
The URLs in the response have the same route prefix used to request them. If you request them with
|
||||
the project-level route, the returned URLs contain `/projects/:id`.
|
||||
|
||||
## Download a Recipe file
|
||||
## Get a recipe file
|
||||
|
||||
Download a recipe file to the package registry. You must use a download URL that the
|
||||
[recipe download URLs endpoint](#recipe-download-urls)
|
||||
returned.
|
||||
Gets a recipe file from the package registry. You must use the download URL returned from the
|
||||
[recipe download URLs](#list-all-recipe-download-urls) endpoint.
|
||||
|
||||
```plaintext
|
||||
GET packages/conan/v1/files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision/export/:file_name
|
||||
|
|
@ -475,11 +480,10 @@ curl --header "Authorization: Bearer <authenticate_token>" "https://gitlab.examp
|
|||
|
||||
This example writes to `conanfile.py` in the current directory.
|
||||
|
||||
## Upload a Recipe file
|
||||
## Upload a recipe file
|
||||
|
||||
Upload a recipe file to the package registry. You must use an upload URL that the
|
||||
[recipe upload URLs endpoint](#recipe-upload-urls)
|
||||
returned.
|
||||
Uploads a specified recipe file in the package registry. You must use the upload URL returned from the
|
||||
[recipe upload URLs](#list-all-recipe-upload-urls) endpoint.
|
||||
|
||||
```plaintext
|
||||
PUT packages/conan/v1/files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision/export/:file_name
|
||||
|
|
@ -503,11 +507,10 @@ curl --request PUT \
|
|||
"https://gitlab.example.com/api/v4/packages/conan/v1/files/my-package/1.0/my-group+my-project/stable/0/export/conanfile.py"
|
||||
```
|
||||
|
||||
## Download a Package file
|
||||
## Get a package file
|
||||
|
||||
Download a package file to the package registry. You must use a download URL that the
|
||||
[package download URLs endpoint](#package-download-urls)
|
||||
returned.
|
||||
Gets a package file from the package registry. You must use the download URL returned from the
|
||||
[package download URLs](#list-all-package-download-urls) endpoint.
|
||||
|
||||
```plaintext
|
||||
GET packages/conan/v1/files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name
|
||||
|
|
@ -536,11 +539,10 @@ curl --header "Authorization: Bearer <authenticate_token>" "https://gitlab.examp
|
|||
|
||||
This example writes to `conaninfo.txt` in the current directory.
|
||||
|
||||
## Upload a Package file
|
||||
## Upload a package file
|
||||
|
||||
Upload a package file to the package registry. You must use an upload URL that the
|
||||
[package upload URLs endpoint](#package-upload-urls)
|
||||
returned.
|
||||
Uploads a specified package file in the package registry. You must use the upload URL returned from the
|
||||
[package upload URLs](#list-all-package-upload-urls) endpoint.
|
||||
|
||||
```plaintext
|
||||
PUT packages/conan/v1/files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name
|
||||
|
|
@ -566,9 +568,9 @@ curl --request PUT \
|
|||
"https://gitlab.example.com/api/v4/packages/conan/v1/files/my-package/1.0/my-group+my-project/stable/0/package/103f6067a947f366ef91fc1b7da351c588d1827f/0/conaninfo.txt"
|
||||
```
|
||||
|
||||
## Delete a Package (delete a Conan recipe)
|
||||
## Delete a recipe and package
|
||||
|
||||
Delete the Conan recipe and package files from the registry:
|
||||
Deletes a specified Conan recipe and the associated package files from the package registry.
|
||||
|
||||
```plaintext
|
||||
DELETE <route-prefix>/conans/:package_name/:package_version/:package_username/:package_channel
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ a few different methods, based on where the variable is created or defined.
|
|||
### Pass YAML-defined CI/CD variables
|
||||
|
||||
You can use the `variables` keyword to pass CI/CD variables to a downstream pipeline.
|
||||
These variables are "trigger variables" for [variable precedence](../variables/_index.md#cicd-variable-precedence).
|
||||
These variables are pipeline variables for [variable precedence](../variables/_index.md#cicd-variable-precedence).
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
|||
|
|
@ -1151,6 +1151,41 @@ As a workaround you can either:
|
|||
- If a single large variable is larger than `ARG_MAX`, try using [Secure Files](../secure_files/_index.md), or
|
||||
bring the file to the job through some other mechanism.
|
||||
|
||||
### `Insufficient permissions to set pipeline variables` error for a downstream pipeline
|
||||
|
||||
When triggering a downstream pipeline, you might get this error unexpectedly:
|
||||
|
||||
```plaintext
|
||||
Failed - (downstream pipeline can not be created, Insufficient permissions to set pipeline variables)
|
||||
```
|
||||
|
||||
This error occurs when a downstream project has [restricted pipeline variables](#restrict-pipeline-variables) and the trigger job either:
|
||||
|
||||
- Has variables defined. For example:
|
||||
|
||||
```yaml
|
||||
trigger-job:
|
||||
variables:
|
||||
VAR_FOR_DOWNSTREAM: "test"
|
||||
trigger: my-group/my-project
|
||||
```
|
||||
|
||||
- Receives variables from [default variables](../yaml/_index.md#default-variables) defined in a top-level `variables` section. For example:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
DEFAULT_VAR: "test"
|
||||
|
||||
trigger-job:
|
||||
trigger: my-group/my-project
|
||||
```
|
||||
|
||||
Variables passed to a downstream pipeline in a trigger job are [pipeline variables](#use-pipeline-variables),
|
||||
so the workaround is to either:
|
||||
|
||||
- Remove the `variables` defined in the trigger job to avoid passing variables.
|
||||
- [Prevent default variables from being passed to the downstream pipeline](../pipelines/downstream_pipelines.md#prevent-default-variables-from-being-passed).
|
||||
|
||||
### Default variable doesn't expand in job variable of the same name
|
||||
|
||||
You cannot use a default variable's value in a job variable of the same name. A default variable
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Application Security Testing
|
||||
group: Composition Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Learn how to configure dependency scanning, detect vulnerabilities in your project dependencies, and resolve them through practical step-by-step guidance.
|
||||
title: 'Tutorial: Set up dependency scanning'
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: Application Security Testing
|
||||
group: Composition Analysis
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Learn how to generate and export a Software Bill of Materials (SBOM) in CycloneDX format for your project dependencies and save it as a CI/CD artifact.
|
||||
title: 'Tutorial: Export dependency list in SBOM format'
|
||||
---
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ Set up Dependency Scanning. For detailed instructions, follow [the Dependency Sc
|
|||
- apk add --update jq curl
|
||||
stage: .post
|
||||
script:
|
||||
- |
|
||||
- |
|
||||
curl --header "Authorization: Bearer $PRIVATE_TOKEN" --output export.sh --url "https://gitlab.com/api/v4/snippets/<SNIPPET_ID>/raw"
|
||||
- /bin/sh export.sh
|
||||
artifacts:
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ This window takes place on May 5 - 7, 2025 from 09:00 UTC to 22:00 UTC.
|
|||
| [GraphQL `target` field for to-do items replaced with `targetEntity`](https://gitlab.com/gitlab-org/gitlab/-/issues/484987) | Low | Foundations | Project |
|
||||
| [`ciJobTokenScopeAddProject` GraphQL mutation is deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/474175) | Low | Govern | Project |
|
||||
| [Removal of `migrationState` field in `ContainerRepository` GraphQL API](https://gitlab.com/gitlab-org/gitlab/-/issues/459869) | Low | Package | Project |
|
||||
| [Rename `setPreReceiveSecretDetection` GraphQL mutation to `setSecretPushProtection`](https://gitlab.com/gitlab-org/gitlab/-/issues/514414) | Medium | Application_security_testing | Project |
|
||||
| [Updated tooling to release CI/CD components to the Catalog](https://gitlab.com/groups/gitlab-org/-/epics/12788) | High | Verify | Instance |
|
||||
| [Increased default security for use of pipeline variables](https://gitlab.com/gitlab-org/gitlab/-/issues/502382) | Medium | Verify | Project |
|
||||
| [Amazon S3 Signature Version 2](https://gitlab.com/gitlab-org/container-registry/-/issues/1449) | Low | Package | Project |
|
||||
|
|
|
|||
|
|
@ -1839,14 +1839,14 @@ In 18.0 we are removing the `duoProAssignedUsersCount` GraphQL field. Users may
|
|||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
<div class="deprecation " data-milestone="18.0">
|
||||
|
||||
### Rename `setPreReceiveSecretDetection` GraphQL mutation to `setSecretPushProtection`
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
||||
- Announced in GitLab <span class="milestone">17.7</span>
|
||||
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/update/terminology/#breaking-change))
|
||||
- Removal in GitLab <span class="milestone">18.0</span>
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/514414).
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ title: Code Suggestions
|
|||
- [Removed support for GitLab native model](https://gitlab.com/groups/gitlab-org/-/epics/10752) in GitLab 16.2.
|
||||
- [Introduced support for Code Generation](https://gitlab.com/gitlab-org/gitlab/-/issues/415583) in GitLab 16.3.
|
||||
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/435271) in GitLab 16.7.
|
||||
- Subscription changed to require GitLab Duo Pro on February 15, 2024.
|
||||
- Changed to require GitLab Duo add-on in GitLab 17.6 and later.
|
||||
- [Changed](https://gitlab.com/gitlab-org/fulfillment/meta/-/issues/2031) to require the GitLab Duo Pro add-on on February 15, 2024. Previously, this feature was included with Premium and Ultimate subscriptions.
|
||||
- [Changed](https://gitlab.com/gitlab-org/fulfillment/meta/-/issues/2031) to require the GitLab Duo Pro or GitLab Duo Enterprise add-on for all supported GitLab versions starting October 17, 2024.
|
||||
- [Introduced support for Fireworks AI-hosted Qwen2.5 code completion model](https://gitlab.com/groups/gitlab-org/-/epics/15850) in GitLab 17.6, with a flag named `fireworks_qwen_code_completion`.
|
||||
|
||||
{{< /history >}}
|
||||
|
|
@ -42,9 +42,16 @@ you want to use to manage Code Suggestions requests:
|
|||
[View a click-through demo](https://gitlab.navattic.com/code-suggestions).
|
||||
<!-- Video published on 2023-12-09 --> <!-- Demo published on 2024-02-01 -->
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use Code Suggestions, you need:
|
||||
|
||||
- A Premium or Ultimate subscription with the GitLab Duo Pro or Enterprise add-on.
|
||||
- An assigned seat in your GitLab Duo subscription.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
GitLab Duo requires GitLab 17.2 and later for the best user experience and results. Earlier versions may continue to work, however the experience may be degraded. You should [upgrade to the latest version of GitLab](../../../../update/_index.md#upgrade-gitlab) for the best experience.
|
||||
GitLab Duo requires GitLab 17.2 and later for the best user experience and results. Earlier versions may continue to work, however, the experience may be degraded. You should [upgrade to the latest version of GitLab](../../../../update/_index.md#upgrade-gitlab) for the best experience.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -53198,6 +53198,9 @@ msgstr ""
|
|||
msgid "SecurityInventory|Security inventory"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityInventory|Tool coverage: %{coverage}%%"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityInventory|View security coverage and vulnerabilities for all the projects in this group."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -65427,6 +65430,9 @@ msgstr ""
|
|||
msgid "Vulnerabilities|Vulnerability report export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerabilities|changed vulnerability status to Needs Triage because it was redetected in pipeline %{pipeline_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Plan', :smoke, :health_check, product_group: :project_management do
|
||||
let!(:user) do
|
||||
create(:user,
|
||||
name: "QA User <img src=x onerror=alert(2)<img src=x onerror=alert(1)>",
|
||||
password: "pw_#{SecureRandom.hex(12)}",
|
||||
api_client: Runtime::API::Client.as_admin)
|
||||
end
|
||||
|
||||
let!(:project) { create(:project, name: 'xss-test-for-mentions-project') }
|
||||
|
||||
describe 'check xss occurence in @mentions in issues', :requires_admin do
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
|
||||
project.add_member(user)
|
||||
|
||||
create(:issue, project: project).visit!
|
||||
end
|
||||
|
||||
it 'mentions a user in a comment', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347949' do
|
||||
work_item_enabled = Page::Project::Issue::Show.perform(&:work_item_enabled?)
|
||||
page_type = work_item_enabled ? Page::Project::WorkItem::Show : Page::Project::Issue::Show
|
||||
|
||||
page_type.perform do |show|
|
||||
show.select_all_activities_filter
|
||||
show.comment("cc-ing you here @#{user.username}")
|
||||
|
||||
expect do
|
||||
expect(show).to have_comment("cc-ing you here")
|
||||
end.not_to raise_error # Selenium::WebDriver::Error::UnhandledAlertError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -151,7 +151,6 @@ spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
|
|||
spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
|
||||
spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
|
||||
spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
|
||||
spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
|
||||
spec/frontend/packages_and_registries/settings/group/components/forwarding_settings_spec.js
|
||||
spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
|
||||
spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ RSpec.describe "Admin manages runner in admin section", :js, feature_category: :
|
|||
shared_examples 'assignable runner' do
|
||||
it 'enables a runner for a project' do
|
||||
within_testid('unassigned-projects') do
|
||||
within('tr', text: project2.full_name) do
|
||||
within('li', text: project2.full_name) do
|
||||
click_on 'Enable'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,6 +63,21 @@ RSpec.describe "User comments on issue", :js, feature_category: :team_planning d
|
|||
|
||||
expect(page).not_to have_content('Continue editing')
|
||||
end
|
||||
|
||||
context "with a user whose name contains XSS" do
|
||||
let_it_be(:xss_user) { create(:user, name: "User <img src=x onerror=alert(2)<img src=x onerror=alert(1)>") }
|
||||
|
||||
before do
|
||||
project.add_guest(xss_user)
|
||||
end
|
||||
|
||||
it "escapes username when mentioning user" do
|
||||
mention = "@#{xss_user.username} check this out"
|
||||
|
||||
expect { add_note(mention) }.not_to raise_error
|
||||
expect(page).to have_content(mention)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when editing comments" do
|
||||
|
|
|
|||
|
|
@ -7,147 +7,10 @@ RSpec.describe WorkItems::WorkItemsFinder, feature_category: :team_planning do
|
|||
|
||||
it_behaves_like 'issues or work items finder', :work_item, '{Issues|WorkItems}Finder#execute context'
|
||||
|
||||
context 'when group parameter is present' do
|
||||
context 'with group parameter' do
|
||||
include_context '{Issues|WorkItems}Finder#execute context', :work_item
|
||||
|
||||
let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group, author: user) }
|
||||
let_it_be(:group_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: group, author: user2)
|
||||
end
|
||||
|
||||
let_it_be(:subgroup_work_item) { create(:work_item, :group_level, namespace: subgroup, author: user) }
|
||||
let_it_be(:subgroup_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: subgroup, author: user2)
|
||||
end
|
||||
|
||||
let_it_be(:subgroup2) { create(:group, :private, parent: group) }
|
||||
let_it_be(:subgroup2_work_item) { create(:work_item, :group_level, namespace: subgroup2, author: user) }
|
||||
let_it_be(:subgroup2_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: subgroup2, author: user2)
|
||||
end
|
||||
|
||||
let(:params) { { group_id: group } }
|
||||
let(:scope) { 'all' }
|
||||
|
||||
context 'when namespace_level_work_items is disabled' do
|
||||
before do
|
||||
stub_feature_flags(namespace_level_work_items: false)
|
||||
end
|
||||
|
||||
it 'does not return group level work items' do
|
||||
expect(items).to contain_exactly(item1, item5)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns group level work items' do
|
||||
expect(items).to contain_exactly(group_work_item)
|
||||
end
|
||||
|
||||
context 'when user has access to confidential items' do
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential group-level items' do
|
||||
expect(items).to contain_exactly(group_work_item, group_confidential_work_item)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when include_descendants is true' do
|
||||
before do
|
||||
params[:include_descendants] = true
|
||||
end
|
||||
|
||||
context 'when user does not have access to all subgroups' do
|
||||
it 'includes work items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item, item1, item4, item5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has read access to all subgroups' do
|
||||
before_all do
|
||||
subgroup2.add_guest(user)
|
||||
end
|
||||
|
||||
it 'includes work items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup2_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access all confidential items' do
|
||||
before_all do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential items from subgroups and child projects' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
group_confidential_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup_confidential_work_item,
|
||||
subgroup2_work_item,
|
||||
subgroup2_confidential_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access confidential issues of certain subgroups only' do
|
||||
before_all do
|
||||
subgroup2.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup2_work_item,
|
||||
subgroup2_confidential_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exclude_projects is true' do
|
||||
before do
|
||||
params[:exclude_projects] = true
|
||||
end
|
||||
|
||||
it 'does not include work items from projects' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when include_ancestors is true' do
|
||||
let(:params) { { group_id: subgroup, include_ancestors: true } }
|
||||
|
||||
it 'includes work items from ancestor groups' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when both include_descendants and include_ancestors are true' do
|
||||
let_it_be(:sub_subgroup) { create(:group, parent: subgroup) }
|
||||
let_it_be(:sub_subgroup_work_item) { create(:work_item, :group_level, namespace: sub_subgroup, author: user) }
|
||||
|
||||
let(:params) { { group_id: subgroup, include_descendants: true, include_ancestors: true } }
|
||||
|
||||
it 'includes work items from ancestor groups, subgroups, and child projects' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item, sub_subgroup_work_item, item4)
|
||||
end
|
||||
end
|
||||
it_behaves_like 'work items finder group parameter'
|
||||
end
|
||||
|
||||
context 'with start and end date filtering' do
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ describe('Merge requests list app', () => {
|
|||
expect(findIssuableList().props()).toMatchObject({
|
||||
namespace: 'gitlab-org/gitlab',
|
||||
recentSearchesStorageKey: 'merge_requests',
|
||||
sortOptions: getSortOptions({ hasManualSort: false }),
|
||||
sortOptions: getSortOptions({ hasManualSort: false, hasDueDate: false }),
|
||||
initialSortBy: 'CREATED_DESC',
|
||||
issuableSymbol: '!',
|
||||
issuables: getQueryResponse.data.namespace.mergeRequests.nodes,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { GlFilteredSearchToken } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { sortableFields } from '~/packages_and_registries/package_registry/utils';
|
||||
import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
|
|
@ -42,7 +43,7 @@ describe('Package Search', () => {
|
|||
it('has a registry search component', async () => {
|
||||
mountComponent();
|
||||
|
||||
await nextTick();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findPersistedSearch().exists()).toBe(true);
|
||||
});
|
||||
|
|
@ -84,7 +85,7 @@ describe('Package Search', () => {
|
|||
`('in a $page page binds the right props', async ({ isGroupPage }) => {
|
||||
mountComponent(isGroupPage);
|
||||
|
||||
await nextTick();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findPersistedSearch().props()).toMatchObject({
|
||||
tokens: expect.arrayContaining([
|
||||
|
|
@ -124,7 +125,7 @@ describe('Package Search', () => {
|
|||
|
||||
mountComponent();
|
||||
|
||||
await nextTick();
|
||||
await waitForPromises();
|
||||
|
||||
findPersistedSearch().vm.$emit('update', payload);
|
||||
|
||||
|
|
@ -156,7 +157,7 @@ describe('Package Search', () => {
|
|||
|
||||
mountComponent();
|
||||
|
||||
await nextTick();
|
||||
await waitForPromises();
|
||||
|
||||
findPersistedSearch().vm.$emit('update', payload);
|
||||
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ describe('WorkItemActions component', () => {
|
|||
expect(findDropdownItemsActual()).toEqual([
|
||||
{
|
||||
testId: 'notifications-toggle-form',
|
||||
text: '',
|
||||
text: 'Notifications',
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
|
|
@ -289,7 +289,7 @@ describe('WorkItemActions component', () => {
|
|||
},
|
||||
{
|
||||
testId: 'truncation-toggle-action',
|
||||
text: '',
|
||||
text: 'Truncate descriptions',
|
||||
},
|
||||
{
|
||||
testId: 'sidebar-toggle-action',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'work items finder group parameter' do
|
||||
context 'when group parameter is present' do
|
||||
let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group, author: user) }
|
||||
let_it_be(:group_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: group, author: user2)
|
||||
end
|
||||
|
||||
let_it_be(:subgroup_work_item) { create(:work_item, :group_level, namespace: subgroup, author: user) }
|
||||
let_it_be(:subgroup_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: subgroup, author: user2)
|
||||
end
|
||||
|
||||
let_it_be(:subgroup2) { create(:group, :private, parent: group) }
|
||||
let_it_be(:subgroup2_work_item) { create(:work_item, :group_level, namespace: subgroup2, author: user) }
|
||||
let_it_be(:subgroup2_confidential_work_item) do
|
||||
create(:work_item, :confidential, :group_level, namespace: subgroup2, author: user2)
|
||||
end
|
||||
|
||||
let(:params) { { group_id: group } }
|
||||
let(:scope) { 'all' }
|
||||
|
||||
before do
|
||||
stub_licensed_features(epics: true)
|
||||
end
|
||||
|
||||
context 'when namespace_level_work_items and work_item_epics is disabled' do
|
||||
before do
|
||||
stub_feature_flags(namespace_level_work_items: false, work_item_epics: false)
|
||||
end
|
||||
|
||||
it 'does not return group level work items' do
|
||||
expect(items).to contain_exactly(item1, item5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work_item_epics is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_item_epics: false)
|
||||
end
|
||||
|
||||
it 'returns group level work items' do
|
||||
expect(items).to contain_exactly(group_work_item)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns group level work items' do
|
||||
expect(items).to contain_exactly(group_work_item)
|
||||
end
|
||||
|
||||
context 'when user has access to confidential items' do
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential group-level items' do
|
||||
expect(items).to contain_exactly(group_work_item, group_confidential_work_item)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when include_descendants is true' do
|
||||
before do
|
||||
params[:include_descendants] = true
|
||||
end
|
||||
|
||||
context 'when user does not have access to all subgroups' do
|
||||
it 'includes work items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item, item1, item4, item5)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has read access to all subgroups' do
|
||||
before_all do
|
||||
subgroup2.add_guest(user)
|
||||
end
|
||||
|
||||
it 'includes work items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup2_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access all confidential items' do
|
||||
before_all do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential items from subgroups and child projects' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
group_confidential_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup_confidential_work_item,
|
||||
subgroup2_work_item,
|
||||
subgroup2_confidential_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can access confidential issues of certain subgroups only' do
|
||||
before_all do
|
||||
subgroup2.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'includes confidential items from subgroups and child projects with access' do
|
||||
expect(items).to contain_exactly(
|
||||
group_work_item,
|
||||
subgroup_work_item,
|
||||
subgroup2_work_item,
|
||||
subgroup2_confidential_work_item,
|
||||
item1,
|
||||
item4,
|
||||
item5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exclude_projects is true' do
|
||||
before do
|
||||
params[:exclude_projects] = true
|
||||
end
|
||||
|
||||
it 'does not include work items from projects' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when include_ancestors is true' do
|
||||
let(:params) { { group_id: subgroup, include_ancestors: true } }
|
||||
|
||||
it 'includes work items from ancestor groups' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when both include_descendants and include_ancestors are true' do
|
||||
let_it_be(:sub_subgroup) { create(:group, parent: subgroup) }
|
||||
let_it_be(:sub_subgroup_work_item) { create(:work_item, :group_level, namespace: sub_subgroup, author: user) }
|
||||
|
||||
let(:params) { { group_id: subgroup, include_descendants: true, include_ancestors: true } }
|
||||
|
||||
it 'includes work items from ancestor groups, subgroups, and child projects' do
|
||||
expect(items).to contain_exactly(group_work_item, subgroup_work_item, sub_subgroup_work_item, item4)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require_relative '../../../tooling/ci/changed_files'
|
||||
|
||||
RSpec.describe CI::ChangedFiles, feature_category: :tooling do
|
||||
let(:instance) { described_class.new }
|
||||
|
||||
describe '#should_run_checks_for_changed_files' do
|
||||
# The mock values are based on allowed values from the docs
|
||||
# https://docs.gitlab.com/ci/variables/predefined_variables/
|
||||
let(:pipeline_source) { 'merge_request_event' }
|
||||
let(:merge_request_event_type) { 'merged_result' }
|
||||
let(:commit_ref_name) { 'feature-branch' }
|
||||
|
||||
before do
|
||||
stub_env('CI_PIPELINE_SOURCE', pipeline_source)
|
||||
stub_env('CI_MERGE_REQUEST_EVENT_TYPE', merge_request_event_type)
|
||||
stub_env('CI_COMMIT_REF_NAME', commit_ref_name)
|
||||
end
|
||||
|
||||
context 'when in a valid merge request environment' do
|
||||
it 'returns true when no tier labels exist' do
|
||||
stub_env('CI_MERGE_REQUEST_LABELS', nil)
|
||||
expect(instance.should_run_checks_for_changed_files).to be true
|
||||
end
|
||||
|
||||
it 'returns true when tier-1 label exists' do
|
||||
stub_env('CI_MERGE_REQUEST_LABELS', 'pipeline::tier-1,other-label')
|
||||
expect(instance.should_run_checks_for_changed_files).to be true
|
||||
end
|
||||
|
||||
it 'returns false when other tier label exists' do
|
||||
stub_env('CI_MERGE_REQUEST_LABELS', 'pipeline::tier-2,other-label')
|
||||
expect(instance.should_run_checks_for_changed_files).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not in a valid merge request environment' do
|
||||
context 'when the merge request event type is merge_train' do
|
||||
let(:merge_request_event_type) { 'merge_train' }
|
||||
|
||||
it 'returns false' do
|
||||
expect(instance.should_run_checks_for_changed_files).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline source is not merged_request_event' do
|
||||
let(:pipeline_source) { 'push' }
|
||||
|
||||
it 'returns false when pipeline source is push' do
|
||||
expect(instance.should_run_checks_for_changed_files).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the current branch is CI_DEFAULT_BRANCH' do
|
||||
let(:commit_ref_name) { 'master' }
|
||||
|
||||
it 'returns false' do
|
||||
stub_env('CI_DEFAULT_BRANCH', 'master')
|
||||
expect(instance.should_run_checks_for_changed_files).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the environment variables are not set' do
|
||||
let(:pipeline_source) { nil }
|
||||
let(:merge_request_event_type) { nil }
|
||||
let(:commit_ref_name) { nil }
|
||||
|
||||
it 'returns false' do
|
||||
expect(instance.should_run_checks_for_changed_files).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_changed_files_in_merged_results_pipeline' do
|
||||
let(:git_diff_output) { "file1.js\nfile2.rb\nfile3.vue" }
|
||||
|
||||
before do
|
||||
allow(instance).to receive(:`)
|
||||
.with('git diff --name-only --diff-filter=d HEAD~..HEAD')
|
||||
.and_return(git_diff_output)
|
||||
end
|
||||
|
||||
context 'when git diff is run in a merged results pipeline' do
|
||||
it 'returns an array when there are changed files' do
|
||||
expect(instance.get_changed_files_in_merged_results_pipeline)
|
||||
.to match_array(['file1.js', 'file2.rb', 'file3.vue'])
|
||||
end
|
||||
|
||||
context "when there are no changed files" do
|
||||
let(:git_diff_output) { "" }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(instance.get_changed_files_in_merged_results_pipeline).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#filter_and_get_changed_files_in_mr' do
|
||||
context 'when checks should run for changed files' do \
|
||||
let(:changed_files_output) { ['file1.js', 'file2.rb', 'file3.vue'] }
|
||||
|
||||
before do
|
||||
allow(instance).to receive_messages(
|
||||
should_run_checks_for_changed_files: true,
|
||||
get_changed_files_in_merged_results_pipeline: changed_files_output
|
||||
)
|
||||
end
|
||||
|
||||
context 'when changed files exist' do
|
||||
it 'returns filtered files' do
|
||||
expect(instance.filter_and_get_changed_files_in_mr(filter_pattern: /\.(js|vue)$/))
|
||||
.to match_array(['file1.js', 'file3.vue'])
|
||||
end
|
||||
|
||||
it 'returns all files when filter is empty' do
|
||||
expect(instance.filter_and_get_changed_files_in_mr)
|
||||
.to match_array(changed_files_output)
|
||||
end
|
||||
|
||||
it 'returns empty array and prints warning when no files match filter' do
|
||||
allow(instance).to receive(:get_changed_files_in_merged_results_pipeline).and_return(['file1.txt',
|
||||
'file2.rb'])
|
||||
expect(instance).to receive(:puts).with('No files were changed. Skipping...')
|
||||
|
||||
expect(instance.filter_and_get_changed_files_in_mr(filter_pattern: /\.(js|vue)$/)).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checks should not run for changed files' do
|
||||
before do
|
||||
allow(instance).to receive(:should_run_checks_for_changed_files).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns ["."] and prints warning' do
|
||||
expect(instance).to receive(:puts).with("Changed file criteria didn't match... Command will run for all files")
|
||||
|
||||
expect(instance.filter_and_get_changed_files_in_mr(filter_pattern: /\.(js|vue)$/)).to eq(['.'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run_eslint_for_changed_files' do
|
||||
let(:files) { ['file1.js', 'file2.vue'] }
|
||||
let(:eslint_command) { ['yarn', 'run', 'lint:eslint', '--format', 'gitlab', 'file1.js', 'file2.vue'] }
|
||||
|
||||
before do
|
||||
allow(instance).to receive(:puts).with('Running ESLint...')
|
||||
end
|
||||
|
||||
context 'when there are changed files to lint' do
|
||||
before do
|
||||
allow(instance).to receive(:filter_and_get_changed_files_in_mr).and_return(files)
|
||||
end
|
||||
|
||||
it 'runs eslint with the correct arguments and returns exit 0 on success' do
|
||||
expect(instance).to receive(:system).with(*eslint_command).and_return(true)
|
||||
|
||||
status = instance_double(Process::Status, exitstatus: 0)
|
||||
allow(instance).to receive(:last_command_status).and_return(status)
|
||||
|
||||
expect(instance.run_eslint_for_changed_files).to eq(0)
|
||||
end
|
||||
|
||||
it 'runs eslint with the correct arguments and returns exit 1 on failure' do
|
||||
expect(instance).to receive(:system).with(*eslint_command).and_return(false)
|
||||
|
||||
status = instance_double(Process::Status, exitstatus: 1)
|
||||
allow(instance).to receive(:last_command_status).and_return(status)
|
||||
|
||||
expect(instance.run_eslint_for_changed_files).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no changed files to lint' do
|
||||
it 'does not run eslint and returns exit code 0' do
|
||||
allow(instance).to receive(:filter_and_get_changed_files_in_mr).and_return([])
|
||||
expect(instance).not_to receive(:system)
|
||||
|
||||
expect(instance.run_eslint_for_changed_files).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Run CLI commands' do
|
||||
it 'returns 0 for empty args' do
|
||||
allow(ARGV).to receive(:empty?).and_return(true)
|
||||
|
||||
expect(instance.process_command_and_determine_exit_status).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns 0 when eslint succeeds' do
|
||||
allow(ARGV).to receive(:first).and_return('eslint')
|
||||
allow(instance).to receive(:run_eslint_for_changed_files).and_return(0)
|
||||
|
||||
expect(instance.process_command_and_determine_exit_status).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns exit code when eslint fails' do
|
||||
allow(ARGV).to receive(:first).and_return('eslint')
|
||||
allow(instance).to receive(:run_eslint_for_changed_files).and_return(11)
|
||||
|
||||
expect(instance.process_command_and_determine_exit_status).to eq(11)
|
||||
end
|
||||
|
||||
it 'returns 1 for unknown commands' do
|
||||
allow(ARGV).to receive(:first).and_return('unknown')
|
||||
|
||||
expect(instance.process_command_and_determine_exit_status).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CI
|
||||
class ChangedFiles
|
||||
FRONTEND_FILES_FILTER = /\.(js|cjs|mjs|vue)$/
|
||||
|
||||
def initialize(env: ENV, args: ARGV)
|
||||
@env = env
|
||||
@args = args
|
||||
end
|
||||
|
||||
private attr_reader :env, :args
|
||||
|
||||
def should_run_checks_for_changed_files
|
||||
is_valid_mr_event = env['CI_PIPELINE_SOURCE'] == 'merge_request_event' &&
|
||||
env['CI_MERGE_REQUEST_EVENT_TYPE'] != 'merge_train'
|
||||
|
||||
labels = env['CI_MERGE_REQUEST_LABELS']
|
||||
|
||||
is_tier_1_pipeline = labels.nil? || labels.include?('pipeline::tier-1')
|
||||
|
||||
is_not_master_branch = env['CI_COMMIT_REF_NAME'] != env['CI_DEFAULT_BRANCH']
|
||||
|
||||
is_valid_mr_event && is_tier_1_pipeline && is_not_master_branch
|
||||
end
|
||||
|
||||
# See: https://gitlab.com/groups/gitlab-org/-/epics/16845#note_2370956250
|
||||
# for why we use `HEAD~` to compare
|
||||
def get_changed_files_in_merged_results_pipeline
|
||||
`git diff --name-only --diff-filter=d HEAD~..HEAD`.split("\n")
|
||||
end
|
||||
|
||||
def filter_and_get_changed_files_in_mr(filter_pattern: //)
|
||||
changed_files =
|
||||
if should_run_checks_for_changed_files
|
||||
get_changed_files_in_merged_results_pipeline.grep(filter_pattern)
|
||||
else
|
||||
puts "Changed file criteria didn't match... Command will run for all files"
|
||||
['.']
|
||||
end
|
||||
|
||||
puts 'No files were changed. Skipping...' if changed_files.empty?
|
||||
|
||||
changed_files
|
||||
end
|
||||
|
||||
def run_eslint_for_changed_files
|
||||
puts 'Running ESLint...'
|
||||
files = filter_and_get_changed_files_in_mr(filter_pattern: FRONTEND_FILES_FILTER)
|
||||
|
||||
return 0 if files.empty?
|
||||
|
||||
command = ["yarn", "run", "lint:eslint", "--format", "gitlab", *files]
|
||||
system(*command)
|
||||
|
||||
last_command_status.exitstatus
|
||||
end
|
||||
|
||||
def last_command_status
|
||||
$?
|
||||
end
|
||||
|
||||
def process_command_and_determine_exit_status
|
||||
return 0 if args.empty?
|
||||
|
||||
command = args.first
|
||||
|
||||
case command
|
||||
when "eslint"
|
||||
run_eslint_for_changed_files
|
||||
else
|
||||
warn "Unknown command: #{command}"
|
||||
1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
runner = CI::ChangedFiles.new
|
||||
exit runner.process_command_and_determine_exit_status
|
||||
end
|
||||
Loading…
Reference in New Issue