Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9438e2d741
commit
140dc6e06d
|
|
@ -1 +1 @@
|
|||
707429494a96d985d94e0b10da1c1c5519fa5fb6
|
||||
3de4744135aca47419474d244258a5e285dad834
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
31259f5394382f015730d7bbfeb775623d69145f
|
||||
7e5564cdc5834808c2d668ffd5788146defaf881
|
||||
|
|
|
|||
|
|
@ -1,3 +1,20 @@
|
|||
<script>
|
||||
import PageHeading from '~/vue_shared/components/page_heading.vue';
|
||||
import AiCatalogNavTabs from './components/ai_catalog_nav_tabs.vue';
|
||||
|
||||
export default {
|
||||
name: 'AiCatalogApp',
|
||||
components: {
|
||||
AiCatalogNavTabs,
|
||||
PageHeading,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<div>
|
||||
<page-heading :heading="s__('AI|AI Catalog')" />
|
||||
<ai-catalog-nav-tabs />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
import { GlTab, GlTabs } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { AI_CATALOG_AGENTS_ROUTE } from '../router/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlTab,
|
||||
GlTabs,
|
||||
},
|
||||
computed: {
|
||||
tabs() {
|
||||
return [
|
||||
{
|
||||
text: s__('AI|Agents'),
|
||||
route: AI_CATALOG_AGENTS_ROUTE,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
navigateTo(route) {
|
||||
if (this.$route.path !== route) {
|
||||
this.$router.push({ name: route });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-mb-4 gl-flex lg:gl-items-center">
|
||||
<gl-tabs content-class="gl-py-0" class="gl-w-full">
|
||||
<gl-tab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.text"
|
||||
:title="tab.text"
|
||||
@click="navigateTo(tab.route)"
|
||||
/>
|
||||
</gl-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
query aiCatalogAgents {
|
||||
aiCatalogAgents @client {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
description
|
||||
model
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
query userWorkflows {
|
||||
currentUser @client {
|
||||
id
|
||||
workflows {
|
||||
nodes {
|
||||
id
|
||||
type
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import createDefaultClient from '~/lib/graphql';
|
|||
import AiCatalogApp from './ai_catalog_app.vue';
|
||||
import { createRouter } from './router';
|
||||
|
||||
import userWorkflowsQuery from './graphql/user_workflows.query.graphql';
|
||||
import aiCatalogAgentsQuery from './graphql/ai_catalog_agents.query.graphql';
|
||||
|
||||
export const initAiCatalog = (selector = '#js-ai-catalog') => {
|
||||
const el = document.querySelector(selector);
|
||||
|
|
@ -26,24 +26,23 @@ export const initAiCatalog = (selector = '#js-ai-catalog') => {
|
|||
|
||||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
apolloProvider.clients.defaultClient.cache.writeQuery({
|
||||
query: userWorkflowsQuery,
|
||||
query: aiCatalogAgentsQuery,
|
||||
data: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
workflows: {
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Workflow 1',
|
||||
type: 'Type 1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Workflow 2',
|
||||
type: 'Type 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
aiCatalogAgents: {
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Claude Sonnet 4',
|
||||
description: 'Smart, efficient model for everyday user',
|
||||
model: 'claude-sonnet-4-20250514',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Claude Opus 4',
|
||||
description: 'Powerful, large model for complex challenges',
|
||||
model: 'claude-opus-4-20250514',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<script>
|
||||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
import aiCatalogAgentsQuery from '../graphql/ai_catalog_agents.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'AiCatalogAgents',
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
},
|
||||
apollo: {
|
||||
aiCatalogAgents: {
|
||||
query: aiCatalogAgentsQuery,
|
||||
update: (data) => data.aiCatalogAgents.nodes,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
aiCatalogAgents: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.aiCatalogAgents.loading;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="isLoading">
|
||||
<gl-skeleton-loader />
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- Replace this content with generic visualization component -->
|
||||
<div v-for="agent in aiCatalogAgents" :key="agent.id">
|
||||
<p>{{ agent.name }}</p>
|
||||
<p>{{ agent.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<script>
|
||||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
import userWorkflowsQuery from '../graphql/user_workflows.query.graphql';
|
||||
|
||||
export default {
|
||||
name: 'AiCatalogIndex',
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
},
|
||||
apollo: {
|
||||
userWorkflows: {
|
||||
query: userWorkflowsQuery,
|
||||
update: (data) => data.currentUser.workflows.nodes,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userWorkflows: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.userWorkflows.loading;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ s__('AI|AI Catalog') }}</h1>
|
||||
<div v-if="isLoading">
|
||||
<gl-skeleton-loader />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="workflow in userWorkflows" :key="workflow.id">
|
||||
<p>{{ workflow.name }}</p>
|
||||
<p>{{ workflow.type }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1 +1,2 @@
|
|||
export const AI_CATALOG_INDEX_ROUTE = 'ai-catalog';
|
||||
export const AI_CATALOG_AGENTS_ROUTE = '/agents';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import AiCatalogIndex from '../pages/ai_catalog_index.vue';
|
||||
import { AI_CATALOG_INDEX_ROUTE } from './constants';
|
||||
import AiCatalogAgents from '../pages/ai_catalog_agents.vue';
|
||||
import { AI_CATALOG_INDEX_ROUTE, AI_CATALOG_AGENTS_ROUTE } from './constants';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
|
@ -13,7 +13,12 @@ export const createRouter = (base) => {
|
|||
{
|
||||
name: AI_CATALOG_INDEX_ROUTE,
|
||||
path: '',
|
||||
component: AiCatalogIndex,
|
||||
component: AiCatalogAgents,
|
||||
},
|
||||
{
|
||||
name: AI_CATALOG_AGENTS_ROUTE,
|
||||
path: '/agents',
|
||||
component: AiCatalogAgents,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { TaskItem } from '@tiptap/extension-task-item';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { truncate } from '~/lib/utils/text_utility';
|
||||
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
||||
|
||||
export default TaskItem.extend({
|
||||
|
|
@ -55,12 +57,31 @@ export default TaskItem.extend({
|
|||
const nodeView = this.parent?.();
|
||||
return ({ node, ...args }) => {
|
||||
const nodeViewInstance = nodeView({ node, ...args });
|
||||
const checkbox = nodeViewInstance.dom.querySelector('input[type=checkbox]');
|
||||
|
||||
const updateAriaLabel = (textContent) => {
|
||||
checkbox.setAttribute(
|
||||
'aria-label',
|
||||
sprintf(__('Check option: %{option}'), {
|
||||
option: truncate(textContent, 100),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
updateAriaLabel(node.firstChild.textContent);
|
||||
|
||||
if (node.attrs.inapplicable) {
|
||||
nodeViewInstance.dom.querySelector('input[type=checkbox]').disabled = true;
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
|
||||
return nodeViewInstance;
|
||||
return {
|
||||
...nodeViewInstance,
|
||||
update: (updatedNode) => {
|
||||
const result = nodeViewInstance.update(updatedNode);
|
||||
updateAriaLabel(updatedNode.firstChild.textContent);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export default {
|
|||
>
|
||||
<template v-if="item.edit_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
|
||||
v-gl-tooltip="$options.i18n.editLabel"
|
||||
data-testid="feature-flag-edit-button"
|
||||
class="gl-flex-grow"
|
||||
icon="pencil"
|
||||
|
|
@ -215,7 +215,7 @@ export default {
|
|||
</template>
|
||||
<template v-if="item.destroy_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
|
||||
v-gl-tooltip="$options.i18n.deleteLabel"
|
||||
class="gl-flex-grow"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
|
|
@ -232,7 +232,7 @@ export default {
|
|||
>
|
||||
<template v-if="item.edit_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
|
||||
v-gl-tooltip="$options.i18n.editLabel"
|
||||
data-testid="feature-flag-edit-button"
|
||||
class="gl-flex-grow"
|
||||
icon="pencil"
|
||||
|
|
@ -242,7 +242,7 @@ export default {
|
|||
</template>
|
||||
<template v-if="item.destroy_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
|
||||
v-gl-tooltip="$options.i18n.deleteLabel"
|
||||
data-testid="feature-flag-delete-button"
|
||||
class="gl-flex-grow"
|
||||
variant="danger"
|
||||
|
|
|
|||
|
|
@ -159,7 +159,6 @@ export default {
|
|||
{
|
||||
key: 'progress',
|
||||
label: __('Status'),
|
||||
tdClass: '!gl-align-middle',
|
||||
tdAttr: { 'data-testid': 'import-status-indicator' },
|
||||
},
|
||||
{
|
||||
|
|
@ -916,7 +915,7 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
<template #cell(progress)="{ item: group }">
|
||||
<div class="gl-mt-3">
|
||||
<div class="gl-mt-2 gl-pt-1">
|
||||
<import-status-cell
|
||||
class="gl-items-end lg:gl-items-start"
|
||||
:status="group.visibleStatus"
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ export default {
|
|||
IssuableList,
|
||||
IssueCardStatistics,
|
||||
IssueCardTimeInfo,
|
||||
WorkItemStatusBadge: () =>
|
||||
import('ee_component/work_items/components/shared/work_item_status_badge.vue'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
|
@ -101,6 +103,7 @@ export default {
|
|||
'isPublicVisibilityRestricted',
|
||||
'isSignedIn',
|
||||
'rssPath',
|
||||
'isStatusFeatureEnabledOnInstance',
|
||||
],
|
||||
data() {
|
||||
const state = getParameterByName(PARAM_STATE);
|
||||
|
|
@ -501,6 +504,9 @@ export default {
|
|||
Sentry.captureException(error);
|
||||
});
|
||||
},
|
||||
showStatusBadge(issuable) {
|
||||
return issuable?.status && this.isStatusFeatureEnabledOnInstance;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -558,6 +564,16 @@ export default {
|
|||
<issue-card-statistics :issue="issuable" />
|
||||
</template>
|
||||
|
||||
<template #custom-status="{ issuable = {} }">
|
||||
<div v-if="showStatusBadge(issuable)" class="gl-max-w-20">
|
||||
<work-item-status-badge
|
||||
:name="issuable.status.name"
|
||||
:icon-name="issuable.status.iconName"
|
||||
:color="issuable.status.color"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #empty-state>
|
||||
<gl-empty-state
|
||||
:description="emptyStateDescription"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export async function mountIssuesDashboardApp() {
|
|||
isPublicVisibilityRestricted,
|
||||
isSignedIn,
|
||||
rssPath,
|
||||
isStatusFeatureEnabledOnInstance,
|
||||
} = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
|
|
@ -61,6 +62,7 @@ export async function mountIssuesDashboardApp() {
|
|||
isPublicVisibilityRestricted: parseBoolean(isPublicVisibilityRestricted),
|
||||
isSignedIn: parseBoolean(isSignedIn),
|
||||
rssPath,
|
||||
isStatusFeatureEnabledOnInstance: parseBoolean(isStatusFeatureEnabledOnInstance),
|
||||
},
|
||||
render: (createComponent) => createComponent(IssuesDashboardApp),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -489,14 +489,81 @@
|
|||
list-style-type: none;
|
||||
position: relative;
|
||||
min-height: 22px;
|
||||
padding-inline-start: 28px;
|
||||
padding-inline-start: 32px;
|
||||
margin-inline-start: 0 !important;
|
||||
|
||||
> p > input.task-list-item-checkbox,
|
||||
> input.task-list-item-checkbox {
|
||||
position: absolute;
|
||||
inset-inline-start: $gl-padding-8;
|
||||
inset-block-start: 5px;
|
||||
inset-block-start: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul[data-type=taskList] input[type=checkbox],
|
||||
ul.task-list .task-list-item-checkbox {
|
||||
all: unset;
|
||||
@apply gl-w-5 gl-h-5 gl-cursor-pointer gl-box-border gl-rounded-base focus-visible:gl-focus;
|
||||
background-color: var(--gl-control-background-color-default);
|
||||
border: 1px solid var(--gl-control-border-color-default);
|
||||
|
||||
// Ensure size of the target for pointer inputs is at least 24 pixels to satisfy WCAG 2.5.8.
|
||||
&::before {
|
||||
content: '';
|
||||
margin-top: calc(-#{$gl-spacing-scale-2} - 1px);
|
||||
margin-left: calc(-#{$gl-spacing-scale-2} - 1px);
|
||||
@apply gl-absolute gl-z-1 gl-w-6 gl-h-6 gl-bg-transparent;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
border-color: var(--gl-control-border-color-hover);
|
||||
}
|
||||
|
||||
&:not(:disabled):focus {
|
||||
@apply gl-focus;
|
||||
border-color: var(--gl-control-border-color-focus);
|
||||
}
|
||||
|
||||
&:checked {
|
||||
background-color: var(--gl-control-background-color-selected-default);
|
||||
border-color: var(--gl-control-border-color-selected-default);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@apply gl-absolute gl-z-2 gl-w-5 gl-h-5 gl-mt-[-1px] gl-ml-[-1px] gl-rounded-base;
|
||||
mask: url("#{$gl-icon-check}") center center no-repeat;
|
||||
background-color: var(--gl-control-indicator-color-selected);
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
background-color: LinkText; // stylelint-disable-line scale-unlimited/declaration-strict-value
|
||||
border-color: LinkText; // stylelint-disable-line scale-unlimited/declaration-strict-value
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:disabled):checked:hover {
|
||||
background-color: var(--gl-control-background-color-selected-hover);
|
||||
border-color: var(--gl-control-border-color-selected-hover);
|
||||
}
|
||||
|
||||
&:not(:disabled):checked:focus {
|
||||
background-color: var(--gl-control-background-color-selected-focus);
|
||||
border-color: var(--gl-control-border-color-selected-focus);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--gl-control-background-color-disabled);
|
||||
border-color: var(--gl-control-border-color-disabled);
|
||||
@apply gl-cursor-not-allowed gl-text-disabled;
|
||||
}
|
||||
|
||||
&:disabled:checked {
|
||||
background-color: var(--gl-control-background-color-disabled);
|
||||
border-color: var(--gl-control-border-color-disabled);
|
||||
|
||||
&::after {
|
||||
background-color: var(--gl-control-indicator-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,7 +225,8 @@ module IssuesHelper
|
|||
is_public_visibility_restricted:
|
||||
Gitlab::CurrentSettings.restricted_visibility_levels&.include?(Gitlab::VisibilityLevel::PUBLIC).to_s,
|
||||
is_signed_in: current_user.present?.to_s,
|
||||
rss_path: url_for(safe_params.merge(rss_url_options))
|
||||
rss_path: url_for(safe_params.merge(rss_url_options)),
|
||||
is_status_feature_enabled_on_instance: Feature.enabled?(:work_item_status_feature_flag, :instance).to_s
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class RemoveValidateEpicWorkItemsSyncWorkerJobInstances < Gitlab::Database::Migration[2.3]
|
||||
DEPRECATED_JOB_CLASS = %w[
|
||||
ValidateEpicWorkItemSyncWorker
|
||||
]
|
||||
disable_ddl_transaction!
|
||||
milestone '18.2'
|
||||
|
||||
def up
|
||||
sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASS)
|
||||
end
|
||||
|
||||
def down; end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
b6a20c3d430adab4ffc8f216accb11b23a56fa6e9f0a95e320beaf0ca8caa6f5
|
||||
|
|
@ -296,7 +296,7 @@ help integrators set its fields.
|
|||
The format is extensively described in the documentation of
|
||||
[SAST](../../user/application_security/sast/_index.md#download-a-sast-report),
|
||||
[DAST](../../user/application_security/dast/browser/_index.md),
|
||||
[Dependency Scanning](../../user/application_security/dependency_scanning/_index.md#output),
|
||||
[Dependency Scanning](../../user/application_security/dependency_scanning/_index.md#understanding-the-results),
|
||||
and [Container Scanning](../../user/application_security/container_scanning/_index.md#reports-json-format)
|
||||
|
||||
You can find the schemas for these scanners here:
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ and complete an integration with the Secure stage.
|
|||
- Your report artifact must be in one of our supported formats.
|
||||
For more information, see the [documentation on reports](secure.md#report).
|
||||
- Documentation for [SAST output](../../user/application_security/sast/_index.md#download-a-sast-report).
|
||||
- Documentation for [Dependency Scanning reports](../../user/application_security/dependency_scanning/_index.md#output).
|
||||
- Documentation for [Dependency Scanning reports](../../user/application_security/dependency_scanning/_index.md#understanding-the-results).
|
||||
- Documentation for [Container Scanning reports](../../user/application_security/container_scanning/_index.md#reports-json-format).
|
||||
- See this [example secure job definition that also defines the artifact created](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml).
|
||||
- If you need a new kind of scan or report, [create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new#)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ GitLab offers security analyzers that can generate a report compatible with GitL
|
|||
|
||||
- [Container Scanning](../container_scanning/_index.md#getting-started)
|
||||
- [Container Scanning For Registry](../container_scanning/_index.md#container-scanning-for-registry)
|
||||
- [Dependency Scanning](../dependency_scanning/_index.md#configuration)
|
||||
- [Dependency Scanning](../dependency_scanning/_index.md#getting-started)
|
||||
- [Dependency Scanning CI/CD Component](https://gitlab.com/explore/catalog/components/dependency-scanning) (experimental)
|
||||
|
||||
## Checking new vulnerabilities
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -45,52 +45,127 @@ ensure coverage for all of these dependency types. To cover as much of your risk
|
|||
we encourage you to use all of our security scanners. For a comparison of these features, see
|
||||
[Dependency Scanning compared to Container Scanning](../../comparison_dependency_and_container_scanning.md).
|
||||
|
||||
## How it scans an application
|
||||
## Getting started
|
||||
|
||||
The dependency scanning using SBOM approach relies on two distinct phases:
|
||||
Enable the Dependency Scanning using SBOM feature with one of the following options:
|
||||
|
||||
- First, the dependency detection phase that focuses solely on creating a comprehensive inventory of your
|
||||
project’s dependencies and their relationship (dependency graph). This inventory is captured in an SBOM (Software Bill of Materials)
|
||||
document.
|
||||
- Second, after the CI/CD pipeline completes, the GitLab platform processes your SBOM report and performs
|
||||
a thorough security analysis using the built-in GitLab SBOM Vulnerability Scanner. It is the same scanner
|
||||
that provides [Continuous Vulnerability Scanning](../../continuous_vulnerability_scanning/_index.md).
|
||||
- Use the `latest` Dependency Scanning CI/CD template `Dependency-Scanning.latest.gitlab-ci.yml` to enable a GitLab provided analyzer.
|
||||
- The (deprecated) Gemnasium analyzer is used by default.
|
||||
- To enable the new Dependency Scanning analyzer, set the CI/CD variable `DS_ENFORCE_NEW_ANALYZER` to `true`.
|
||||
- Use the [Scan Execution Policies](../../policies/scan_execution_policies.md) with the `latest` template to enable a GitLab provided analyzer.
|
||||
- The (deprecated) Gemnasium analyzer is used by default.
|
||||
- To enable the new Dependency Scanning analyzer, set the CI/CD variable `DS_ENFORCE_NEW_ANALYZER` to `true`.
|
||||
- Use the [Dependency Scanning CI/CD component](https://gitlab.com/explore/catalog/components/dependency-scanning) to enable the new Dependency Scanning analyzer.
|
||||
- Provide your own CycloneDX SBOM document as [a CI/CD artifact report](../../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) from a successful pipeline.
|
||||
|
||||
This separation of concerns and the modularity of this architecture allows to better support customers through expansion
|
||||
of language support, a tighter integration and experience within the GitLab platform, and a shift towards industry standard
|
||||
report types.
|
||||
The preferred method is to use the new Dependency Scanning analyzer and this is what is documented in the next section.
|
||||
To enable the (deprecated) Gemnasium analyzer, refer to the enablement instructions for the [legacy Dependency Scanning feature](../_index.md#getting-started).
|
||||
|
||||
## Dependency detection
|
||||
## Understanding the results
|
||||
|
||||
Dependency scanning using SBOM requires the detected dependencies to be captured in a CycloneDX SBOM document.
|
||||
However, the modular aspect of this functionality allows you to select how this document is generated:
|
||||
The dependency scanning analyzer produces CycloneDX Software Bill of Materials (SBOM) for each supported
|
||||
lock file or dependency graph export detected.
|
||||
|
||||
- Using the Dependency Scanning analyzer provided by GitLab (recommended)
|
||||
- Using the (deprecated) Gemnasium analyzer provided by GitLab
|
||||
- Using a custom job with a 3rd party CycloneDX SBOM generator or a custom tool.
|
||||
### CycloneDX Software Bill of Materials
|
||||
|
||||
In order to activate dependency scanning using SBOM, the provided CycloneDX SBOM document must:
|
||||
The dependency scanning analyzer outputs a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM)
|
||||
for each supported lock or dependency graph export it detects. The CycloneDX SBOMs are created as job artifacts.
|
||||
|
||||
- Comply with [the CycloneDX specification](https://github.com/CycloneDX/specification) version `1.4`, `1.5`, or `1.6`. Online validator available on [CycloneDX Web Tool](https://cyclonedx.github.io/cyclonedx-web-tool/validate).
|
||||
- Comply with the GitLab CycloneDX property taxonomy.
|
||||
- Be uploaded as [a CI/CD artifact report](../../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) from a successful pipeline.
|
||||
The CycloneDX SBOMs are:
|
||||
|
||||
When using GitLab provided analyzers, these requirements are met.
|
||||
- Named `gl-sbom-<package-type>-<package-manager>.cdx.json`.
|
||||
- Available as job artifacts of the dependency scanning job.
|
||||
- Uploaded as `cyclonedx` reports.
|
||||
- Saved in the same directory as the detected lock or dependency graph exports files.
|
||||
|
||||
## Security analysis
|
||||
For example, if your project has the following structure:
|
||||
|
||||
Once a compatible CycloneDX SBOM document is uploaded, GitLab automatically performs the security analysis
|
||||
with the GitLab SBOM Vulnerability Scanner. Each component is checked against the GitLab Advisory Database and
|
||||
scan results are processed in the following manners:
|
||||
```plaintext
|
||||
.
|
||||
├── ruby-project/
|
||||
│ └── Gemfile.lock
|
||||
├── ruby-project-2/
|
||||
│ └── Gemfile.lock
|
||||
└── php-project/
|
||||
└── composer.lock
|
||||
```
|
||||
|
||||
If the SBOM report is declared by a CI/CD job on the default branch: vulnerabilities are created,
|
||||
and can be seen in the [vulnerability report](../../vulnerability_report/_index.md).
|
||||
The following CycloneDX SBOMs are created as job artifacts:
|
||||
|
||||
If the SBOM report is declared by a CI/CD job on a non-default branch: security findings are created,
|
||||
and can be seen in the [security tab of the pipeline view](../../detect/security_scanning_results.md) and MR security widget.
|
||||
This functionality is behing a feature flag and tracked in [Epic 14636](https://gitlab.com/groups/gitlab-org/-/epics/14636).
|
||||
```plaintext
|
||||
.
|
||||
├── ruby-project/
|
||||
│ ├── Gemfile.lock
|
||||
│ └── gl-sbom-gem-bundler.cdx.json
|
||||
├── ruby-project-2/
|
||||
│ ├── Gemfile.lock
|
||||
│ └── gl-sbom-gem-bundler.cdx.json
|
||||
└── php-project/
|
||||
├── composer.lock
|
||||
└── gl-sbom-packagist-composer.cdx.json
|
||||
```
|
||||
|
||||
### Supported package types
|
||||
### Merging multiple CycloneDX SBOMs
|
||||
|
||||
You can use a CI/CD job to merge the multiple CycloneDX SBOMs into a single SBOM.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
GitLab uses [CycloneDX Properties](https://cyclonedx.org/use-cases/#properties--name-value-store)
|
||||
to store implementation-specific details in the metadata of each CycloneDX SBOM, such as the
|
||||
location of dependency graph exports and lock files. If multiple CycloneDX SBOMs are merged together,
|
||||
this information is removed from the resulting merged file.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
For example, the following `.gitlab-ci.yml` extract demonstrates how the Cyclone SBOM files can be
|
||||
merged, and the resulting file validated.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- test
|
||||
- merge-cyclonedx-sboms
|
||||
|
||||
include:
|
||||
- component: $CI_SERVER_FQDN/components/dependency-scanning/main@0
|
||||
|
||||
merge cyclonedx sboms:
|
||||
stage: merge-cyclonedx-sboms
|
||||
image:
|
||||
name: cyclonedx/cyclonedx-cli:0.27.1
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- find . -name "gl-sbom-*.cdx.json" -exec cyclonedx merge --output-file gl-sbom-all.cdx.json --input-files "{}" +
|
||||
# optional: validate the merged sbom
|
||||
- cyclonedx validate --input-version v1_6 --input-file gl-sbom-all.cdx.json
|
||||
artifacts:
|
||||
paths:
|
||||
- gl-sbom-all.cdx.json
|
||||
```
|
||||
|
||||
## Optimization
|
||||
|
||||
To optimize Dependency Scanning with SBOM according to your requirements you can:
|
||||
|
||||
- Exclude files and directories from the scan.
|
||||
- Define the max depth to look for files.
|
||||
|
||||
### Exclude files and directories from the scan
|
||||
|
||||
To exclude files or directories from being scanned, use `DS_EXCLUDED_PATHS` with a comma-separated list of patterns in your `.gitlab-ci.yml`. This will prevent specified files and directories from being targeted by the scan.
|
||||
|
||||
### Define the max depth to look for files
|
||||
|
||||
To optmize the analyzer behavior you may set a max depth value through the `DS_MAX_DEPTH` environment variable. A value of `-1` scans all directories regardless of depth. The default is `2`.
|
||||
|
||||
## Roll out
|
||||
|
||||
After you are confident in the Dependency Scanning with SBOM results for a single project, you can extend its implementation to additional projects:
|
||||
|
||||
- Use [enforced scan execution](../../detect/security_configuration.md#create-a-shared-configuration) to apply Dependency Scanning with SBOM settings across groups.
|
||||
- If you have unique requirements, Dependency Scanning with SBOM can be run in [offline environments](../../offline_deployments/_index.md).
|
||||
|
||||
## Supported package types
|
||||
|
||||
For the security analysis to be effective, the components listed in your SBOM report must have corresponding
|
||||
entries in the [GitLab Advisory Database](../../gitlab_advisory_database/_index.md).
|
||||
|
|
@ -122,7 +197,7 @@ Enable the Dependency Scanning using SBOM feature with one of the following opti
|
|||
- Provide your own CycloneDX SBOM document as [a CI/CD artifact report](../../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) from a successful pipeline.
|
||||
|
||||
The preferred method is to use the new Dependency Scanning analyzer and this is what is documented in the next section.
|
||||
To enable the (deprecated) Gemnasium analyzer, refer to the enablement instructions for the [legacy Dependency Scanning feature](../_index.md#enabling-the-analyzer).
|
||||
To enable the (deprecated) Gemnasium analyzer, refer to the enablement instructions for the [legacy Dependency Scanning feature](../_index.md#getting-started).
|
||||
|
||||
## Enabling the analyzer
|
||||
|
||||
|
|
@ -483,7 +558,7 @@ The following variables allow configuration of global dependency scanning settin
|
|||
| `DS_PIPCOMPILE_REQUIREMENTS_FILE_NAME_PATTERN` | Defines which requirement files to process using glob pattern matching (for example, `requirements*.txt` or `*-requirements.txt`). The pattern should match filenames only, not directory paths. See [glob pattern documentation](https://github.com/bmatcuk/doublestar/tree/v1?tab=readme-ov-file#patterns) for syntax details. |
|
||||
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). |
|
||||
|
||||
#### Overriding dependency scanning jobs
|
||||
##### Overriding dependency scanning jobs
|
||||
|
||||
To override a job definition declare a new job with the same name as the one to override.
|
||||
Place this new job after the template inclusion and specify any additional keys under it.
|
||||
|
|
@ -501,93 +576,50 @@ dependency-scanning:
|
|||
|
||||
When using the Dependency Scanning CI/CD component, the analyzer can be customized by configuring the [inputs](https://gitlab.com/explore/catalog/components/dependency-scanning).
|
||||
|
||||
## Output
|
||||
## How it scans an application
|
||||
|
||||
The dependency scanning analyzer produces CycloneDX Software Bill of Materials (SBOM) for each supported
|
||||
lock file or dependency graph export detected.
|
||||
The dependency scanning using SBOM approach relies on two distinct phases:
|
||||
|
||||
### CycloneDX Software Bill of Materials
|
||||
- First, the dependency detection phase that focuses solely on creating a comprehensive inventory of your
|
||||
project’s dependencies and their relationship (dependency graph). This inventory is captured in an SBOM (Software Bill of Materials)
|
||||
document.
|
||||
- Second, after the CI/CD pipeline completes, the GitLab platform processes your SBOM report and performs
|
||||
a thorough security analysis using the built-in GitLab SBOM Vulnerability Scanner. It is the same scanner
|
||||
that provides [Continuous Vulnerability Scanning](../../continuous_vulnerability_scanning/_index.md).
|
||||
|
||||
{{< history >}}
|
||||
This separation of concerns and the modularity of this architecture allows to better support customers through expansion
|
||||
of language support, a tighter integration and experience within the GitLab platform, and a shift towards industry standard
|
||||
report types.
|
||||
|
||||
- Generally available in GitLab 15.7.
|
||||
## Dependency detection
|
||||
|
||||
{{< /history >}}
|
||||
Dependency scanning using SBOM requires the detected dependencies to be captured in a CycloneDX SBOM document.
|
||||
However, the modular aspect of this functionality allows you to select how this document is generated:
|
||||
|
||||
The dependency scanning analyzer outputs a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM)
|
||||
for each supported lock or dependency graph export it detects. The CycloneDX SBOMs are created as job artifacts.
|
||||
- Using the Dependency Scanning analyzer provided by GitLab (recommended)
|
||||
- Using the (deprecated) Gemnasium analyzer provided by GitLab
|
||||
- Using a custom job with a 3rd party CycloneDX SBOM generator or a custom tool.
|
||||
|
||||
The CycloneDX SBOMs are:
|
||||
To activate dependency scanning using SBOM, the provided CycloneDX SBOM document must:
|
||||
|
||||
- Named `gl-sbom-<package-type>-<package-manager>.cdx.json`.
|
||||
- Available as job artifacts of the dependency scanning job.
|
||||
- Uploaded as `cyclonedx` reports.
|
||||
- Saved in the same directory as the detected lock or dependency graph exports files.
|
||||
- Comply with [the CycloneDX specification](https://github.com/CycloneDX/specification) version `1.4`, `1.5`, or `1.6`. Online validator available on [CycloneDX Web Tool](https://cyclonedx.github.io/cyclonedx-web-tool/validate).
|
||||
- Comply with [the GitLab CycloneDX property taxonomy](../../../../development/sec/cyclonedx_property_taxonomy.md).
|
||||
- Be uploaded as [a CI/CD artifact report](../../../../ci/yaml/artifacts_reports.md#artifactsreportscyclonedx) from a successful pipeline.
|
||||
|
||||
For example, if your project has the following structure:
|
||||
When using GitLab-provided analyzers, these requirements are met.
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── ruby-project/
|
||||
│ └── Gemfile.lock
|
||||
├── ruby-project-2/
|
||||
│ └── Gemfile.lock
|
||||
└── php-project/
|
||||
└── composer.lock
|
||||
```
|
||||
## Security analysis
|
||||
|
||||
The following CycloneDX SBOMs are created as job artifacts:
|
||||
After a compatible CycloneDX SBOM document is uploaded, GitLab automatically performs the security analysis
|
||||
with the GitLab SBOM Vulnerability Scanner. Each component is checked against the GitLab Advisory Database and
|
||||
scan results are processed in the following manners:
|
||||
|
||||
```plaintext
|
||||
.
|
||||
├── ruby-project/
|
||||
│ ├── Gemfile.lock
|
||||
│ └── gl-sbom-gem-bundler.cdx.json
|
||||
├── ruby-project-2/
|
||||
│ ├── Gemfile.lock
|
||||
│ └── gl-sbom-gem-bundler.cdx.json
|
||||
└── php-project/
|
||||
├── composer.lock
|
||||
└── gl-sbom-packagist-composer.cdx.json
|
||||
```
|
||||
If the SBOM report is declared by a CI/CD job on the default branch: vulnerabilities are created,
|
||||
and can be seen in the [vulnerability report](../../vulnerability_report/_index.md).
|
||||
|
||||
### Merging multiple CycloneDX SBOMs
|
||||
|
||||
You can use a CI/CD job to merge the multiple CycloneDX SBOMs into a single SBOM.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
GitLab uses [CycloneDX Properties](https://cyclonedx.org/use-cases/#properties--name-value-store)
|
||||
to store implementation-specific details in the metadata of each CycloneDX SBOM, such as the
|
||||
location of dependency graph exports and lock files. If multiple CycloneDX SBOMs are merged together,
|
||||
this information is removed from the resulting merged file.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
For example, the following `.gitlab-ci.yml` extract demonstrates how the Cyclone SBOM files can be
|
||||
merged, and the resulting file validated.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- test
|
||||
- merge-cyclonedx-sboms
|
||||
|
||||
include:
|
||||
- component: $CI_SERVER_FQDN/components/dependency-scanning/main@0
|
||||
|
||||
merge cyclonedx sboms:
|
||||
stage: merge-cyclonedx-sboms
|
||||
image:
|
||||
name: cyclonedx/cyclonedx-cli:0.27.1
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- find . -name "gl-sbom-*.cdx.json" -exec cyclonedx merge --output-file gl-sbom-all.cdx.json --input-files "{}" +
|
||||
# optional: validate the merged sbom
|
||||
- cyclonedx validate --input-version v1_6 --input-file gl-sbom-all.cdx.json
|
||||
artifacts:
|
||||
paths:
|
||||
- gl-sbom-all.cdx.json
|
||||
```
|
||||
If the SBOM report is declared by a CI/CD job on a non-default branch: security findings are created,
|
||||
and can be seen in the [security tab of the pipeline view](../../vulnerability_report/pipeline.md) and MR security widget.
|
||||
This functionality is behind a feature flag and tracked in [Epic 14636](https://gitlab.com/groups/gitlab-org/-/epics/14636).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Enable static reachability analysis to identify high-risk dependencies.
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- Enable [Dependency Scanning by using SBOM](dependency_scanning_sbom/_index.md#configuration).
|
||||
- Enable [Dependency Scanning by using SBOM](dependency_scanning_sbom/_index.md#getting-started).
|
||||
|
||||
Make sure you follow the [pip](dependency_scanning_sbom/_index.md#pip) or [pipenv](dependency_scanning_sbom/_index.md#pipenv)
|
||||
related instructions for dependency scanning using SBOM. You can also use any other Python package manager that is [supported](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning#supported-files) by the DS analyzer.
|
||||
|
|
|
|||
|
|
@ -1,22 +1,365 @@
|
|||
---
|
||||
stage: Application Security Testing
|
||||
stage: Secure
|
||||
group: Static 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
|
||||
title: Roll out security scanning
|
||||
title: 'Roll out application security testing'
|
||||
---
|
||||
|
||||
You can roll out security scanning to individual projects, subgroups, and groups. You should start
|
||||
with individual projects, then increase the scope in increments. An incremental roll out allows you
|
||||
to evaluate the results at each point and adjust as needed.
|
||||
Plan your application security testing implementation in phases to ensure a smooth transition to a
|
||||
more secure development practice.
|
||||
|
||||
To enable security scanning of individual projects:
|
||||
This guide helps you implement GitLab application security testing across your organization in
|
||||
phases. By starting with a pilot group and gradually expanding coverage, you can minimize disruption
|
||||
while maximizing security benefits. The phased approach allows your team to become familiar with
|
||||
application security testing tools and workflows before scaling to all projects.
|
||||
|
||||
- Enable individual security scanners.
|
||||
- Enable all security scanners by using AutoDevOps.
|
||||
Prerequisites:
|
||||
|
||||
To enable security scanning of multiple projects, subgroups, or groups, use one of the following
|
||||
methods:
|
||||
- GitLab Ultimate.
|
||||
- Familiarity with GitLab CI/CD pipelines. The following GitLab self-paced courses provide a good
|
||||
introduction:
|
||||
- [Introduction to CI/CD](https://university.gitlab.com/courses/introduction-to-cicd-s2)
|
||||
- [Hands-on Labs: CI Fundamentals](https://university.gitlab.com/courses/hands-on-labs-ci-fundamentals)
|
||||
- Understanding of your organization's security requirements and risk tolerance.
|
||||
|
||||
- [Scan execution policy](../policies/scan_execution_policies.md)
|
||||
- [Pipeline execution policy](../policies/pipeline_execution_policies.md)
|
||||
- [Compliance framework](../../compliance/compliance_frameworks/_index.md)
|
||||
## Scope
|
||||
|
||||
This guide covers how to plan and execute a phased implementation of GitLab application security
|
||||
testing features, including configuration, vulnerability management, and prevention
|
||||
strategies. It assumes you want to gradually introduce application security testing to minimize
|
||||
disruption to existing workflows while securing your codebase.
|
||||
|
||||
## Phases
|
||||
|
||||
The implementation consists of two main phases:
|
||||
|
||||
1. **Pilot phase**: Implement application security testing for a limited set of projects to validate
|
||||
configurations and train teams.
|
||||
1. **Rollout phase**: Expand application security testing to all target projects using the knowledge
|
||||
gained during the pilot.
|
||||
|
||||
## Pilot phase
|
||||
|
||||
The pilot phase allows you to apply application security testing with minimal risk before a wider
|
||||
rollout.
|
||||
|
||||
Consider the following guidance before starting on the pilot phase:
|
||||
|
||||
- Identify key stakeholders including security team members, developers, and project managers.
|
||||
- Select pilot projects that are representative of your codebase but not critical to daily
|
||||
operations.
|
||||
- Schedule training sessions for developers and security team members.
|
||||
- Document current security practices to measure improvements.
|
||||
|
||||
### Pilot goals
|
||||
|
||||
The pilot phase helps you achieve several key objectives:
|
||||
|
||||
- Implement application security testing without slowing development
|
||||
|
||||
During the pilot, application security testing results are available to developers in the UI,
|
||||
without blocking merge requests. This approach minimizes risk to projects outside the pilot's
|
||||
scope while collecting valuable data on your current security posture. In the rollout phase you
|
||||
should use a [merge request approval policy](#merge-request-approval-policy) to add an additional
|
||||
approval gate when vulnerabilities are detected in merge requests.
|
||||
|
||||
- Establish scalable detection methods
|
||||
|
||||
Implement application security testing on pilot projects in a way that can be expanded to include
|
||||
all projects in the wider rollout scope. Focus on configurations that scale well and can be
|
||||
standardized across projects.
|
||||
|
||||
- Test scan times
|
||||
|
||||
Test scan times on representative codebases and applications.
|
||||
|
||||
- Simulate the vulnerability remediation workflow
|
||||
|
||||
Simulate detecting, triaging, analyzing, and remediating vulnerabilities in the developer
|
||||
workflows. Verify that engineers can act on findings.
|
||||
|
||||
- Compare maintenance costs
|
||||
|
||||
Compare the maintenance of a single solution versus integrating multiple endpoint solutions. How
|
||||
well does this integrate into the IDE, merge request, and pipeline?
|
||||
|
||||
#### Benefits for developers
|
||||
|
||||
Developers in the pilot group will gain:
|
||||
|
||||
- Familiarity with application security testing methods and how to interpret results.
|
||||
- Experience preventing vulnerabilities from being merged into the default branch.
|
||||
- Understanding of the vulnerability management workflow that begins when a vulnerability is
|
||||
detected in the default branch.
|
||||
|
||||
#### Benefits for security management
|
||||
|
||||
Security team members participating in the pilot will gain:
|
||||
|
||||
- Experience with vulnerability tracking and management in GitLab.
|
||||
- Data to establish security baselines and set realistic remediation goals.
|
||||
- Insights to refine the security policy before wider rollout.
|
||||
|
||||
### Pilot plan
|
||||
|
||||
Proper planning ensures an effective pilot phase.
|
||||
|
||||
#### Roles and responsibilities
|
||||
|
||||
Define who is responsible for:
|
||||
|
||||
- Configuring application security testing
|
||||
- Reviewing scan results
|
||||
- Triaging vulnerabilities
|
||||
- Managing remediation
|
||||
- Training team members
|
||||
- Measuring the pilot's success
|
||||
|
||||
### Pilot scope
|
||||
|
||||
Carefully select which projects to include in the pilot phase.
|
||||
|
||||
Consider these factors when selecting pilot projects:
|
||||
|
||||
- Include projects with different technology stacks to test application security testing
|
||||
effectiveness.
|
||||
- Choose projects with active development to see real-time results.
|
||||
- Select projects with teams open to learning new security practices.
|
||||
- Avoid starting with mission-critical applications.
|
||||
|
||||
### Security application security testing order
|
||||
|
||||
Introduce security application security testing in the following order. This balances value and ease
|
||||
of deployment.
|
||||
|
||||
- Dependency scanning
|
||||
- SAST
|
||||
- Advanced SAST
|
||||
- Pipeline secret detection
|
||||
- Secret push protection
|
||||
- Container scanning
|
||||
- DAST
|
||||
- API security testing
|
||||
- IaC scanning
|
||||
- Operational container scanning
|
||||
|
||||
## Test pilot projects
|
||||
|
||||
With planning complete, begin implementing application security testing of your pilot projects.
|
||||
|
||||
### Set up testing of pilot projects
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Maintainer role for the projects in which application security testing is to be
|
||||
enabled.
|
||||
|
||||
For each project in scope:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Secure > Security configuration**.
|
||||
1. Expand **Security configuration**.
|
||||
1. Enable the appropriate application security testing based on your project's stack.
|
||||
|
||||
For more details, see [Security configuration](../configuration/_index.md).
|
||||
|
||||
### For developers
|
||||
|
||||
Introduce developers to the tools that provide visibility into security findings.
|
||||
|
||||
#### Pipeline results
|
||||
|
||||
Developers can view security findings directly in pipeline results:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Build > Pipelines**.
|
||||
1. Select the pipeline to review.
|
||||
1. In the pipeline details, select the **Security** tab to view detected vulnerabilities.
|
||||
|
||||
For more details, see
|
||||
[View security scan results in pipelines](../vulnerability_report/pipeline.md).
|
||||
|
||||
#### Merge request security widget
|
||||
|
||||
The security widget provides visibility into vulnerabilities detected in merge request pipelines:
|
||||
|
||||
1. Open a merge request.
|
||||
1. Review the security widget to see detected vulnerabilities.
|
||||
1. Select **Expand** to see detailed findings.
|
||||
|
||||
For more details, see [View security scan results in merge requests](security_scan_results.md).
|
||||
|
||||
#### VS Code integration with GitLab Workflow extension
|
||||
|
||||
Developers can view security findings directly in their IDE:
|
||||
|
||||
1. Install the GitLab Workflow extension for VS Code.
|
||||
1. Connect the extension to your GitLab instance.
|
||||
1. Use the extension to view security findings without leaving your development environment.
|
||||
|
||||
For more details, see
|
||||
[GitLab Workflow extension for VS Code](../../../editor_extensions/visual_studio_code/_index.md).
|
||||
|
||||
## Vulnerability management workflow
|
||||
|
||||
Establish a structured workflow for handling detected vulnerabilities.
|
||||
|
||||
The vulnerability management workflow consists of four key stages:
|
||||
|
||||
1. **Detect**: Find vulnerabilities through automated application security testing in pipelines.
|
||||
1. **Triage**: Assess the severity and impact of detected vulnerabilities.
|
||||
1. **Analyze**: Investigate the root cause and determine the best approach for remediation.
|
||||
1. **Remediate**: Implement fixes to resolve the vulnerabilities.
|
||||
|
||||
### Efficient triage
|
||||
|
||||
GitLab provides several features to streamline vulnerability triage:
|
||||
|
||||
- Vulnerability filters to focus on high-impact issues first.
|
||||
- Severity and confidence ratings to prioritize efforts.
|
||||
- Vulnerability tracking to maintain visibility of outstanding issues.
|
||||
- Risk assessment data.
|
||||
|
||||
For more details, see [Triage](../triage/_index.md).
|
||||
|
||||
Triage should include regular reviews of the vulnerability report with security stakeholders.
|
||||
|
||||
### Efficient remediation
|
||||
|
||||
Streamline the remediation process with these GitLab features:
|
||||
|
||||
- Automated remediation suggestions for certain vulnerability types.
|
||||
- Merge request creation directly from vulnerability details.
|
||||
- Vulnerability history tracking to monitor progress.
|
||||
- Automatically resolve vulnerabilities that are no longer detected.
|
||||
|
||||
For more details, see [Remediate](../remediate/_index.md).
|
||||
|
||||
#### Integrate with ticketing systems
|
||||
|
||||
You can use a GitLab issue to track the remediation work required for a vulnerability.
|
||||
Alternatively, you can use a Jira issue if that is your primary ticketing system.
|
||||
|
||||
For more details, see
|
||||
[Linking a vulnerability to GitLab and Jira issues](../vulnerabilities/_index.md#linking-a-vulnerability-to-gitlab-and-jira-issues).
|
||||
|
||||
## Vulnerability prevention
|
||||
|
||||
Implement features to prevent vulnerabilities from being introduced in the first place.
|
||||
|
||||
### Merge request approval policy
|
||||
|
||||
Use a merge request approval policy to add an extra approval requirement if the number and
|
||||
severity of vulnerabilities in a merge request exceeds a specific threshold. This allows an extra
|
||||
review from a member of the application security team, providing an extra level of scrutiny.
|
||||
|
||||
Configure approval policies to require security reviews:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Secure > Policies**.
|
||||
1. Select **New policy**
|
||||
1. In the **Merge request approval policy** pane, select **Select policy**.
|
||||
1. Add a merge request approval policy requiring approval from security team members.
|
||||
|
||||
For more details, see
|
||||
[Security approvals in merge requests](../policies/merge_request_approval_policies.md).
|
||||
|
||||
## Rollout phase
|
||||
|
||||
After a successful pilot, expand application security testing to all target projects.
|
||||
|
||||
Before starting on the rollout phase consider the following:
|
||||
|
||||
- Evaluate the results of the pilot phase.
|
||||
- Document lessons learned and best practices.
|
||||
- Prepare training materials based on pilot experiences.
|
||||
- Update implementation plans based on pilot feedback.
|
||||
|
||||
### Define access to team members
|
||||
|
||||
Application security testing tasks require specific roles or permissions. For each person taking
|
||||
part in the rollout phases, define their access according to the tasks they'll be
|
||||
performing.
|
||||
|
||||
- Users with the Developer role can view vulnerabilities on their projects and merge requests.
|
||||
- Users with the Maintainer role can configure security configurations for projects.
|
||||
- Users assigned a Custom Role with `admin_vulnerability` permission can manage and triage
|
||||
vulnerabilities.
|
||||
- Users assigned a Custom Role with `manage_security_policy_link` permission can enforce policies
|
||||
on groups and projects.
|
||||
|
||||
For more details, see
|
||||
[Roles and permissions](../../permissions.md#application-security-group-permissions).
|
||||
|
||||
### Rollout goals
|
||||
|
||||
The rollout phase aims to implement application security testing across all projects in scope,
|
||||
using the knowledge and experience gained during the pilot.
|
||||
|
||||
### Rollout plan
|
||||
|
||||
Review and update roles and responsibilities established during the pilot. The same team
|
||||
structure should work for the rollout, but you may need to add more team members as the
|
||||
scope expands.
|
||||
|
||||
## Implement application security testing at scale
|
||||
|
||||
Use policy features to efficiently scale your security implementation.
|
||||
|
||||
### Use policy inheritance
|
||||
|
||||
Use policy inheritance to maximize effectiveness while also minimizing the number of policies to be
|
||||
managed.
|
||||
|
||||
Consider the scenario in which you have a top-level group named Finance which contains subgroups A,
|
||||
B, and C. You want to run dependency scanning and secret detection on all projects in the Finance
|
||||
group. For each subgroup you want to run different sets of application security testing tools.
|
||||
|
||||
To achieve this goal, you could define 3 policies for the Finance group:
|
||||
|
||||
- Policy 1:
|
||||
- Includes dependency scanning and secret detection.
|
||||
- Applies to the Finance group, all its subgroups, and their projects.
|
||||
- Policy 2:
|
||||
- Includes DAST and API security testing.
|
||||
- Scoped to only subgroups A and B.
|
||||
- Policy 3:
|
||||
- Includes SAST.
|
||||
- Scoped to only subgroup C.
|
||||
|
||||
Only a single set of policies needs to be maintained but still provides the flexibility to suit
|
||||
the needs of different projects.
|
||||
|
||||
For more details, see [Enforcement](../policies/_index.md#enforcement).
|
||||
|
||||
### Configure scan execution policies
|
||||
|
||||
Implement consistent application security testing across multiple projects by using scan execution
|
||||
policies.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role, or a custom role with `manage_security_policy_link` permission, for
|
||||
the groups in which application security testing is to be enabled.
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. Select **Secure > Policies**.
|
||||
1. Create scan execution policies based on the application security testing configuration used
|
||||
during the pilot phase.
|
||||
|
||||
For more details, see [Security policies](../policies/_index.md).
|
||||
|
||||
### Scale gradually
|
||||
|
||||
Scale the rollout gradually, first to the pilot projects and incrementally to all target projects.
|
||||
When applying policies to all groups and projects, create awareness to all project stakeholders as
|
||||
this can impact changes in pipelines and merge request workflows. For example, notify stakeholders
|
||||
|
||||
Implement your security policies in phases:
|
||||
|
||||
1. Start by applying policies to the projects from the pilot phase.
|
||||
1. Monitor for any issues or disruptions.
|
||||
1. Gradually expand the policies' scope to include more projects.
|
||||
1. Continue until all target projects are covered.
|
||||
|
||||
For more details, see [Policy design guidelines](../policies/_index.md#policy-design-guidelines).
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ For more information, see:
|
|||
|
||||
- [Enable Secret Detection](secret_detection/pipeline/_index.md#enable-the-analyzer)
|
||||
- [Secret Detection settings](secret_detection/pipeline/configure.md)
|
||||
- [Enable Dependency Scanning](dependency_scanning/_index.md#configuration)
|
||||
- [Enable Dependency Scanning](dependency_scanning/_index.md#getting-started)
|
||||
- [Dependency Scanning settings](dependency_scanning/_index.md#available-cicd-variables)
|
||||
|
||||
## Step 4: Review scan results
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ You can find more information at each of the pages below:
|
|||
- [API Fuzzing offline directions](../api_fuzzing/configuration/offline_configuration.md)
|
||||
- [License Scanning offline directions](../../compliance/license_scanning_of_cyclonedx_files/_index.md#running-in-an-offline-environment)
|
||||
- [Dependency Scanning offline directions](../dependency_scanning/_index.md#offline-environment)
|
||||
- [IaC Scanning offline directions](../iac_scanning/_index.md#offline-configuration)
|
||||
|
||||
## Loading Docker images onto your offline host
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ No contextual information (for example, a list of project dependencies) is sent
|
|||
To enable License scanning of CycloneDX files:
|
||||
|
||||
- Using the Dependency Scanning template
|
||||
- Enable [Dependency Scanning](../../application_security/dependency_scanning/_index.md#enabling-the-analyzer)
|
||||
- Enable [Dependency Scanning](../../application_security/dependency_scanning/_index.md#getting-started)
|
||||
and ensure that its prerequisites are met.
|
||||
- On GitLab Self-Managed, you can [choose package registry metadata to synchronize](../../../administration/settings/security_and_compliance.md#choose-package-registry-metadata-to-sync) in the **Admin** area for the GitLab instance. For this data synchronization to work, you must allow outbound network traffic from your GitLab instance to the domain `storage.googleapis.com`. If you have limited or no network connectivity then refer to the documentation section [running in an offline environment](#running-in-an-offline-environment) for further guidance.
|
||||
- Or use the [CI/CD component](../../../ci/components/_index.md) for applicable package registries.
|
||||
|
|
|
|||
|
|
@ -2407,6 +2407,9 @@ msgstr ""
|
|||
msgid "AI|Accept & Insert"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|Agents"
|
||||
msgstr ""
|
||||
|
||||
msgid "AI|Apply AI-generated description"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12669,6 +12672,9 @@ msgstr ""
|
|||
msgid "Check feature availability on namespace plan"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check option: %{option}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check out branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { GlTab, GlTabs } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import AiCatalogNavTabs from '~/ai/catalog/components/ai_catalog_nav_tabs.vue';
|
||||
import { AI_CATALOG_AGENTS_ROUTE } from '~/ai/catalog/router/constants';
|
||||
|
||||
describe('AiCatalogNavTabs', () => {
|
||||
let wrapper;
|
||||
|
||||
const mockRouter = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
|
||||
const createComponent = ({ routePath = '/ai/catalog' } = {}) => {
|
||||
wrapper = shallowMountExtended(AiCatalogNavTabs, {
|
||||
mocks: {
|
||||
$route: {
|
||||
path: routePath,
|
||||
},
|
||||
$router: mockRouter,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findTabs = () => wrapper.findComponent(GlTabs);
|
||||
const findAllTabs = () => wrapper.findAllComponents(GlTab);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders tabs', () => {
|
||||
expect(findTabs().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the correct number of tabs', () => {
|
||||
expect(findAllTabs()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders the Agents tab', () => {
|
||||
const agentsTab = findAllTabs().at(0);
|
||||
|
||||
expect(agentsTab.attributes('title')).toBe('Agents');
|
||||
});
|
||||
|
||||
describe('navigation', () => {
|
||||
it('navigates to the correct route when tab is clicked', () => {
|
||||
const agentsTab = findAllTabs().at(0);
|
||||
|
||||
agentsTab.vm.$emit('click');
|
||||
|
||||
expect(mockRouter.push).toHaveBeenCalledWith({ name: AI_CATALOG_AGENTS_ROUTE });
|
||||
});
|
||||
|
||||
it('does not navigate if already on the same route', () => {
|
||||
createComponent({ routePath: AI_CATALOG_AGENTS_ROUTE });
|
||||
|
||||
const agentsTab = findAllTabs().at(0);
|
||||
|
||||
agentsTab.vm.$emit('click');
|
||||
|
||||
expect(mockRouter.push).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
import AiCatalogAgents from '~/ai/catalog/pages/ai_catalog_agents.vue';
|
||||
|
||||
describe('AiCatalogAgents', () => {
|
||||
let wrapper;
|
||||
|
||||
const mockAgentsData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Agent 1',
|
||||
description: 'Description for agent 1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Test Agent 2',
|
||||
description: 'Description for agent 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Test Agent 3',
|
||||
description: 'Description for agent 3',
|
||||
},
|
||||
];
|
||||
|
||||
const emptyAgentsData = [];
|
||||
|
||||
const createComponent = ({ loading = false, mockData = mockAgentsData } = {}) => {
|
||||
wrapper = shallowMountExtended(AiCatalogAgents, {
|
||||
data() {
|
||||
return { aiCatalogAgents: mockData };
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
aiCatalogAgents: {
|
||||
loading,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findAllParagraphs = () => wrapper.findAll('p');
|
||||
|
||||
describe('loading state', () => {
|
||||
it('shows skeleton loader when loading', () => {
|
||||
createComponent({ loading: true });
|
||||
|
||||
expect(findSkeletonLoader().exists()).toBe(true);
|
||||
expect(findAllParagraphs()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not show agent content when loading', () => {
|
||||
createComponent({ loading: true });
|
||||
|
||||
expect(findAllParagraphs()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with agent data', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('displays agent names and descriptions correctly', () => {
|
||||
const paragraphs = findAllParagraphs();
|
||||
|
||||
// Should have 6 paragraphs total (2 per agent: name and description)
|
||||
expect(paragraphs).toHaveLength(6);
|
||||
|
||||
// Check agent names (even indices: 0, 2, 4)
|
||||
expect(paragraphs.at(0).text()).toBe('Test Agent 1');
|
||||
expect(paragraphs.at(2).text()).toBe('Test Agent 2');
|
||||
expect(paragraphs.at(4).text()).toBe('Test Agent 3');
|
||||
|
||||
// Check agent descriptions (odd indices: 1, 3, 5)
|
||||
expect(paragraphs.at(1).text()).toBe('Description for agent 1');
|
||||
expect(paragraphs.at(3).text()).toBe('Description for agent 2');
|
||||
expect(paragraphs.at(5).text()).toBe('Description for agent 3');
|
||||
});
|
||||
|
||||
it('does not show skeleton loader', () => {
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with empty agent data', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({ mockData: emptyAgentsData });
|
||||
});
|
||||
|
||||
it('renders no agent paragraphs', () => {
|
||||
expect(findAllParagraphs()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not show skeleton loader', () => {
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
import AiCatalogIndex from '~/ai/catalog/pages/ai_catalog_index.vue';
|
||||
|
||||
describe('AiCatalogIndex', () => {
|
||||
let wrapper;
|
||||
|
||||
const mockWorkflowsData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Workflow 1',
|
||||
type: 'Type A',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Test Workflow 2',
|
||||
type: 'Type B',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Test Workflow 3',
|
||||
type: 'Type C',
|
||||
},
|
||||
];
|
||||
|
||||
const emptyWorkflowsData = [];
|
||||
|
||||
const createComponent = ({ loading = false, mockData = mockWorkflowsData } = {}) => {
|
||||
wrapper = shallowMountExtended(AiCatalogIndex, {
|
||||
data() {
|
||||
return { userWorkflows: mockData };
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
userWorkflows: {
|
||||
loading,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findTitle = () => wrapper.find('h1');
|
||||
const findAllParagraphs = () => wrapper.findAll('p');
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('renders the page title', async () => {
|
||||
await createComponent();
|
||||
|
||||
expect(findTitle().text()).toBe('AI Catalog');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading state', () => {
|
||||
it('shows skeleton loader when loading', () => {
|
||||
createComponent({ loading: true });
|
||||
|
||||
expect(findSkeletonLoader().exists()).toBe(true);
|
||||
expect(findAllParagraphs()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with workflow data', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('displays workflow names and types correctly', () => {
|
||||
const paragraphs = findAllParagraphs();
|
||||
|
||||
// Should have 6 paragraphs total (2 per workflow: name and type)
|
||||
expect(paragraphs).toHaveLength(6);
|
||||
|
||||
// Check workflow names (even indices: 0, 2, 4)
|
||||
expect(paragraphs.at(0).text()).toBe('Test Workflow 1');
|
||||
expect(paragraphs.at(2).text()).toBe('Test Workflow 2');
|
||||
expect(paragraphs.at(4).text()).toBe('Test Workflow 3');
|
||||
|
||||
// Check workflow types (odd indices: 1, 3, 5)
|
||||
expect(paragraphs.at(1).text()).toBe('Type A');
|
||||
expect(paragraphs.at(3).text()).toBe('Type B');
|
||||
expect(paragraphs.at(5).text()).toBe('Type C');
|
||||
});
|
||||
|
||||
it('does not show skeleton loader', () => {
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with empty workflow data', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent({ mockData: emptyWorkflowsData });
|
||||
});
|
||||
|
||||
it('renders no workflow paragraphs', () => {
|
||||
expect(findAllParagraphs()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not show skeleton loader', () => {
|
||||
expect(findSkeletonLoader().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -26,25 +26,26 @@ describe('content_editor/extensions/task_item', () => {
|
|||
tiptapEditor.commands.setContent(initialDoc.toJSON());
|
||||
|
||||
expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
|
||||
<li
|
||||
data-checked="false"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
<li
|
||||
data-checked="false"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
aria-label="Check option: foo"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders task item as disabled if it is inapplicable', () => {
|
||||
|
|
@ -53,27 +54,28 @@ describe('content_editor/extensions/task_item', () => {
|
|||
tiptapEditor.commands.setContent(initialDoc.toJSON());
|
||||
|
||||
expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
|
||||
<li
|
||||
data-checked="false"
|
||||
data-inapplicable="true"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
disabled=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
<li
|
||||
data-checked="false"
|
||||
data-inapplicable="true"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
aria-label="Check option: foo"
|
||||
disabled=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
|
||||
it('ignores any <s> tags in the task item', () => {
|
||||
|
|
@ -87,26 +89,27 @@ describe('content_editor/extensions/task_item', () => {
|
|||
`);
|
||||
|
||||
expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
|
||||
<li
|
||||
data-checked="false"
|
||||
data-inapplicable="true"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
disabled=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
<li
|
||||
data-checked="false"
|
||||
data-inapplicable="true"
|
||||
dir="auto"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
aria-label="Check option: foo"
|
||||
disabled=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span />
|
||||
</label>
|
||||
<div>
|
||||
<p
|
||||
dir="auto"
|
||||
>
|
||||
foo
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ describe('IssuesDashboardApp component', () => {
|
|||
isPublicVisibilityRestricted: false,
|
||||
isSignedIn: true,
|
||||
rssPath: 'rss/path',
|
||||
isStatusFeatureEnabledOnInstance: true,
|
||||
};
|
||||
|
||||
let defaultQueryResponse = issuesQueryResponse;
|
||||
|
|
@ -83,6 +84,14 @@ describe('IssuesDashboardApp component', () => {
|
|||
defaultQueryResponse.data.issues.nodes[0].blockingCount = 1;
|
||||
defaultQueryResponse.data.issues.nodes[0].healthStatus = null;
|
||||
defaultQueryResponse.data.issues.nodes[0].weight = 5;
|
||||
defaultQueryResponse.data.issues.nodes[0].status = {
|
||||
color: '#DD2B0E',
|
||||
iconName: 'status-cancelled',
|
||||
id: 'gid://gitlab/WorkItems::Statuses::SystemDefined::Status/4',
|
||||
name: "Won't do",
|
||||
position: 0,
|
||||
__typename: 'WorkItemStatus',
|
||||
};
|
||||
}
|
||||
|
||||
const findDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
|
|
|
|||
Loading…
Reference in New Issue