Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-06-24 03:09:02 +00:00
parent 9438e2d741
commit 140dc6e06d
36 changed files with 1669 additions and 1001 deletions

View File

@ -1 +1 @@
707429494a96d985d94e0b10da1c1c5519fa5fb6
3de4744135aca47419474d244258a5e285dad834

View File

@ -1 +1 @@
31259f5394382f015730d7bbfeb775623d69145f
7e5564cdc5834808c2d668ffd5788146defaf881

View File

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

View File

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

View File

@ -0,0 +1,10 @@
query aiCatalogAgents {
aiCatalogAgents @client {
nodes {
id
name
description
model
}
}
}

View File

@ -1,12 +0,0 @@
query userWorkflows {
currentUser @client {
id
workflows {
nodes {
id
type
name
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -1 +1,2 @@
export const AI_CATALOG_INDEX_ROUTE = 'ai-catalog';
export const AI_CATALOG_AGENTS_ROUTE = '/agents';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
b6a20c3d430adab4ffc8f216accb11b23a56fa6e9f0a95e320beaf0ca8caa6f5

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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