Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/Geo Replicate a new Git repository type.md31
-rw-r--r--.gitlab/issue_templates/Geo Replicate a new blob type.md31
-rw-r--r--.nvmrc2
-rw-r--r--CHANGELOG.md6
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/batch_comments/components/submit_dropdown.vue2
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json1
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue75
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue72
-rw-r--r--app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue7
-rw-r--r--app/assets/javascripts/usage_quotas/storage/constants.js53
-rw-r--r--app/assets/javascripts/usage_quotas/storage/init_project_storage.js2
-rw-r--r--app/assets/javascripts/usage_quotas/storage/utils.js52
-rw-r--r--app/components/projects/ml/models_index_component.html.haml1
-rw-r--r--app/components/projects/ml/models_index_component.rb29
-rw-r--r--app/graphql/mutations/work_items/create.rb8
-rw-r--r--app/models/ml/model.rb2
-rw-r--r--app/presenters/ml/model_presenter.rb17
-rw-r--r--app/presenters/ml/models_index_presenter.rb29
-rw-r--r--app/views/projects/ml/models/index.html.haml3
-rw-r--r--config/feature_flags/development/raise_error_for_missing_audit_event_yml.yml8
-rw-r--r--doc/administration/dedicated/index.md7
-rw-r--r--doc/administration/geo/replication/datatypes.md4
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md16
-rw-r--r--doc/api/graphql/reference/index.md25
-rw-r--r--doc/ci/jobs/ci_job_token.md4
-rw-r--r--doc/ci/yaml/includes.md54
-rw-r--r--doc/development/internal_analytics/snowplow/troubleshooting.md4
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/topics/gitlab_flow.md562
-rw-r--r--doc/user/ai_features.md15
-rw-r--r--doc/user/project/merge_requests/index.md4
-rw-r--r--lib/gitlab/audit/auditor.rb13
-rw-r--r--lib/gitlab/ci/interpolation/block.rb47
-rw-r--r--lib/gitlab/ci/interpolation/functions/base.rb50
-rw-r--r--lib/gitlab/ci/interpolation/functions/truncate.rb38
-rw-r--r--lib/gitlab/ci/interpolation/functions_stack.rb69
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rwxr-xr-xscripts/generate-e2e-pipeline6
-rw-r--r--spec/components/projects/ml/models_index_component_spec.rb41
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js170
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js73
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js90
-rw-r--r--spec/frontend/usage_quotas/storage/components/usage_graph_spec.js12
-rw-r--r--spec/frontend/usage_quotas/storage/mock_data.js89
-rw-r--r--spec/frontend/usage_quotas/storage/utils_spec.js67
-rw-r--r--spec/lib/gitlab/audit/auditor_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/interpolation/block_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions/base_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb39
-rw-r--r--spec/presenters/ml/model_presenter_spec.rb43
-rw-r--r--spec/presenters/ml/models_index_presenter_spec.rb33
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb10
-rw-r--r--spec/requests/projects/ml/models_controller_spec.rb7
-rw-r--r--yarn.lock8
59 files changed, 1026 insertions, 1202 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2fc78dd28f8..7184bdc1bbc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -133,7 +133,7 @@ workflow:
variables:
PG_VERSION: "14"
- DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-18.16-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
+ DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-18.17-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
# We set $GITLAB_DEPENDENCY_PROXY to another variable (since it's set at the group level and has higher precedence than .gitlab-ci.yml)
# so that we can override $GITLAB_DEPENDENCY_PROXY_ADDRESS in workflow rules.
GITLAB_DEPENDENCY_PROXY_ADDRESS: "${GITLAB_DEPENDENCY_PROXY}"
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 8061f7e2cd0..0e579f87702 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -3,7 +3,7 @@
- .default-retry
- .default-before_script
- .assets-compile-cache
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-18.16:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-18.17:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36
variables:
SETUP_DB: "false"
WEBPACK_VENDOR_DLL: "true"
diff --git a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
index b77247954de..a8f1c847f18 100644
--- a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md
@@ -2,19 +2,22 @@
This template is based on a model named `CoolWidget`.
-To adapt this template, find and replace the following tokens:
-
-- `CoolWidget`
-- `Cool Widget`
-- `cool_widget`
-- `coolWidget`
-
-If your Model's pluralized form is non-standard, i.e. it doesn't just end in `s`, then find and replace the following tokens *first*:
-
-- `CoolWidgets`
-- `Cool Widgets`
-- `cool_widgets`
-- `coolWidgets`
+To adapt this template, find and replace the following:
+
+Template placeholders
+
+- name: Cool Widgets
+ description: the human-readable name of the model (plural)
+- name: Cool Widget
+ description: the human-readable name of the model (singular)
+- name: cool_widgets
+ description: the snake-cased name of the model (plural)
+- name: cool_widget
+ description: the snake-cased name of the model (singular)
+- name: CoolWidget
+ description: the ActiveRecord class name of the model
+- name: coolWidget
+ description: the camel-cased name of the model
-->
@@ -188,7 +191,7 @@ The Geo primary site needs to checksum every replicable so secondaries can verif
```yaml
---
table_name: cool_widget_states
- description: Separate table for cool widget verification states
+ description: Separate table for Cool Widget verification states
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXXXX
milestone: 'XX.Y'
feature_categories:
diff --git a/.gitlab/issue_templates/Geo Replicate a new blob type.md b/.gitlab/issue_templates/Geo Replicate a new blob type.md
index 94c93bd27e4..3d88e2e7f73 100644
--- a/.gitlab/issue_templates/Geo Replicate a new blob type.md
+++ b/.gitlab/issue_templates/Geo Replicate a new blob type.md
@@ -2,19 +2,22 @@
This template is based on a model named `CoolWidget`.
-To adapt this template, find and replace the following tokens:
-
-- `CoolWidget`
-- `Cool Widget`
-- `cool_widget`
-- `coolWidget`
-
-If your Model's pluralized form is non-standard, i.e. it doesn't just end in `s`, find and replace the following tokens *first*:
-
-- `CoolWidgets`
-- `Cool Widgets`
-- `cool_widgets`
-- `coolWidgets`
+To adapt this template, find and replace the following:
+
+Template placeholders
+
+- name: Cool Widgets
+ description: the human-readable name of the model (plural)
+- name: Cool Widget
+ description: the human-readable name of the model (singular)
+- name: cool_widgets
+ description: the snake-cased name of the model (plural)
+- name: cool_widget
+ description: the snake-cased name of the model (singular)
+- name: CoolWidget
+ description: the ActiveRecord class name of the model
+- name: coolWidget
+ description: the camel-cased name of the model
-->
@@ -191,7 +194,7 @@ The Geo primary site needs to checksum every replicable so secondaries can verif
```yaml
---
table_name: cool_widget_states
- description: Separate table for cool widget verification states
+ description: Separate table for Cool Widget verification states
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXXXX
milestone: 'XX.Y'
feature_categories:
diff --git a/.nvmrc b/.nvmrc
index 8d2a45160e5..39d00c05179 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-18.16.1 \ No newline at end of file
+18.17.0 \ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3636bb8a6d..9fe373753b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2541,6 +2541,12 @@ entry.
- [Add index to group_group_links table](gitlab-org/gitlab@9a3f2c1a90b54074e61d0abf07101ce664198e81) ([merge request](gitlab-org/gitlab!117386))
- [Validate the projects.creator_id foregin key synchronously](gitlab-org/gitlab@ed9351984a16f20506babf6eab6706b917904ed1) ([merge request](gitlab-org/gitlab!117147))
+## 15.11.13 (2023-07-27)
+
+### Fixed (1 change)
+
+- [Disable IAT verification by default](gitlab-org/gitlab@a294195e5b2a9580d0c2c1dc4069cff2856e84bb) ([merge request](gitlab-org/gitlab!127520))
+
## 15.11.12 (2023-07-14)
No changes.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index f21e9b1b461..d5633826fe9 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b3ea2f8bb802758d667f642b1228d31990559343
+7342cf5a8c8c31ba70a4a622a6c1d671d4fc8287
diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
index dd88d51eecf..839fc03bd51 100644
--- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue
@@ -72,7 +72,7 @@ export default {
// whenever a item in the autocomplete dropdown is clicked
const originalClickOutHandler = this.$refs.submitDropdown.$refs.dropdown.clickOutHandler;
this.$refs.submitDropdown.$refs.dropdown.clickOutHandler = (e) => {
- if (!e.composedPath().includes(this.$el)) {
+ if (!e.target.closest('.atwho-container')) {
originalClickOutHandler(e);
}
};
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index d60a83bc156..bb0671b0636 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -99,6 +99,7 @@
"DependencyProxyBlobRegistry",
"DependencyProxyManifestRegistry",
"DesignManagementRepositoryRegistry",
+ "GroupWikiRepositoryRegistry",
"JobArtifactRegistry",
"LfsObjectRegistry",
"MergeRequestDiffRegistry",
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
index 94bc15fa0d0..c2b7309002e 100644
--- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_app.vue
@@ -2,6 +2,7 @@
import { GlAlert, GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { updateRepositorySize } from '~/api/projects_api';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
import {
ERROR_MESSAGE,
LEARN_MORE_LABEL,
@@ -11,10 +12,13 @@ import {
TOTAL_USAGE_DEFAULT_TEXT,
HELP_LINK_ARIA_LABEL,
RECALCULATE_REPOSITORY_LABEL,
- projectContainerRegistryPopoverContent,
+ PROJECT_STORAGE_TYPES,
+ NAMESPACE_STORAGE_TYPES,
+ usageQuotasHelpPaths,
+ storageTypeHelpPaths,
} from '../constants';
import getProjectStorageStatistics from '../queries/project_storage.query.graphql';
-import { parseGetProjectStorageResults } from '../utils';
+import { getStorageTypesFromProjectStatistics, descendingStorageUsageSort } from '../utils';
import UsageGraph from './usage_graph.vue';
import ProjectStorageDetail from './project_storage_detail.vue';
@@ -28,10 +32,7 @@ export default {
UsageGraph,
ProjectStorageDetail,
},
- inject: ['projectPath', 'helpLinks'],
- provide: {
- containerRegistryPopoverContent: projectContainerRegistryPopoverContent,
- },
+ inject: ['projectPath'],
apollo: {
project: {
query: getProjectStorageStatistics,
@@ -40,9 +41,6 @@ export default {
fullPath: this.projectPath,
};
},
- update(data) {
- return parseGetProjectStorageResults(data, this.helpLinks);
- },
error() {
this.error = ERROR_MESSAGE;
},
@@ -56,11 +54,39 @@ export default {
};
},
computed: {
+ isStatisticsEmpty() {
+ return this.project?.statistics == null;
+ },
totalUsage() {
- return this.project?.storage?.totalUsage || TOTAL_USAGE_DEFAULT_TEXT;
+ if (!this.isStatisticsEmpty) {
+ return numberToHumanSize(this.project?.statistics?.storageSize, 1);
+ }
+
+ return TOTAL_USAGE_DEFAULT_TEXT;
},
- storageTypes() {
- return this.project?.storage?.storageTypes || [];
+ projectStorageTypes() {
+ if (this.isStatisticsEmpty) {
+ return [];
+ }
+
+ return getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ this.project?.statistics,
+ this.project?.statisticsDetailsPaths,
+ storageTypeHelpPaths,
+ ).sort(descendingStorageUsageSort('value'));
+ },
+ namespaceStorageTypes() {
+ if (this.isStatisticsEmpty) {
+ return [];
+ }
+
+ return getStorageTypesFromProjectStatistics(
+ NAMESPACE_STORAGE_TYPES,
+ this.project?.statistics,
+ this.project?.statisticsDetailsPaths,
+ storageTypeHelpPaths,
+ );
},
},
methods: {
@@ -83,6 +109,7 @@ export default {
alertEl?.classList.remove('gl-display-none');
},
},
+ usageQuotasHelpPaths,
LEARN_MORE_LABEL,
USAGE_QUOTAS_LABEL,
TOTAL_USAGE_TITLE,
@@ -99,17 +126,16 @@ export default {
<div class="gl-pt-5 gl-px-3">
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
<div>
- <p class="gl-m-0 gl-font-lg gl-font-weight-bold">{{ $options.TOTAL_USAGE_TITLE }}</p>
+ <h2 class="gl-m-0 gl-font-lg gl-font-weight-bold">{{ $options.TOTAL_USAGE_TITLE }}</h2>
<p class="gl-m-0 gl-text-gray-400">
{{ $options.TOTAL_USAGE_SUBTITLE }}
<gl-link
- :href="helpLinks.usageQuotas"
+ :href="$options.usageQuotasHelpPaths.usageQuotas"
target="_blank"
:aria-label="helpLinkAriaLabel($options.USAGE_QUOTAS_LABEL)"
data-testid="usage-quotas-help-link"
+ >{{ $options.LEARN_MORE_LABEL }}</gl-link
>
- {{ $options.LEARN_MORE_LABEL }}
- </gl-link>
</p>
</div>
<p class="gl-m-0 gl-font-size-h-display gl-font-weight-bold" data-testid="total-usage">
@@ -117,7 +143,7 @@ export default {
</p>
</div>
</div>
- <div v-if="project.statistics" class="gl-w-full">
+ <div v-if="!isStatisticsEmpty" class="gl-w-full">
<usage-graph :root-storage-statistics="project.statistics" :limit="0" />
</div>
<div class="gl-w-full gl-my-5">
@@ -129,6 +155,19 @@ export default {
{{ $options.RECALCULATE_REPOSITORY_LABEL }}
</gl-button>
</div>
- <project-storage-detail :storage-types="storageTypes" />
+ <project-storage-detail
+ :storage-types="projectStorageTypes"
+ data-testid="usage-quotas-project-usage-details"
+ />
+ <div>
+ <h2 class="gl-mb-2 gl-mt-5 gl-font-lg gl-font-weight-bold">
+ {{ s__('UsageQuota|Namespace entities') }}
+ </h2>
+
+ <project-storage-detail
+ :storage-types="namespaceStorageTypes"
+ data-testid="usage-quotas-namespace-usage-details"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue
index ce487beca07..6cc1f63e04f 100644
--- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue
+++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue
@@ -7,10 +7,7 @@ import {
HELP_LINK_ARIA_LABEL,
PROJECT_TABLE_LABEL_STORAGE_TYPE,
PROJECT_TABLE_LABEL_USAGE,
- containerRegistryId,
- containerRegistryPopoverId,
} from '../constants';
-import { descendingStorageUsageSort } from '../utils';
import StorageTypeIcon from './storage_type_icon.vue';
export default {
@@ -23,33 +20,12 @@ export default {
StorageTypeIcon,
GlPopover,
},
- inject: ['containerRegistryPopoverContent'],
props: {
storageTypes: {
type: Array,
required: true,
},
},
- computed: {
- sizeSortedStorageTypes() {
- const warnings = {
- [containerRegistryId]: {
- popoverId: containerRegistryPopoverId,
- popoverContent: this.containerRegistryPopoverContent,
- },
- };
-
- return this.storageTypes
- .map((type) => {
- const warning = warnings[type.storageType.id] || null;
- return {
- warning,
- ...type,
- };
- })
- .sort(descendingStorageUsageSort('value'));
- },
- },
methods: {
helpLinkAriaLabel(linkTitle) {
return sprintf(HELP_LINK_ARIA_LABEL, {
@@ -73,42 +49,39 @@ export default {
};
</script>
<template>
- <gl-table-lite :items="sizeSortedStorageTypes" :fields="$options.projectTableFields">
+ <gl-table-lite :items="storageTypes" :fields="$options.projectTableFields">
<template #cell(storageType)="{ item }">
<div class="gl-display-flex gl-flex-direction-row">
- <storage-type-icon
- :name="item.storageType.id"
- :data-testid="`${item.storageType.id}-icon`"
- />
+ <storage-type-icon :name="item.id" :data-testid="`${item.id}-icon`" />
<div>
- <p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.storageType.id}-name`">
+ <p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.id}-name`">
<gl-link
- v-if="item.storageType.detailsPath && item.value"
- :data-testid="`${item.storageType.id}-details-link`"
- :href="item.storageType.detailsPath"
- >{{ item.storageType.name }}</gl-link
+ v-if="item.detailsPath && item.value"
+ :data-testid="`${item.id}-details-link`"
+ :href="item.detailsPath"
+ >{{ item.name }}</gl-link
>
<template v-else>
- {{ item.storageType.name }}
+ {{ item.name }}
</template>
<gl-link
- v-if="item.storageType.helpPath"
- :href="item.storageType.helpPath"
+ v-if="item.helpPath"
+ :href="item.helpPath"
target="_blank"
- :aria-label="helpLinkAriaLabel(item.storageType.name)"
- :data-testid="`${item.storageType.id}-help-link`"
+ :aria-label="helpLinkAriaLabel(item.name)"
+ :data-testid="`${item.id}-help-link`"
>
<gl-icon name="question-o" :size="12" />
</gl-link>
</p>
- <p class="gl-mb-0" :data-testid="`${item.storageType.id}-description`">
- {{ item.storageType.description }}
+ <p class="gl-mb-0" :data-testid="`${item.id}-description`">
+ {{ item.description }}
</p>
- <p v-if="item.storageType.warningMessage" class="gl-mb-0 gl-font-sm">
+ <p v-if="item.warningMessage" class="gl-mb-0 gl-font-sm">
<gl-icon name="warning" :size="12" />
- <gl-sprintf :message="item.storageType.warningMessage">
+ <gl-sprintf :message="item.warningMessage">
<template #warningLink="{ content }">
- <gl-link :href="item.storageType.warningLink" target="_blank" class="gl-font-sm">{{
+ <gl-link :href="item.warningLink" target="_blank" class="gl-font-sm">{{
content
}}</gl-link>
</template>
@@ -119,20 +92,23 @@ export default {
</template>
<template #cell(value)="{ item }">
- {{ numberToHumanSize(item.value, 1) }}
+ <span :data-testid="item.id + '-value'">
+ {{ numberToHumanSize(item.value, 1) }}
+ </span>
<template v-if="item.warning">
<gl-icon
- :id="item.warning.popoverId"
+ :id="item.id + '-warning-icon'"
name="warning"
class="gl-mt-2 gl-lg-mt-0 gl-lg-ml-2"
+ :data-testid="item.id + '-warning-icon'"
/>
<gl-popover
triggers="hover focus"
placement="top"
- :target="item.warning.popoverId"
+ :target="item.id + '-warning-icon'"
:content="item.warning.popoverContent"
- :data-testid="item.warning.popoverId"
+ :data-testid="item.id + '-popover'"
/>
</template>
</template>
diff --git a/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue b/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue
index c1e513d3a00..33f202e69db 100644
--- a/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue
+++ b/app/assets/javascripts/usage_quotas/storage/components/usage_graph.vue
@@ -18,7 +18,6 @@ export default {
computed: {
storageTypes() {
const {
- containerRegistrySize,
buildArtifactsSize,
lfsObjectsSize,
packagesSize,
@@ -52,12 +51,6 @@ export default {
size: packagesSize,
},
{
- id: 'containerRegistry',
- style: this.usageStyle(this.barRatio(containerRegistrySize)),
- class: 'gl-bg-data-viz-aqua-800',
- size: containerRegistrySize,
- },
- {
id: 'buildArtifacts',
style: this.usageStyle(this.barRatio(buildArtifactsSize)),
class: 'gl-bg-data-viz-green-500',
diff --git a/app/assets/javascripts/usage_quotas/storage/constants.js b/app/assets/javascripts/usage_quotas/storage/constants.js
index af93a1889ff..3fdf61a5947 100644
--- a/app/assets/javascripts/usage_quotas/storage/constants.js
+++ b/app/assets/javascripts/usage_quotas/storage/constants.js
@@ -14,25 +14,34 @@ export const TOTAL_USAGE_DEFAULT_TEXT = __('Not applicable.');
export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link');
export const RECALCULATE_REPOSITORY_LABEL = s__('UsageQuota|Recalculate repository usage');
-export const projectContainerRegistryPopoverContent = s__(
- 'UsageQuotas|The project-level storage statistics for the Container Registry are directional only and do not include savings for instance-wide deduplication.',
-);
-
export const containerRegistryId = 'containerRegistrySize';
export const containerRegistryPopoverId = 'container-registry-popover';
+export const containerRegistryPopover = {
+ content: s__(
+ 'UsageQuotas|Container Registry storage statistics are not used to calculate the total project storage. Total project storage is calculated after namespace container deduplication, where the total of all unique containers is added to the namespace storage total.',
+ ),
+ docsLink: helpPagePath(
+ 'user/packages/container_registry/reduce_container_registry_storage.html',
+ { anchor: 'check-container-registry-storage-use' },
+ ),
+};
+
export const PROJECT_TABLE_LABEL_STORAGE_TYPE = s__('UsageQuota|Storage type');
export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
+export const usageQuotasHelpPaths = {
+ usageQuotas: helpPagePath('user/usage_quotas'),
+ usageQuotasProjectStorageLimit: helpPagePath('user/usage_quotas', {
+ anchor: 'project-storage-limit',
+ }),
+ usageQuotasNamespaceStorageLimit: helpPagePath('user/usage_quotas', {
+ anchor: 'namespace-storage-limit',
+ }),
+};
+
export const PROJECT_STORAGE_TYPES = [
{
- id: 'containerRegistry',
- name: __('Container Registry'),
- description: s__(
- 'UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.',
- ),
- },
- {
id: 'buildArtifacts',
name: __('Job artifacts'),
description: s__('UsageQuota|Job artifacts created by CI/CD.'),
@@ -64,14 +73,20 @@ export const PROJECT_STORAGE_TYPES = [
},
];
-export const projectHelpPaths = {
- usageQuotas: helpPagePath('user/usage_quotas'),
- usageQuotasProjectStorageLimit: helpPagePath('user/usage_quotas', {
- anchor: 'project-storage-limit',
- }),
- usageQuotasNamespaceStorageLimit: helpPagePath('user/usage_quotas', {
- anchor: 'namespace-storage-limit',
- }),
+export const NAMESPACE_STORAGE_TYPES = [
+ {
+ id: 'containerRegistry',
+ name: __('Container Registry'),
+ description: s__(
+ `UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.`,
+ ),
+ warning: {
+ popoverContent: containerRegistryPopover.content,
+ },
+ },
+];
+
+export const storageTypeHelpPaths = {
lfsObjects: helpPagePath('/user/project/repository/reducing_the_repo_size_using_git', {
anchor: 'repository-cleanup',
}),
diff --git a/app/assets/javascripts/usage_quotas/storage/init_project_storage.js b/app/assets/javascripts/usage_quotas/storage/init_project_storage.js
index 00cb274902d..e7378fcde0e 100644
--- a/app/assets/javascripts/usage_quotas/storage/init_project_storage.js
+++ b/app/assets/javascripts/usage_quotas/storage/init_project_storage.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import { projectHelpPaths as helpLinks } from './constants';
import ProjectStorageApp from './components/project_storage_app.vue';
Vue.use(VueApollo);
@@ -25,7 +24,6 @@ export default (containerId = 'js-project-storage-count-app') => {
name: 'ProjectStorageApp',
provide: {
projectPath,
- helpLinks,
},
render(createElement) {
return createElement(ProjectStorageApp);
diff --git a/app/assets/javascripts/usage_quotas/storage/utils.js b/app/assets/javascripts/usage_quotas/storage/utils.js
index 0460cd0a9b2..445c3efc9e6 100644
--- a/app/assets/javascripts/usage_quotas/storage/utils.js
+++ b/app/assets/javascripts/usage_quotas/storage/utils.js
@@ -1,54 +1,32 @@
-import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { PROJECT_STORAGE_TYPES } from './constants';
-
+/**
+ * Populates an array of storage types with usage value and other details
+ *
+ * @param {Array} selectedStorageTypes selected storage types that will be populated
+ * @param {Object} projectStatistics object of storage values, with storage type as keys
+ * @param {Object} statisticsDetailsPaths object of storage detail paths, with storage type as keys
+ * @param {Object} helpLinks object of help paths, with storage type as keys
+ * @returns {Array}
+ */
export const getStorageTypesFromProjectStatistics = (
+ selectedStorageTypes,
projectStatistics,
- helpLinks = {},
statisticsDetailsPaths = {},
+ helpLinks = {},
) =>
- PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
+ selectedStorageTypes.reduce((types, currentType) => {
const helpPath = helpLinks[currentType.id];
const value = projectStatistics[`${currentType.id}Size`];
const detailsPath = statisticsDetailsPaths[currentType.id];
return types.concat({
- storageType: {
- ...currentType,
- helpPath,
- detailsPath,
- },
+ ...currentType,
+ helpPath,
+ detailsPath,
value,
});
}, []);
/**
- * This method parses the results from `getProjectStorageStatistics` call.
- *
- * @param {Object} data graphql result
- * @returns {Object}
- */
-export const parseGetProjectStorageResults = (data, helpLinks) => {
- const projectStatistics = data?.project?.statistics;
- if (!projectStatistics) {
- return {};
- }
- const { storageSize } = projectStatistics;
- const storageTypes = getStorageTypesFromProjectStatistics(
- projectStatistics,
- helpLinks,
- data?.project?.statisticsDetailsPaths,
- );
-
- return {
- storage: {
- totalUsage: numberToHumanSize(storageSize, 1),
- storageTypes,
- },
- statistics: projectStatistics,
- };
-};
-
-/**
* Creates a sorting function to sort storage types by usage in the graph and in the table
*
* @param {string} storageUsageKey key storing value of storage usage
diff --git a/app/components/projects/ml/models_index_component.html.haml b/app/components/projects/ml/models_index_component.html.haml
new file mode 100644
index 00000000000..17a92c211d5
--- /dev/null
+++ b/app/components/projects/ml/models_index_component.html.haml
@@ -0,0 +1 @@
+#js-index-ml-models{ data: { view_model: view_model } }
diff --git a/app/components/projects/ml/models_index_component.rb b/app/components/projects/ml/models_index_component.rb
new file mode 100644
index 00000000000..c5c20565195
--- /dev/null
+++ b/app/components/projects/ml/models_index_component.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Projects
+ module Ml
+ class ModelsIndexComponent < ViewComponent::Base
+ attr_reader :models
+
+ def initialize(models:)
+ @models = models
+ end
+
+ private
+
+ def view_model
+ Gitlab::Json.generate({ models: models_view_model })
+ end
+
+ def models_view_model
+ models.map(&:present).map do |m|
+ {
+ name: m.name,
+ version: m.latest_version_name,
+ path: m.latest_package_path
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
index 9f7b7b5db97..7ce508e5ef1 100644
--- a/app/graphql/mutations/work_items/create.rb
+++ b/app/graphql/mutations/work_items/create.rb
@@ -14,6 +14,7 @@ module Mutations
authorize :create_work_item
MUTUALLY_EXCLUSIVE_ARGUMENTS_ERROR = 'Please provide either projectPath or namespacePath argument, but not both.'
+ DISABLED_FF_ERROR = 'namespace_level_work_items feature flag is disabled. Only project paths allowed.'
argument :confidential, GraphQL::Types::Boolean,
required: false,
@@ -59,6 +60,7 @@ module Mutations
def resolve(project_path: nil, namespace_path: nil, **attributes)
container_path = project_path || namespace_path
container = authorized_find!(container_path)
+ check_feature_available!(container)
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
type = ::WorkItems::Type.find(attributes[:work_item_type_id])
@@ -81,6 +83,12 @@ module Mutations
private
+ def check_feature_available!(container)
+ return unless container.is_a?(::Group) && Feature.disabled?(:namespace_level_work_items, container)
+
+ raise Gitlab::Graphql::Errors::ArgumentError, DISABLED_FF_ERROR
+ end
+
def global_id_compatibility_params(params)
params[:work_item_type_id] = params[:work_item_type_id]&.model_id
diff --git a/app/models/ml/model.rb b/app/models/ml/model.rb
index cc71b15af52..fb15b9fea72 100644
--- a/app/models/ml/model.rb
+++ b/app/models/ml/model.rb
@@ -2,6 +2,8 @@
module Ml
class Model < ApplicationRecord
+ include Presentable
+
validates :project, :default_experiment, presence: true
validates :name,
format: Gitlab::Regex.ml_model_name_regex,
diff --git a/app/presenters/ml/model_presenter.rb b/app/presenters/ml/model_presenter.rb
new file mode 100644
index 00000000000..1317a13351b
--- /dev/null
+++ b/app/presenters/ml/model_presenter.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Ml
+ class ModelPresenter < Gitlab::View::Presenter::Delegated
+ presents ::Ml::Model, as: :model
+
+ def latest_version_name
+ model.latest_version&.version
+ end
+
+ def latest_package_path
+ return unless model.latest_version&.package_id.present?
+
+ Gitlab::Routing.url_helpers.project_package_path(model.project, model.latest_version.package_id)
+ end
+ end
+end
diff --git a/app/presenters/ml/models_index_presenter.rb b/app/presenters/ml/models_index_presenter.rb
deleted file mode 100644
index c61f87fc4af..00000000000
--- a/app/presenters/ml/models_index_presenter.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Ml
- class ModelsIndexPresenter
- def initialize(models)
- @models = models
- end
-
- def present
- data = @models.map do |m|
- {
- name: m.name,
- version: m.latest_version&.version,
- path: package_path(m)
- }
- end
-
- Gitlab::Json.generate({ models: data })
- end
-
- private
-
- def package_path(model)
- return unless model.latest_version&.package.present?
-
- Gitlab::Routing.url_helpers.project_package_path(model.project, model.latest_version.package_id)
- end
- end
-end
diff --git a/app/views/projects/ml/models/index.html.haml b/app/views/projects/ml/models/index.html.haml
index 2caba2ae9be..a1c6376e9b4 100644
--- a/app/views/projects/ml/models/index.html.haml
+++ b/app/views/projects/ml/models/index.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title s_('ModelRegistry|Model registry')
- page_title s_('ModelRegistry|Model registry')
-- presenter = ::Ml::ModelsIndexPresenter.new(@models)
-#js-index-ml-models{ data: { view_model: presenter.present } }
+= render(Projects::Ml::ModelsIndexComponent.new(models: @models))
diff --git a/config/feature_flags/development/raise_error_for_missing_audit_event_yml.yml b/config/feature_flags/development/raise_error_for_missing_audit_event_yml.yml
new file mode 100644
index 00000000000..14835496d39
--- /dev/null
+++ b/config/feature_flags/development/raise_error_for_missing_audit_event_yml.yml
@@ -0,0 +1,8 @@
+---
+name: raise_error_for_missing_audit_event_yml
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127193
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419377
+milestone: '16.3'
+type: development
+group: group::compliance
+default_enabled: false
diff --git a/doc/administration/dedicated/index.md b/doc/administration/dedicated/index.md
index 870a342ef9a..c99e0593f05 100644
--- a/doc/administration/dedicated/index.md
+++ b/doc/administration/dedicated/index.md
@@ -37,9 +37,10 @@ To request the creation of a new GitLab Dedicated environment for your organizat
When onboarding, you must also specify your preference for the weekly four-hour time slot that GitLab uses to perform maintenance and upgrade operations on the tenant instance.
-- APAC (outside working hours): Wednesday 1pm-5pm UTC
-- EU (outside working hours): Tuesday 1am-5am UTC
-- AMER (outside working hours): Tuesday 7am-11am UTC
+- APAC (outside working hours): Wednesday 1 AM - 5 PM UTC
+- EU (outside working hours): Tuesday 1 AM - 5 AM UTC
+- AMER Option 1 (outside working hours): Tuesday 7 AM - 11 AM UTC
+- AMER Option 2 (outside working hours): Sunday 9 PM - Monday 1 AM UTC
NOTE:
Some downtime may be incurred during this window. This downtime is not counting towards [the system SLA](https://about.gitlab.com/handbook/engineering/infrastructure/team/gitlab-dedicated/slas/).
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 1acb8d88907..05f9ec264f7 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -34,7 +34,7 @@ verification methods:
| Git | Project designs repository | Geo with Gitaly | Gitaly Checksum |
| Git | Project Snippets | Geo with Gitaly | Gitaly Checksum |
| Git | Personal Snippets | Geo with Gitaly | Gitaly Checksum |
-| Git | Group wiki repository | Geo with Gitaly | _Not implemented_ |
+| Git | Group wiki repository | Geo with Gitaly | Gitaly Checksum |
| Blobs | User uploads _(file system)_ | Geo with API | SHA256 checksum |
| Blobs | User uploads _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
| Blobs | LFS objects _(file system)_ | Geo with API | SHA256 checksum |
@@ -191,7 +191,7 @@ successfully, you must replicate their data using some other means.
|[Application data in PostgreSQL](../../postgresql/index.md) | **Yes** (10.2) | **Yes** (10.2) | N/A | N/A | |
|[Project repository](../../../user/project/repository/index.md) | **Yes** (10.2) | **Yes** (10.7) | N/A | N/A | Migrated to [self-service framework](../../../development/geo/framework.md) in 16.2. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_repository_replication`, enabled by default in (16.3). |
|[Project wiki repository](../../../user/project/wiki/index.md) | **Yes** (10.2)<sup>2</sup> | **Yes** (10.7)<sup>2</sup> | N/A | N/A | Migrated to [self-service framework](../../../development/geo/framework.md) in 15.11. See GitLab issue [#367925](https://gitlab.com/gitlab-org/gitlab/-/issues/367925) for more details.<br /><br />Behind feature flag `geo_project_wiki_repository_replication`, enabled by default in (15.11). |
-|[Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No | N/A | N/A | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
+|[Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | [**Yes** (16.3)](https://gitlab.com/gitlab-org/gitlab/-/issues/323897) | N/A | N/A | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
|[Uploads](../../uploads.md) | **Yes** (10.2) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | Replication is behind the feature flag `geo_upload_replication`, enabled by default. Verification was behind the feature flag `geo_upload_verification`, removed in 14.8. |
|[LFS objects](../../lfs/index.md) | **Yes** (10.2) | **Yes** (14.6) | [**Yes** (15.1)](https://gitlab.com/groups/gitlab-org/-/epics/5551) | [No](object_storage.md#verification-of-files-in-object-storage) | GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification was behind the feature flag `geo_lfs_object_verification`, removed in 14.7. |
|[Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | N/A | N/A | |
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index be7c5539eab..dc68caae673 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -296,12 +296,16 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_snippet_repositories_synced` | Gauge | 13.4 | Number of syncable snippets synced on secondary | `url` |
| `geo_snippet_repositories_failed` | Gauge | 13.4 | Number of syncable snippets failed on secondary | `url` |
| `geo_snippet_repositories_registry` | Gauge | 13.4 | Number of syncable snippets in the registry | `url` |
-| `geo_group_wiki_repositories` | Gauge | 13.10 | Number of group wikis on primary | `url` |
-| `geo_group_wiki_repositories_checksummed` | Gauge | 13.10 | Number of group wikis checksummed on primary | `url` |
-| `geo_group_wiki_repositories_checksum_failed` | Gauge | 13.10 | Number of group wikis failed to calculate the checksum on primary | `url` |
-| `geo_group_wiki_repositories_synced` | Gauge | 13.10 | Number of syncable group wikis synced on secondary | `url` |
-| `geo_group_wiki_repositories_failed` | Gauge | 13.10 | Number of syncable group wikis failed on secondary | `url` |
-| `geo_group_wiki_repositories_registry` | Gauge | 13.10 | Number of syncable group wikis in the registry | `url` |
+| `geo_group_wiki_repositories` | Gauge | 13.10 | Number of group wikis on primary | `url` |
+| `geo_group_wiki_repositories_checksum_total` | Gauge | 16.3 | Number of group wikis to checksum on primary | `url` |
+| `geo_group_wiki_repositories_checksummed` | Gauge | 13.10 | Number of group wikis that successfully calculated the checksum on primary | `url` |
+| `geo_group_wiki_repositories_checksum_failed` | Gauge | 13.10 | Number of group wikis that failed to calculate the checksum on primary | `url` |
+| `geo_group_wiki_repositories_synced` | Gauge | 13.10 | Number of syncable group wikis synced on secondary | `url` |
+| `geo_group_wiki_repositories_failed` | Gauge | 13.10 | Number of syncable group wikis failed to sync on secondary | `url` |
+| `geo_group_wiki_repositories_registry` | Gauge | 13.10 | Number of group wikis in the registry | `url` |
+| `geo_group_wiki_repositories_verification_total` | Gauge | 16.3 | Number of group wikis to attempt to verify on secondary | `url` |
+| `geo_group_wiki_repositories_verified` | Gauge | 16.3 | Number of group wikis successfully verified on secondary | `url` |
+| `geo_group_wiki_repositories_verification_failed` | Gauge | 16.3 | Number of group wikis that failed verification on secondary | `url` |
| `geo_pages_deployments` | Gauge | 14.3 | Number of pages deployments on primary | `url` |
| `geo_pages_deployments_checksum_total` | Gauge | 14.6 | Number of pages deployments to checksum on primary | `url` |
| `geo_pages_deployments_checksummed` | Gauge | 14.6 | Number of pages deployments that successfully calculated the checksum on primary | `url` |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4ba3a57723e..62b8907e99f 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6888,6 +6888,29 @@ Input type: `UserAddOnAssignmentCreateInput`
| <a id="mutationuseraddonassignmentcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationuseraddonassignmentcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+### `Mutation.userAddOnAssignmentRemove`
+
+WARNING:
+**Introduced** in 16.3.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Input type: `UserAddOnAssignmentRemoveInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationuseraddonassignmentremoveaddonpurchaseid"></a>`addOnPurchaseId` | [`GitlabSubscriptionsAddOnPurchaseID!`](#gitlabsubscriptionsaddonpurchaseid) | Global ID of AddOnPurchase assignment belongs to. |
+| <a id="mutationuseraddonassignmentremoveclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationuseraddonassignmentremoveuserid"></a>`userId` | [`UserID!`](#userid) | Global ID of user whose assignment will be removed. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationuseraddonassignmentremoveclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationuseraddonassignmentremoveerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.userCalloutCreate`
Input type: `UserCalloutCreateInput`
@@ -26301,6 +26324,7 @@ Geo registry class.
| <a id="georegistryclassdependency_proxy_blob_registry"></a>`DEPENDENCY_PROXY_BLOB_REGISTRY` | Geo::DependencyProxyBlobRegistry registry class. |
| <a id="georegistryclassdependency_proxy_manifest_registry"></a>`DEPENDENCY_PROXY_MANIFEST_REGISTRY` | Geo::DependencyProxyManifestRegistry registry class. |
| <a id="georegistryclassdesign_management_repository_registry"></a>`DESIGN_MANAGEMENT_REPOSITORY_REGISTRY` | Geo::DesignManagementRepositoryRegistry registry class. |
+| <a id="georegistryclassgroup_wiki_repository_registry"></a>`GROUP_WIKI_REPOSITORY_REGISTRY` | Geo::GroupWikiRepositoryRegistry registry class. |
| <a id="georegistryclassjob_artifact_registry"></a>`JOB_ARTIFACT_REGISTRY` | Geo::JobArtifactRegistry registry class. |
| <a id="georegistryclasslfs_object_registry"></a>`LFS_OBJECT_REGISTRY` | Geo::LfsObjectRegistry registry class. |
| <a id="georegistryclassmerge_request_diff_registry"></a>`MERGE_REQUEST_DIFF_REGISTRY` | Geo::MergeRequestDiffRegistry registry class. |
@@ -28604,6 +28628,7 @@ One of:
- [`DependencyProxyBlobRegistry`](#dependencyproxyblobregistry)
- [`DependencyProxyManifestRegistry`](#dependencyproxymanifestregistry)
- [`DesignManagementRepositoryRegistry`](#designmanagementrepositoryregistry)
+- [`GroupWikiRepositoryRegistry`](#groupwikirepositoryregistry)
- [`JobArtifactRegistry`](#jobartifactregistry)
- [`LfsObjectRegistry`](#lfsobjectregistry)
- [`MergeRequestDiffRegistry`](#mergerequestdiffregistry)
diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md
index c2fe3071b52..f0a5f45a88b 100644
--- a/doc/ci/jobs/ci_job_token.md
+++ b/doc/ci/jobs/ci_job_token.md
@@ -134,8 +134,8 @@ can make API calls from running pipelines by using the CI/CD job token.
Prerequisite:
-- You must have at least the Maintainer role in the current project and at least
- the Guest role in the allowed project.
+- You must have at least the Maintainer role in the current project. If the allowed project
+ is internal or private, you must have at least the Guest role in that project.
- You must not have more than 100 projects added to the allowlist.
To add a project:
diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md
index 3b6419dbff2..bf326e0203b 100644
--- a/doc/ci/yaml/includes.md
+++ b/doc/ci/yaml/includes.md
@@ -589,6 +589,60 @@ In this example:
- `user` is optional. If not defined, the value is `test-user`.
- `flags` is optional. If not defined, it has no value.
+### Specify functions to manipulate input values
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/409462) in GitLab 16.3.
+
+You can specify predefined functions in the interpolation block to manipulate the input value.
+The format supported is the following:
+
+```yaml
+$[[ input.input-id | <function1> | <function2> | ... <functionN> ]]
+```
+
+Details:
+
+- Only [predefined interpolation functions](#predefined-interpolation-functions) are permitted.
+- A maximum of 3 functions may be specified in a single interpolation block.
+- The functions are executed in the sequence they are specified.
+
+```yaml
+spec:
+ inputs:
+ test:
+ default: '0123456789'
+---
+
+test-job:
+ script: echo $[[ inputs.test | truncate(1,3) ]]
+```
+
+In this example:
+
+- The function [`truncate`](#truncate) applies to the value of `inputs.test`.
+- Assuming the value of `inputs.test` is `0123456789`, then the output of `script` would be `echo 123`.
+
+### Predefined interpolation functions
+
+#### Truncate
+
+Use `truncate` to shorten the interpolated value. For example:
+
+- `truncate(<offset>,<length>)`
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `offset` | Integer | Number of characters to offset by. |
+| `length` | Integer | Number of characters to return after the offset. |
+
+Example:
+
+```yaml
+$[[ inputs.test | truncate(3,5) ]]
+```
+
+Assuming the value of `inputs.test` is `0123456789`, then the output would be `34567`.
+
### Set input parameter values with `include:inputs`
> `include:with` [renamed to `include:inputs`](https://gitlab.com/gitlab-org/gitlab/-/issues/406780) in GitLab 16.0.
diff --git a/doc/development/internal_analytics/snowplow/troubleshooting.md b/doc/development/internal_analytics/snowplow/troubleshooting.md
index c085ceb0f56..b531c6dcd56 100644
--- a/doc/development/internal_analytics/snowplow/troubleshooting.md
+++ b/doc/development/internal_analytics/snowplow/troubleshooting.md
@@ -21,8 +21,8 @@ You will be alarmed via a [Sisense alert](https://app.periscopedata.com/app/gitl
### Locating the problem
First you need to identify at which stage in Snowplow the data pipeline the drop is occurring.
-Start at [Snowplow dashboard](https://console.aws.amazon.com/systems-manager/resource-groups/cloudwatch?dashboard=SnowPlow&region=us-east-1#) on CloudWatch,
-if you do not have access to CloudWatch you need to create an [access request issue](https://gitlab.com/gitlab-com/team-member-epics/access-requests/-/issues/9730) first.
+Start at [Snowplow dashboard](https://console.aws.amazon.com/systems-manager/resource-groups/cloudwatch?dashboard=SnowPlow&region=us-east-1#) on CloudWatch.
+If you do not have access to CloudWatch, GitLab team members can create an access request issue, similar to this one: `https://gitlab.com/gitlab-com/team-member-epics/access-requests/-/issues/9730`.
While on CloudWatch dashboard set time range to last 4 weeks, to get better picture of system characteristics over time. Than visit following charts:
1. `ELB New Flow Count` and `Collector Auto Scaling Group Network In/Out` - they show in order: number of connections to collectors via load balancers and data volume (in bytes) processed by collectors. If there is drop visible there, it means less events were fired from the GitLab application. Proceed to [application layer guide](#troubleshooting-gitlab-application-layer) for more details
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 45c6b398a76..107a31f62b0 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -49,7 +49,7 @@ If the highest number stable branch is unclear, check the [GitLab blog](https://
| [RubyGems](#3-rubygems) | `3.4.x` | A specific RubyGems version is not fully needed, but it's recommended to update so you can enjoy some known performance improvements. |
| [Go](#4-go) | `1.19.x` | From GitLab 16.1, Go 1.19 or later is required. |
| [Git](#git) | `2.38.x` | From GitLab 15.8, Git 2.38.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). |
-| [Node.js](#5-node) | `18.16.x` | From GitLab 16.1, Node.js 18.16 or later is required. |
+| [Node.js](#5-node) | `18.17.x` | From GitLab 16.3, Node.js 18.17 or later is required. |
## GitLab directory structure
@@ -260,7 +260,7 @@ GitLab requires the use of Node to compile JavaScript
assets, and Yarn to manage JavaScript dependencies. The current minimum
requirements for these are:
-- `node` 18.x releases (v18.16.1 or later).
+- `node` 18.x releases (v18.17.0 or later).
[Other LTS versions of Node.js](https://github.com/nodejs/release#release-schedule) might be able to build assets, but we only guarantee Node.js 18.x.
- `yarn` = v1.22.x (Yarn 2 is not supported yet)
diff --git a/doc/topics/gitlab_flow.md b/doc/topics/gitlab_flow.md
index 7dd35419c76..e9feb75dd5c 100644
--- a/doc/topics/gitlab_flow.md
+++ b/doc/topics/gitlab_flow.md
@@ -1,559 +1,11 @@
---
-stage: Create
-group: Source Code
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: 'https://about.gitlab.com/blog/2023/07/27/gitlab-flow-duo/'
+remove_date: '2023-10-27'
---
-# Introduction to Git workflows **(FREE)**
+This document was moved to [another location](https://about.gitlab.com/blog/2023/07/27/gitlab-flow-duo/).
-With Git, you can use a variety of branching strategies and workflows.
-Having a structured workflow for collaboration in complex projects is
-crucial for several reasons:
-
-- **Code organization**: Keep the codebase organized, prevent
- overlapping work, and ensure focused efforts towards a common goal.
-
-- **Version control**: Allow simultaneous work on different features
- without conflicts, maintaining code stability.
-
-- **Code quality**: A code review and approval process helps maintain high
- code quality and adherence to coding standards.
-
-- **Traceability and accountability**: Enable tracking of changes and their authors,
- simplifying issue identification and responsibility assignment.
-
-- **Easier onboarding**: Help new team members quickly grasp the
- development process, and start contributing effectively.
-
-- **Time and resource management**: Enable better planning, resource
- allocation, and meeting deadlines, ensuring an efficient development
- process.
-
-- **CI/CD**: Incorporate automated testing and deployment
- processes, streamlining the release cycle and delivering high-quality
- software consistently.
-
-A structured workflow promotes organization, efficiency, and code
-quality, leading to a more successful and streamlined development process.
-
-If the default workflow is not specifically defined, many organizations
-end up with workflows that are:
-
-- Too complicated.
-- Not clearly defined.
-- Not integrated with their issue tracking systems.
-
-Your organization can use GitLab with any workflow you choose.
-
-## Workflow types
-
-Here are some of the most common Git workflows.
-
-### Centralized workflow
-
-Best suited for small teams transitioning from a centralized version
-control system like SVN. All team members work on a single branch,
-usually `main`, and push their changes directly to the central
-repository.
-
-### Feature branch workflow
-
-Developers create separate branches for each feature or bugfix,
-keeping the 'main' branch stable. When a feature is complete, the
-developer submits a merge request to integrate the
-changes back into `main` after a code review.
-
-### Forking workflow
-
-Commonly used in open-source projects, this workflow allows external
-contributors to work without direct access to the main repository.
-Developers create a fork (personal copy) of the main repository and
-make changes in it. They then submit a merge request to have those changes
-integrated into the main repository.
-
-### Git flow workflow
-
-This workflow is best for projects with a structured release cycle.
-It introduces two long-lived branches: `main` for production-ready
-code and `develop` for integrating features. Additional branches like
-`feature`, `release`, and `hotfix` are used for specific purposes,
-ensuring a strict and organized development process.
-
-### GitLab/GitHub flow
-
-A simplified workflow primarily used for web development and
-continuous deployment. It combines aspects of the Feature branch
-workflow and the Git flow workflow. Developers create feature branches
-from `main`, and after the changes are complete, they are merged back
-into the `main` branch, which is then immediately deployed.
-
-Each of these Git workflows has its advantages and is suited to
-different project types and team structures. Below the most popular
-workflows are reviewed in more details.
-
-## Git workflow
-
-Most version control systems have only one step: committing from the working copy to a shared server.
-
-When you convert to Git, you have to get used to the fact that it takes three steps to share a commit with colleagues.
-
-In Git, you add files from the working copy to the staging area. After that, you commit them to your local repository.
-The third step is pushing to a shared remote repository.
-
-```mermaid
-graph LR
- subgraph Git workflow
- A[Working copy] --> |git add| B[Index]
- B --> |git commit| C[Local repository]
- C --> |git push| D[Remote repository]
- end
-```
-
-After getting used to these three steps, the next challenge is the branching model.
-
-Because many organizations new to Git have no conventions for how to work with it, their repositories can quickly become messy.
-The biggest problem is that many long-running branches emerge that all contain part of the changes.
-People have a hard time figuring out which branch has the latest code, or which branch to deploy to production.
-Frequently, the reaction to this problem is to adopt a standardized pattern such as [Git flow](https://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](https://scottchacon.com/2011/08/31/github-flow.html).
-
-We think there is still room for improvement, and so we've proposed a set of practices called the GitLab Flow.
-
-For a video introduction of this workflow in GitLab, see [GitLab Flow](https://youtu.be/InKNIvky2KE).
-
-## Problems with the Git flow
-
-Git flow was one of the first proposals to use Git branches, and it has received
-a lot of attention. It suggests a `main` branch and a separate `develop` branch,
-with supporting branches for features, releases, and hotfixes. The development
-happens on the `develop` branch, moves to a release branch, and is finally merged
-into the `main` branch.
-
-Git flow is a well-defined standard, but its complexity introduces two problems.
-The first problem is that developers must use the `develop` branch and not `main`. `main` is reserved for code that is released to production.
-It is a convention to call your default branch `main` and to mostly branch from and merge to this.
-Because most tools automatically use the `main` branch as the default, it is annoying to have to switch to another branch.
-
-The second problem of Git flow is the complexity introduced by the hotfix and release branches.
-These branches can be a good idea for some organizations but are overkill for the vast majority of them.
-Nowadays, most organizations practice continuous delivery, which means that your default branch can be deployed.
-Continuous delivery removes the need for hotfix and release branches, including all the ceremony they introduce.
-An example of this ceremony is the merging back of release branches.
-Though specialized tools do exist to solve this, they require documentation and add complexity.
-Frequently, developers make mistakes such as merging changes only into `main` and not into the `develop` branch.
-The reason for these errors is that Git flow is too complicated for most use cases.
-For example, many projects do releases but don't need to do hotfixes.
-
-<!-- vale gitlab.Spelling = NO -->
-
-![Git Flow timeline by Vincent Driessen, used with permission](img/gitlab_flow_gitdashflow.png)
-
-<!-- vale gitlab.Spelling = YES -->
-
-## GitHub flow as a simpler alternative
-
-In reaction to Git flow, GitHub created a simpler alternative.
-[GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) has only feature branches and a `main` branch:
-
-```mermaid
-graph TD
- subgraph Feature branches in GitHub Flow
- A[main branch] ===>B[main branch]
- D[nav branch] --> |add navigation| B
- B ===> C[main branch]
- E[feature-branch] --> |add feature| C
- C ==> F[main branch]
- end
-```
-
-This flow is clean and straightforward, and many organizations have adopted it with great success.
-Another way to integrate change from one branch to another is [rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing).
-Merging everything into the `main` branch and frequently deploying means you minimize the amount of unreleased code. This approach is in line with lean and continuous delivery best practices.
-However, this flow still leaves a lot of questions unanswered regarding deployments, environments, releases, and integrations with issues.
-
-## Introduction to GitLab Flow **(FREE)**
-
-However, if you are looking for guidance on best practices, you can use
-the GitLab Flow. This workflow combines [feature-driven development](https://en.wikipedia.org/wiki/Feature-driven_development)
-and [feature branches](https://martinfowler.com/bliki/FeatureBranch.html) with issue tracking.
-
-While this workflow used at GitLab, you can choose whichever workflow
-suits your organization best.
-
-With GitLab Flow, we offer additional guidance for these questions.
-
-## Production branch with GitLab Flow
-
-GitHub flow assumes you can deploy to production every time you merge a feature branch.
-While this is possible in some cases, such as SaaS applications, there are some cases where this is not possible, such as:
-
-- You don't control the timing of a release. For example, an iOS application that
- is released when it passes App Store validation.
-- You have deployment windows - for example, workdays from 10 AM to 4 PM when the
- operations team is at full capacity - but you also merge code at other times.
-
-In these cases, you can create a production branch that reflects the deployed code.
-You can deploy a new version by merging `main` into the `production` branch.
-While not shown in the graph below, the work on the `main` branch works just like in GitHub flow:
-with feature branches being merged into `main`.
-
-```mermaid
-graph TD
- subgraph Production branch in GitLab Flow
- A[main branch] ==>B[development]
- B ==> C[main branch]
- C ==> D[main branch]
-
- E[production] ====> F[production]
- C --> |deployment| F
- D ==> G[main branch]
- F ==> H[main branch]
- end
-```
-
-If you need to know what code is in production, you can check out the production branch to see.
-The approximate time of deployment is visible as the merge commit in the version control system.
-This time is pretty accurate if you automatically deploy your production branch.
-If you need a more exact time, you can have your deployment script create a tag on each deployment.
-This flow prevents the overhead of releasing, tagging, and merging that happens with Git flow.
-
-## Environment branches with GitLab Flow
-
-It might be a good idea to have an environment that is automatically updated to the `staging` branch.
-Only, in this case, the name of this environment might differ from the branch name.
-Suppose you have a staging environment, a pre-production environment, and a production environment:
-
-```mermaid
-graph LR
- subgraph Environment branches in GitLab Flow
-
- A[staging] ==> B[staging]
- B ==> C[staging]
- C ==> D[staging]
-
- A --> |deploy to<br>pre-prod| G
-
- F[pre-prod] ==> G[pre-prod]
- G ==> H[pre-prod]
- H ==> I[pre-prod]
-
- C --> |deploy to<br>pre-prod| I
-
- J[production] ==> K[production]
- K ==> L[production]
-
- G --> |production <br>deployment| K
-
- end
-```
-
-In this case, deploy the `staging` branch to your staging environment.
-To deploy to pre-production, create a merge request from the `staging` branch to the `pre-prod` branch.
-Go live by merging the `pre-prod` branch into the `production` branch.
-This workflow, where commits only flow downstream, ensures that everything is tested in all environments.
-To cherry-pick a commit with a hotfix, develop it on a feature branch and merge it into `production` with a merge request.
-In this case, do not delete the feature branch yet.
-If `production` passes automatic testing, you then merge the feature branch into the other branches.
-If this is not possible because more manual testing is required, you can send merge requests from the feature branch to the downstream branches.
-
-## Release branches with GitLab Flow
-
-You should work with release branches only if you need to release software to
-the outside world. In this case, each branch contains a minor version, such as
-`2.3-stable` or `2.4-stable`:
-
-```mermaid
-graph LR
- A:::main ===> B((main))
- B:::main ==> C((main))
- C:::main ==> D((main))
- D:::main ==> E((main))
-
- A((main)) ----> F((2.3-stable)):::first
- F --> G((2.3-stable)):::first
- C -.-> |cherry-pick| G
- D --> H((2.4-stable)):::second
-
- classDef main fill:#f4f0ff,stroke:#7b58cf
- classDef first fill:#e9f3fc,stroke:#1f75cb
- classDef second fill:#ecf4ee,stroke:#108548
-```
-
-Create stable branches using `main` as a starting point, and branch as late as possible.
-By doing this, you minimize the length of time during which you have to apply bug fixes to multiple branches.
-After announcing a release branch, only add serious bug fixes to the branch.
-If possible, first merge these bug fixes into `main`, and then cherry-pick them into the release branch.
-If you initially merged into the release branch and then forgot to cherry-pick to `main`, you'd encounter the same bug in subsequent releases.
-Merging into `main` and then cherry-picking into release is called an "upstream first" policy, which is also practiced by [Google](https://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first/) and [Red Hat](https://www.redhat.com/en/blog/a-community-for-using-openstack-with-red-hat-rdo).
-Every time you include a bug fix in a release branch, increase the patch version (to comply with [Semantic Versioning](https://semver.org/)) by setting a new tag.
-Some projects also have a stable branch that points to the same commit as the latest released branch.
-In this flow, it is not common to have a production branch (or Git flow `main` branch).
-
-## Merge/pull requests with GitLab Flow
-
-![Merge request with inline comments](img/gitlab_flow_mr_inline_comments.png)
-
-Merge or pull requests are created in a Git management application. They ask an assigned person to merge two branches.
-Tools such as GitHub and Bitbucket choose the name "pull request", because the first manual action is to pull the feature branch.
-Tools such as GitLab and others choose the name "merge request", because the final action is to merge the feature branch.
-This article refers to them as merge requests.
-
-If you work on a feature branch for more than a few hours, share the intermediate result with the rest of your team.
-To do this, create a merge request without assigning it to anyone.
-Instead, mention people in the description or a comment, for example, "/cc @mark @susan."
-This indicates that the merge request is not ready to be merged yet, but feedback is welcome.
-Your team members can comment on the merge request in general or on specific lines with line comments.
-The merge request serves as a code review tool, and no separate code review tools should be needed.
-If the review reveals shortcomings, anyone can commit and push a fix.
-Usually, the person to do this is the creator of the merge request.
-The diff in the merge request automatically updates when new commits are pushed to the branch.
-In GitLab Flow, you can configure your pipeline to run every time you commit changes to a branch. This type of pipeline is called a branch pipeline. Alternatively, you can configure your pipeline to run every time you make changes to the source branch for a merge request. This type of pipeline is called a [merge request pipeline](../ci/pipelines/merge_request_pipelines.md). In GitLab Flow, you can also take advantage of our [Review Apps](../ci/review_apps/index.md) capability, which are a collaboration tool that provide an environment to showcase product changes. Review Apps provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests allowing stakeholders to see your changes without needing to check out your branch and run your changes in a sandbox environment. When your changes are merged, Review Apps clean up the dynamic environment and related resources preventing environment sprawl.
-
-When you are ready to merge your feature branch, assign the merge request to a maintainer for the project.
-Also, mention any other people from whom you would like feedback.
-After the assigned person feels comfortable with the result, they can merge the branch.
-In GitLab Flow, a [merged results pipeline](../ci/pipelines/merged_results_pipelines.md) runs against the results of the source and target branches merged together.
-If the assigned person does not feel comfortable, they can request more changes or close the merge request without merging.
-
-NOTE:
-In your pipelines, you can include any automated CI tests for unit, security vulnerabilities, code quality, performance, dependency, etc. Information related to the pipeline runs as well as results of tests are all displayed as widgets in the merge request window, so that you can access and visualize all these from a central location.
-
-In GitLab, it is common to protect the long-lived branches, such as the `main` branch, so [most developers can't modify them](../user/permissions.md).
-So, if you want to merge into a protected branch, assign your merge request to someone with the
-Maintainer role.
-
-After you merge a feature branch, you should remove it from the source control software.
-In GitLab, you can do this when merging.
-Removing finished branches ensures that the list of branches shows only work in progress.
-It also ensures that if someone reopens the issue, they can use the same branch name without causing problems.
-
-NOTE:
-When you reopen an issue you need to create a new merge request.
-
-![Remove checkbox for branch in merge requests](img/gitlab_flow_remove_checkbox.png)
-
-## Issue tracking with GitLab Flow
-
-GitLab Flow is a way to make the relation between the code and the issue tracker more transparent.
-
-Any significant change to the code should start with an issue that describes the goal.
-Having a reason for every code change helps to inform the rest of the team and to keep the scope of a feature branch small.
-In GitLab, each change to the codebase starts with an issue in the issue tracking system.
-If there is no issue yet, create the issue if the change requires more than an hour's work.
-In many organizations, raising an issue is part of the development process because they are used in sprint planning.
-The issue title should describe the desired state of the system.
-For example, the issue title `As an administrator, I want to remove users without receiving an error`
-is better than "Administrators can't remove users."
-
-When you are ready to code, create a branch for the issue from the `main` branch.
-This branch is the place for any work related to this change.
-
-NOTE:
-The name of a branch might be dictated by organizational standards.
-
-When you are done or want to discuss the code, open a merge request.
-A merge request is an online place to discuss the change and review the code.
-
-If you open the merge request but do not assign it to anyone, it is a [draft merge request](../user/project/merge_requests/drafts.md).
-Drafts are used to discuss the proposed implementation but are not ready for inclusion in the `main` branch yet.
-Start the title of the merge request with `[Draft]`, `Draft:` or `(Draft)` to prevent it from being merged before it's ready.
-
-When you think the code is ready, assign the merge request to a reviewer.
-The reviewer can merge the changes when they think the code is ready for inclusion in the `main` branch.
-When they press the merge button, GitLab merges the code and creates a merge commit that makes this event visible later on.
-Merge requests always create a merge commit, even when the branch could be merged without one.
-This merge strategy is called "no fast-forward" in Git.
-After the merge, delete the feature branch, because it is no longer needed.
-In GitLab, this deletion is an option when merging.
-
-Suppose that a branch is merged but a problem occurs and the issue is reopened.
-In this case, it is no problem to reuse the same branch name, because the first branch was deleted when it was merged.
-At any time, there is at most one branch for every issue.
-It is possible that one feature branch solves more than one issue.
-
-In GitLab Flow, you can create a merge request from the issue itself. When you do it this way, a feature branch and its related merge request are automatically created and associated to each other and the merge request is automatically related to the issue. In addition, when the merge request is merged the issue is automatically closed for you.
-
-## Linking and closing issues from merge requests
-
-Link to issues by mentioning them in commit messages or the description of a merge request, for example, "Fixes #16" or "Duck typing is preferred. See #12."
-GitLab then creates links to the mentioned issues and creates comments in the issues linking back to the merge request.
-
-To automatically close linked issues, mention them with the words "fixes" or "closes," for example, "fixes #14" or "closes #67." GitLab closes these issues when the code is merged into the default branch.
-
-Like mentioned in the previous section, in GitLab Flow, you can create a merge request from the issue itself. When you do it this way, a feature branch and its related merge request are automatically created and associated to each other and the merge request is automatically related to the issue. In addition, when the merge request is merged the issue is automatically closed for you.
-
-If you have an issue that spans across multiple repositories, create an issue for each repository and link all issues to a parent issue.
-
-## Squashing commits with rebase
-
-With Git, you can use an interactive rebase (`rebase -i`) to squash multiple commits into one or reorder them.
-This feature helps you replace a couple of small commits with a single commit, or if you want to make the order more logical:
-
-```shell
-pick c6ee4d3 add a new file to the repo
-pick c3c130b change readme
-
-# Rebase 168afa0..c3c130b onto 168afa0
-#
-# Commands:
-# p, pick = use commit
-# r, reword = use commit, but edit the commit message
-# e, edit = use commit, but stop for amending
-# s, squash = use commit, but meld into previous commit
-# f, fixup = like "squash", but discard this commit's log message
-# x, exec = run command (the rest of the line) using shell
-#
-# These lines can be re-ordered; they are executed from top to bottom.
-#
-# If you remove a line here THAT COMMIT WILL BE LOST.
-#
-# However, if you remove everything, the rebase will be aborted.
-#
-# Note that empty commits are commented out
-~
-~
-~
-"~/demo/gitlab-ce/.git/rebase-merge/git-rebase-todo" 20L, 673C
-```
-
-However, you should avoid rebasing commits you have pushed to a remote server if you have other active contributors in the same branch.
-Because rebasing creates new commits for all your changes, it can cause confusion because the same change would have multiple identifiers.
-It would cause merge errors for anyone working on the same branch because their history would not match with yours. It can be really troublesome for the author or other contributors.
-Also, if someone has already reviewed your code, rebasing makes it hard to tell what changed after the last review.
-
-You should never rebase commits authored by other people unless you've agreed otherwise.
-Not only does this rewrite history, but it also loses authorship information.
-Rebasing prevents the other authors from being attributed and sharing part of the [`git blame`](https://git-scm.com/docs/git-blame).
-
-If a merge involves many commits, it may seem more difficult to undo.
-You might consider solving this by squashing all the changes into one commit just before merging by using the GitLab [Squash-and-Merge](../user/project/merge_requests/squash_and_merge.md) feature.
-Fortunately, you can undo a merge with all its commits.
-The way to do this is by reverting the merge commit.
-Preserving this ability to revert a merge is a good reason to always use the "no fast-forward" (`--no-ff`) strategy when you merge manually.
-
-NOTE:
-If you revert a merge commit and then change your mind, revert the revert commit to redo the merge.
-Git does not allow you to merge the code again otherwise.
-
-## Reducing merge commits in feature branches
-
-Having lots of merge commits can make your repository history messy.
-Therefore, you should try to avoid merge commits in feature branches.
-Often, people avoid merge commits by just using rebase to reorder their commits after the commits on the `main` branch.
-Using rebase prevents a merge commit when merging `main` into your feature branch, and it creates a neat linear history.
-However, as discussed in [the section about rebasing](#squashing-commits-with-rebase), you should avoid rebasing commits in a feature branch that you're sharing with others.
-
-Rebasing could create more work, as every time you rebase, you may need to resolve the same conflicts.
-Sometimes you can reuse recorded resolutions (`rerere`), but merging is better, because you only have to resolve conflicts once.
-The Git documentation has a thorough explanation of the [tradeoffs between merging and rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing#:~:text=Final%20commit%20history-,The,-Perils%20of%20Rebasing).
-
-A good way to prevent creating many merge commits is to not frequently merge `main` into the feature branch.
-Three reasons to merge in `main`:
-
-1. Utilizing new code.
-1. Resolving merge conflicts.
-1. Updating long-running branches.
-
-To use some code that was introduced in `main` after you created the feature branch, cherry-pick a commit.
-
-If your feature branch has a merge conflict, creating a merge commit is a standard way of solving this.
-
-NOTE:
-Sometimes you can use `.gitattributes` to reduce merge conflicts.
-For example, you can set your changelog file to use the [union merge driver](https://git-scm.com/docs/gitattributes#gitattributes-union) so that multiple new entries don't conflict with each other.
-
-The last reason for creating merge commits is to keep long-running feature branches up-to-date with the latest state of the project.
-The solution here is to keep your feature branches short-lived.
-Most feature branches should take less than one day of work.
-If your feature branches often take more than a day of work, try to split your features into smaller units of work.
-
-If you need to keep a feature branch open for more than a day, there are a few strategies to keep it up-to-date.
-One option is to use continuous integration (CI) to merge in `main` at the start of the day.
-Another option is to only merge in from well-defined points in time, for example, a tagged release.
-You could also use [feature toggles](https://martinfowler.com/bliki/FeatureToggle.html) or [feature flags](../operations/feature_flags.md) to hide incomplete features so you can still merge back into `main` every day.
-
-NOTE:
-Don't confuse automatic branch testing with continuous integration.
-Martin Fowler makes this distinction in [an article about feature branches](https://martinfowler.com/bliki/FeatureBranch.html):
-"\[People\] say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
-That's continuous building, and a Good Thing, but there's no *integration*, so it's not CI."
-
-In conclusion, you should try to prevent merge commits, but not eliminate them.
-Your codebase should be clean, but your history should represent what actually happened.
-Developing software happens in small, messy steps, and it is OK to have your history reflect this.
-You can use tools to view the network graphs of commits and understand the messy history that created your code.
-If you rebase code, the commit history changes. Because of changed commit identifiers, tools can't restore the commit history.
-
-## Commit often and push frequently
-
-Another way to make your development work easier is to commit often.
-Every time you have a working set of tests and code, you should make a commit.
-Splitting up work into individual commits provides context for developers looking at your code later.
-Smaller commits make it clear how a feature was developed. They help you roll back to a specific good point in time, or to revert one code change without reverting several unrelated changes.
-
-Committing often also helps you share your work, which is important so that everyone is aware of what you are working on.
-You should push your feature branch frequently, even when it is not yet ready for review.
-By sharing your work in a feature branch or [a merge request](#mergepull-requests-with-gitlab-flow), you prevent your team members from duplicating work.
-Sharing your work before it's complete also allows for discussion and feedback about the changes. This feedback can help improve the code before it gets to review.
-
-## How to write a good commit message
-
-A commit message should reflect your intention, not just the contents of the commit.
-You can see the changes in a commit, so the commit message should explain why you made those changes:
-
-```shell
-# This commit message doesn't give enough information
-git commit -m 'Improve XML generation'
-
-# These commit messages clearly state the intent of the commit
-git commit -m 'Properly escape special characters in XML generation'
-```
-
-An example of a good commit message is: "Combine templates to reduce duplicate code in the user views."
-The words "change," "improve," "fix," and "refactor" don't add much information to a commit message.
-For more information, see Tim Pope's excellent [note about formatting commit messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
-
-To add more context to a commit message, consider adding information regarding the
-origin of the change, such the GitLab issue URL or Jira issue number. That way, you provide
-more information for users who need in-depth context about the change.
-
-For example:
-
-```plaintext
-Properly escape special characters in XML generation.
-
-Issue: gitlab.com/gitlab-org/gitlab/-/issues/1
-```
-
-## Testing before merging
-
-In old workflows, the continuous integration (CI) server commonly ran tests on the `main` branch only.
-Developers had to ensure their code did not break the `main` branch.
-When using GitLab Flow, developers create their branches from this `main` branch, so it is essential that it never breaks.
-Therefore, each merge request must be tested before it is accepted.
-CI software like GitLab CI/CD shows the build results right in the merge request itself to simplify the process.
-
-There is one drawback to testing merge requests: the CI server only tests the feature branch itself, not the merged result.
-Ideally, the server could also test the `main` branch after each change.
-However, retesting on every commit to `main` is computationally expensive and means you are more frequently waiting for test results.
-Because feature branches should be short-lived, testing just the branch is an acceptable risk.
-If new commits in `main` cause merge conflicts with the feature branch, merge `main` back into the branch to make the CI server re-run the tests.
-As said before, if you often have feature branches that last for more than a few days, you should make your issues smaller.
-
-In GitLab Flow, your can include automated CI tests in your branch or merge request pipelines, which can run when you commit changes to a branch.
-
-## Working with feature branches
-
-Some tips for working with feature branches:
-
-- When you create a feature branch locally, always update your local copy of `main` before
- branching off from it.
-- When creating a feature branch, always branch from `main` unless you know your work
- depends on some other branch. For example, to create `feature-x-update`, branch from
- `feature-x` instead of `main`.
-- If you merge in another branch after starting, explain the reason in the merge commit.
-- If you have not pushed your branch upstream yet, you can still pull in new changes
- by rebasing your local feature branch against your local copy of its parent branch.
-- Do not merge recent changes from other branches into your local feature branch if your code
- can work and merge cleanly without those extra changes. Each time you merge commits into your
- feature branch, you add a merge commit to your feature branch. These merge commits
- later end up littering the history in your `main` branch.
+<!-- This redirect file can be deleted after <2023-10-27>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md
index 3dea892509e..dac62a96210 100644
--- a/doc/user/ai_features.md
+++ b/doc/user/ai_features.md
@@ -138,7 +138,7 @@ We cannot guarantee that the large language model produces results that are corr
This feature is an [Experiment](../policy/experiment-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#enable-third-party-ai-features) to be enabled.
-Getting help has never been easier. If you have a question about how the GitLab product works, you can ask product how-to questions and get AI generated support from GitLab Duo Chat.
+Getting help has never been easier. If you have a question about how the GitLab product works, you can get AI generated support from GitLab Duo Chat.
1. In the lower-left corner, select the Help icon.
1. Select **Ask in GitLab Duo Chat**. A drawer opens on the right side of your screen.
@@ -159,8 +159,7 @@ These summaries are automatically generated. They are available on the merge req
Provide feedback on this experimental feature in [issue 408726](https://gitlab.com/gitlab-org/gitlab/-/issues/408726).
-**Data usage**: When you use this quick action, the diff of changes between the head of the source branch
-and the target branch is sent to the large language model referenced above.
+**Data usage**: When using this quick action, the diff of changes between the source branch's head and the target branch is sent to the large language model.
### Summarize my merge request review **(ULTIMATE SAAS)**
@@ -193,7 +192,7 @@ This feature is an [Experiment](../policy/experiment-beta-support.md) on GitLab.
In a merge request, you can get a list of suggested tests for the file you are reviewing. This functionality can help determine if appropriate test coverage has been provided, or if you need more coverage for your project.
-View a [click-through demo](https://go.gitlab.com/Xfp0l4).
+View a [click-through demo](https://go.gitlab.com/Xfp0l4).
To generate a test suggestion:
@@ -208,7 +207,7 @@ Feedback on this experimental feature can be provided in [issue 408995](https://
**Data usage**: When you use this feature, the following data is sent to the large language model referenced above:
- Contents of the file
-- The file name
+- The filename
### Summarize issue discussions **(ULTIMATE SAAS)**
@@ -255,7 +254,7 @@ GitLab AI features leverage generative AI to help increase velocity and aim to h
### Progressive enhancement
-These features are designed as a progressive enhancement to existing GitLab features across our DevSecOps platform. They are designed to fail gracefully and should not prevent the core functionality of the underlying feature. Please note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md).
+These features are designed as a progressive enhancement to existing GitLab features across our DevSecOps platform. They are designed to fail gracefully and should not prevent the core functionality of the underlying feature. You should note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md).
### Stability and performance
@@ -265,7 +264,7 @@ These features are in a variety of [feature support levels](../policy/experiment
### Data privacy
-Some AI features require the use of third-party AI services models and APIs from: Google AI and OpenAI. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use in order to provide these features.
+Some AI features require the use of third-party AI services models and APIs from: Google AI and OpenAI. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use to provide these features.
Group owners can control which top-level groups have access to third-party AI features by using the [group level third-party AI features setting](group/manage.md#enable-third-party-ai-features).
@@ -280,4 +279,4 @@ Generative AI may produce unexpected results that may be:
- Insecure code
- Offensive or insensitive
-GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We will continue improving the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly.
+GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We improve the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index dfa62628e46..76cdd4cfcdd 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -18,10 +18,6 @@ Merge requests include:
- A comment section for discussion threads.
- The list of commits.
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
-For a quick overview of merge requests,
-view [this GitLab Flow video](https://www.youtube.com/watch?v=InKNIvky2KE).
-
## Create a merge request
Learn the various ways to [create a merge request](creating_merge_requests.md).
diff --git a/lib/gitlab/audit/auditor.rb b/lib/gitlab/audit/auditor.rb
index a59237fbb1f..c8dc804c4a1 100644
--- a/lib/gitlab/audit/auditor.rb
+++ b/lib/gitlab/audit/auditor.rb
@@ -76,13 +76,18 @@ module Gitlab
@authentication_event = @context.fetch(:authentication_event, false)
@authentication_provider = @context[:authentication_provider]
- # TODO: Remove this code once we close https://gitlab.com/gitlab-org/gitlab/-/issues/367870
return if @is_audit_event_yaml_defined
- message = 'Logging audit events without an event type definition will be deprecated soon ' \
- '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)'
+ if Feature.enabled?(:raise_error_for_missing_audit_event_yml)
+ raise StandardError, "Audit event type YML file is not defined for #{@name}. Please read " \
+ "https://docs.gitlab.com/ee/development/audit_event_guide/" \
+ "#how-to-instrument-new-audit-events for adding a new audit event"
+ else
+ message = 'Logging audit events without an event type definition will be deprecated soon ' \
+ '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)'
- Gitlab::AppLogger.warn(message: message, event_type: @name)
+ Gitlab::AppLogger.warn(message: message, event_type: @name)
+ end
end
def single_audit
diff --git a/lib/gitlab/ci/interpolation/block.rb b/lib/gitlab/ci/interpolation/block.rb
index 389cbf378a2..d064a520921 100644
--- a/lib/gitlab/ci/interpolation/block.rb
+++ b/lib/gitlab/ci/interpolation/block.rb
@@ -3,36 +3,43 @@
module Gitlab
module Ci
module Interpolation
+ ##
+ # This class represents an interpolation block. The format supported is:
+ # $[[ <access> | <function1> | <function2> | ... <functionN> ]]
+ #
+ # <access> specifies the value to retrieve (e.g. `inputs.key`).
+ # <function> can be optionally provided with or without arguments to
+ # manipulate the access value. Functions are evaluated in the order
+ # they are presented.
class Block
PREFIX = '$[['
- PATTERN = /(?<block>\$\[\[\s*(?<access>.*?)\s*\]\])/.freeze
+ PATTERN = /(?<block>\$\[\[\s*(?<data>.*?)\s*\]\])/.freeze
+ MAX_FUNCTIONS = 3
- attr_reader :block, :data, :ctx
+ attr_reader :block, :data, :ctx, :errors
def initialize(block, data, ctx)
@block = block
- @ctx = ctx
@data = data
+ @ctx = ctx
+ @errors = []
+ @value = nil
- @access = Interpolation::Access.new(@data, ctx)
+ evaluate!
end
def valid?
errors.none?
end
- def errors
- @access.errors
- end
-
def content
- @access.content
+ data
end
def value
raise ArgumentError, 'block invalid' unless valid?
- @access.value
+ @value
end
def self.match(data)
@@ -42,6 +49,26 @@ module Gitlab
yield ::Regexp.last_match(1), ::Regexp.last_match(2)
end
end
+
+ private
+
+ # We expect the block data to be a string with one or more entities delimited by pipes:
+ # <access> | <function1> | <function2> | ... <functionN>
+ def evaluate!
+ data_access, *functions = data.split('|').map(&:strip)
+ access = Interpolation::Access.new(data_access, ctx)
+
+ return @errors.concat(access.errors) unless access.valid?
+ return @errors.push('too many functions in interpolation block') if functions.count > MAX_FUNCTIONS
+
+ result = Interpolation::FunctionsStack.new(functions).evaluate(access.value)
+
+ if result.success?
+ @value = result.value
+ else
+ @errors.concat(result.errors)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/interpolation/functions/base.rb b/lib/gitlab/ci/interpolation/functions/base.rb
new file mode 100644
index 00000000000..ad86b9c3ddb
--- /dev/null
+++ b/lib/gitlab/ci/interpolation/functions/base.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Interpolation
+ module Functions
+ class Base
+ attr_reader :errors
+
+ def self.function_expression_pattern
+ raise NotImplementedError
+ end
+
+ def self.name
+ raise NotImplementedError
+ end
+
+ def self.matches?(function_expression)
+ function_expression_pattern.match?(function_expression)
+ end
+
+ def initialize(function_expression)
+ @errors = []
+ @function_args = parse_args(function_expression)
+ end
+
+ def valid?
+ errors.empty?
+ end
+
+ def execute(_input_value)
+ raise NotImplementedError
+ end
+
+ private
+
+ attr_reader :function_args
+
+ def error(message)
+ errors << "error in `#{self.class.name}` function: #{message}"
+ end
+
+ def parse_args(function_expression)
+ self.class.function_expression_pattern.match(function_expression)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/interpolation/functions/truncate.rb b/lib/gitlab/ci/interpolation/functions/truncate.rb
new file mode 100644
index 00000000000..7700675c3f0
--- /dev/null
+++ b/lib/gitlab/ci/interpolation/functions/truncate.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Interpolation
+ module Functions
+ class Truncate < Base
+ def self.function_expression_pattern
+ /^#{name}\(\s*(?<offset>\d+)\s*,\s*(?<length>\d+)\s*\)?$/
+ end
+
+ def self.name
+ 'truncate'
+ end
+
+ def execute(input_value)
+ if input_value.is_a?(String)
+ input_value[offset, length].to_s
+ else
+ error('invalid input type: truncate can only be used with string inputs')
+ nil
+ end
+ end
+
+ private
+
+ def offset
+ function_args[:offset].to_i
+ end
+
+ def length
+ function_args[:length].to_i
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/interpolation/functions_stack.rb b/lib/gitlab/ci/interpolation/functions_stack.rb
new file mode 100644
index 00000000000..70158e144c1
--- /dev/null
+++ b/lib/gitlab/ci/interpolation/functions_stack.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Interpolation
+ ##
+ # This class matches the given function string with a predefined
+ # function and then applies it to the input value.
+ #
+ class FunctionsStack
+ Output = Struct.new(:value, :errors) do
+ def success?
+ errors.empty?
+ end
+ end
+
+ FUNCTIONS = [
+ Functions::Truncate
+ ].freeze
+
+ attr_reader :errors
+
+ def initialize(function_expressions)
+ @errors = []
+ @functions = build_stack(function_expressions)
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def evaluate(input_value)
+ return Output.new(nil, errors) unless valid?
+
+ functions.reduce(Output.new(input_value, [])) do |output, function|
+ break output unless output.success?
+
+ output_value = function.execute(output.value)
+
+ if function.valid?
+ Output.new(output_value, [])
+ else
+ Output.new(nil, function.errors)
+ end
+ end
+ end
+
+ private
+
+ attr_reader :functions
+
+ def build_stack(function_expressions)
+ function_expressions.map do |function_expression|
+ matching_function = FUNCTIONS.find { |function| function.matches?(function_expression) }
+
+ if matching_function.present?
+ matching_function.new(function_expression)
+ else
+ message = "no function matching `#{function_expression}`: " \
+ 'check that the function name, arguments, and types are correct'
+
+ errors << message
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 834e89c6ee0..2e597b2901b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -49807,9 +49807,6 @@ msgstr ""
msgid "UsageQuotas|Namespace transfer data used"
msgstr ""
-msgid "UsageQuotas|The project-level storage statistics for the Container Registry are directional only and do not include savings for instance-wide deduplication."
-msgstr ""
-
msgid "UsageQuota|%{linkStart}Shared runners%{linkEnd} are disabled, so there are no limits set on pipeline usage"
msgstr ""
@@ -49891,6 +49888,9 @@ msgstr ""
msgid "UsageQuota|Month"
msgstr ""
+msgid "UsageQuota|Namespace entities"
+msgstr ""
+
msgid "UsageQuota|Namespace overview"
msgstr ""
diff --git a/package.json b/package.json
index afb282e0d34..fa6ce046851 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"@gitlab/cluster-client": "^1.2.0",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
- "@gitlab/svgs": "3.57.0",
+ "@gitlab/svgs": "3.58.0",
"@gitlab/ui": "64.20.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230713160749",
diff --git a/scripts/generate-e2e-pipeline b/scripts/generate-e2e-pipeline
index 2b882094dc2..31a3122050a 100755
--- a/scripts/generate-e2e-pipeline
+++ b/scripts/generate-e2e-pipeline
@@ -56,7 +56,11 @@ echo "***Saving generated qa pipeline files***"
for key in "${!qa_pipelines[@]}"; do
echo "Generating $key"
- cp ".gitlab/ci/${qa_pipelines[$key]}" "$key"
+ if [ "${CI_PROJECT_NAMESPACE}" = "gitlab-cn" ]; then
+ cp "jh/.gitlab/ci/${qa_pipelines[$key]}" "$key"
+ else
+ cp ".gitlab/ci/${qa_pipelines[$key]}" "$key"
+ fi
echo >>"$key" # add empty line so it's easier to read if debugging
echo "$variables" >>"$key"
diff --git a/spec/components/projects/ml/models_index_component_spec.rb b/spec/components/projects/ml/models_index_component_spec.rb
new file mode 100644
index 00000000000..e4599cc5eec
--- /dev/null
+++ b/spec/components/projects/ml/models_index_component_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Projects::Ml::ModelsIndexComponent, type: :component, feature_category: :mlops do
+ let_it_be(:project) { build_stubbed(:project) }
+ let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
+ let_it_be(:model2) { build_stubbed(:ml_models, project: project) }
+ let_it_be(:models) { [model1, model2] }
+
+ subject(:component) do
+ described_class.new(models: models)
+ end
+
+ describe 'rendered' do
+ let(:element) { page.find("#js-index-ml-models") }
+
+ before do
+ render_inline component
+ end
+
+ it 'renders element with view_model' do
+ element = page.find("#js-index-ml-models")
+
+ expect(Gitlab::Json.parse(element['data-view-model'])).to eq({
+ 'models' => [
+ {
+ 'name' => model1.name,
+ 'version' => model1.latest_version.version,
+ 'path' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}"
+ },
+ {
+ 'name' => model2.name,
+ 'version' => nil,
+ 'path' => nil
+ }
+ ]
+ })
+ end
+ end
+end
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
index 576263d5418..ca5f80f331c 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
@@ -1,19 +1,19 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { GlButton, GlDrawer, GlModal } from '@gitlab/ui';
+import { GlButton, GlModal } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
-import CiEditorHeader from '~/ci/pipeline_editor/components/editor/ci_editor_header.vue';
import CommitSection from '~/ci/pipeline_editor/components/commit/commit_section.vue';
import PipelineEditorDrawer from '~/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue';
import JobAssistantDrawer from '~/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue';
import PipelineEditorFileNav from '~/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue';
import PipelineEditorFileTree from '~/ci/pipeline_editor/components/file_tree/container.vue';
-import BranchSwitcher from '~/ci/pipeline_editor/components/file_nav/branch_switcher.vue';
import PipelineEditorHeader from '~/ci/pipeline_editor/components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from '~/ci/pipeline_editor/components/pipeline_editor_tabs.vue';
import {
CREATE_TAB,
+ EDITOR_APP_DRAWER_HELP,
+ EDITOR_APP_DRAWER_JOB_ASSISTANT,
+ EDITOR_APP_DRAWER_NONE,
FILE_TREE_DISPLAY_KEY,
VALIDATE_TAB,
MERGED_TAB,
@@ -29,10 +29,9 @@ jest.mock('~/lib/utils/common_utils');
describe('Pipeline editor home wrapper', () => {
let wrapper;
- const createComponent = ({ props = {}, glFeatures = {}, data = {}, stubs = {} } = {}) => {
+ const createComponent = ({ props = {}, glFeatures = {}, stubs = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(PipelineEditorHome, {
- data: () => data,
propsData: {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
@@ -53,7 +52,6 @@ describe('Pipeline editor home wrapper', () => {
);
};
- const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher);
const findCommitSection = () => wrapper.findComponent(CommitSection);
const findFileNav = () => wrapper.findComponent(PipelineEditorFileNav);
const findModal = () => wrapper.findComponent(GlModal);
@@ -63,8 +61,16 @@ describe('Pipeline editor home wrapper', () => {
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle');
- const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
- const findJobAssistantBtn = () => wrapper.findByTestId('job-assistant-drawer-toggle');
+
+ const clickHelpBtn = async () => {
+ await findPipelineEditorDrawer().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_HELP);
+ };
+ const clickJobAssistantBtn = async () => {
+ await findJobAssistantDrawer().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_JOB_ASSISTANT);
+ };
+ const closeDrawer = async (finder) => {
+ await finder().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_NONE);
+ };
afterEach(() => {
localStorage.clear();
@@ -103,11 +109,9 @@ describe('Pipeline editor home wrapper', () => {
});
});
describe('when `showSwitchBranchModal` value is true', () => {
- beforeEach(() => {
- createComponent({
- data: { showSwitchBranchModal: true },
- stubs: { PipelineEditorFileNav },
- });
+ beforeEach(async () => {
+ createComponent();
+ await findFileNav().vm.$emit('select-branch');
});
it('is visible', () => {
@@ -115,11 +119,11 @@ describe('Pipeline editor home wrapper', () => {
});
it('pass down `shouldLoadNewBranch` to the branch switcher when primary is selected', async () => {
- expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(false);
+ expect(findFileNav().props('shouldLoadNewBranch')).toBe(false);
await findModal().vm.$emit('primary');
- expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(true);
+ expect(findFileNav().props('shouldLoadNewBranch')).toBe(true);
});
it('closes the modal when secondary action is selected', async () => {
@@ -148,9 +152,7 @@ describe('Pipeline editor home wrapper', () => {
async ({ tab, shouldShow }) => {
expect(findCommitSection().exists()).toBe(true);
- findPipelineEditorTabs().vm.$emit('set-current-tab', tab);
-
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', tab);
expect(findCommitSection().isVisible()).toBe(shouldShow);
},
@@ -159,12 +161,10 @@ describe('Pipeline editor home wrapper', () => {
it('shows the commit form again when coming back to the create tab', async () => {
expect(findCommitSection().isVisible()).toBe(true);
- findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
expect(findCommitSection().isVisible()).toBe(false);
- findPipelineEditorTabs().vm.$emit('set-current-tab', CREATE_TAB);
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', CREATE_TAB);
expect(findCommitSection().isVisible()).toBe(true);
});
@@ -195,7 +195,9 @@ describe('Pipeline editor home wrapper', () => {
describe('when "walkthrough-popover-cta-clicked" is emitted from pipeline editor tabs', () => {
it('passes down `scrollToCommitForm=true` to commit section', async () => {
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
+
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
+
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
});
});
@@ -204,6 +206,7 @@ describe('Pipeline editor home wrapper', () => {
it('passes down `scrollToCommitForm=false` to commit section', async () => {
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
+
await findCommitSection().vm.$emit('scrolled-to-commit-form');
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
});
@@ -211,133 +214,49 @@ describe('Pipeline editor home wrapper', () => {
});
describe('help drawer', () => {
- const clickHelpBtn = async () => {
- findHelpBtn().vm.$emit('click');
- await nextTick();
- };
-
- it('hides the drawer by default', () => {
+ beforeEach(() => {
createComponent();
+ });
+ it('hides the drawer by default', () => {
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
it('toggles the drawer on button click', async () => {
- createComponent({
- stubs: {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- PipelineEditorDrawer,
- },
- });
-
- await clickHelpBtn();
-
- expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
-
- await clickHelpBtn();
-
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
- });
-
- it("closes the drawer through the drawer's close button", async () => {
- createComponent({
- stubs: {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- PipelineEditorDrawer,
- },
- });
await clickHelpBtn();
-
expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
- findPipelineEditorDrawer().findComponent(GlDrawer).vm.$emit('close');
- await nextTick();
-
+ await closeDrawer(findPipelineEditorDrawer);
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
});
describe('job assistant drawer', () => {
- const clickHelpBtn = async () => {
- findHelpBtn().vm.$emit('click');
- await nextTick();
- };
- const clickJobAssistantBtn = async () => {
- findJobAssistantBtn().vm.$emit('click');
- await nextTick();
- };
-
- const stubs = {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- JobAssistantDrawer,
- };
-
- it('hides the job assistant drawer by default', () => {
+ beforeEach(() => {
createComponent({
glFeatures: {
ciJobAssistantDrawer: true,
},
});
+ });
+ it('hides the job assistant drawer by default', () => {
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
});
it('toggles the job assistant drawer on button click', async () => {
- createComponent({
- stubs,
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
- await clickJobAssistantBtn();
-
- expect(findJobAssistantDrawer().props('isVisible')).toBe(true);
-
- await clickJobAssistantBtn();
-
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
- });
-
- it("closes the job assistant drawer through the drawer's close button", async () => {
- createComponent({
- stubs,
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
await clickJobAssistantBtn();
-
expect(findJobAssistantDrawer().props('isVisible')).toBe(true);
- findJobAssistantDrawer().findComponent(GlDrawer).vm.$emit('close');
- await nextTick();
-
+ await closeDrawer(findJobAssistantDrawer);
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
});
it('covers helper drawer when opened last', async () => {
- createComponent({
- stubs: {
- ...stubs,
- PipelineEditorDrawer,
- },
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
await clickHelpBtn();
await clickJobAssistantBtn();
@@ -348,16 +267,6 @@ describe('Pipeline editor home wrapper', () => {
});
it('covered by helper drawer when opened first', async () => {
- createComponent({
- stubs: {
- ...stubs,
- PipelineEditorDrawer,
- },
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
await clickJobAssistantBtn();
await clickHelpBtn();
@@ -370,8 +279,7 @@ describe('Pipeline editor home wrapper', () => {
describe('file tree', () => {
const toggleFileTree = async () => {
- findFileTreeBtn().vm.$emit('click');
- await nextTick();
+ await findFileTreeBtn().vm.$emit('click');
};
describe('button toggle', () => {
@@ -412,9 +320,7 @@ describe('Pipeline editor home wrapper', () => {
describe('when file tree display state is saved in local storage', () => {
beforeEach(() => {
localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true');
- createComponent({
- stubs: { PipelineEditorFileNav },
- });
+ createComponent();
});
it('shows the file tree by default', () => {
@@ -424,9 +330,7 @@ describe('Pipeline editor home wrapper', () => {
describe('when file tree display state is not saved in local storage', () => {
beforeEach(() => {
- createComponent({
- stubs: { PipelineEditorFileNav },
- });
+ createComponent();
});
it('hides the file tree by default', () => {
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
index 1a200090805..88ab51cf135 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
@@ -1,16 +1,24 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ProjectStorageApp from '~/usage_quotas/storage/components/project_storage_app.vue';
import UsageGraph from '~/usage_quotas/storage/components/usage_graph.vue';
-import { TOTAL_USAGE_DEFAULT_TEXT } from '~/usage_quotas/storage/constants';
+import {
+ descendingStorageUsageSort,
+ getStorageTypesFromProjectStatistics,
+} from '~/usage_quotas/storage/utils';
+import {
+ storageTypeHelpPaths,
+ PROJECT_STORAGE_TYPES,
+ NAMESPACE_STORAGE_TYPES,
+ TOTAL_USAGE_DEFAULT_TEXT,
+} from '~/usage_quotas/storage/constants';
import getProjectStorageStatistics from '~/usage_quotas/storage/queries/project_storage.query.graphql';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
import {
- projectData,
mockGetProjectStorageStatisticsGraphQLResponse,
mockEmptyResponse,
defaultProjectProvideValues,
@@ -36,25 +44,26 @@ describe('ProjectStorageApp', () => {
};
const createComponent = ({ provide = {}, mockApollo } = {}) => {
- wrapper = extendedWrapper(
- shallowMount(ProjectStorageApp, {
- apolloProvider: mockApollo,
- provide: {
- ...defaultProjectProvideValues,
- ...provide,
- },
- }),
- );
+ wrapper = shallowMountExtended(ProjectStorageApp, {
+ apolloProvider: mockApollo,
+ provide: {
+ ...defaultProjectProvideValues,
+ ...provide,
+ },
+ });
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findUsagePercentage = () => wrapper.findByTestId('total-usage');
- const findUsageQuotasHelpLink = () => wrapper.findByTestId('usage-quotas-help-link');
const findUsageGraph = () => wrapper.findComponent(UsageGraph);
+ const findProjectDetailsTable = () => wrapper.findByTestId('usage-quotas-project-usage-details');
+ const findNamespaceDetailsTable = () =>
+ wrapper.findByTestId('usage-quotas-namespace-usage-details');
describe('with apollo fetching successful', () => {
let mockApollo;
+ const mockProjectData = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
beforeEach(async () => {
mockApollo = createMockApolloProvider({
@@ -65,13 +74,33 @@ describe('ProjectStorageApp', () => {
});
it('renders correct total usage', () => {
- expect(findUsagePercentage().text()).toBe(projectData.storage.totalUsage);
+ const expectedValue = numberToHumanSize(
+ mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics.storageSize,
+ 1,
+ );
+ expect(findUsagePercentage().text()).toBe(expectedValue);
+ });
+
+ it('passes project storage entities to project details table', () => {
+ const expectedValue = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ mockProjectData.statistics,
+ mockProjectData.statisticsDetailsPaths,
+ storageTypeHelpPaths,
+ ).sort(descendingStorageUsageSort('value'));
+
+ expect(findProjectDetailsTable().props('storageTypes')).toStrictEqual(expectedValue);
});
- it('renders correct usage quotas help link', () => {
- expect(findUsageQuotasHelpLink().attributes('href')).toBe(
- defaultProjectProvideValues.helpLinks.usageQuotas,
+ it('passes namespace storage entities to namespace details table', () => {
+ const expectedValue = getStorageTypesFromProjectStatistics(
+ NAMESPACE_STORAGE_TYPES,
+ mockProjectData.statistics,
+ mockProjectData.statisticsDetailsPaths,
+ storageTypeHelpPaths,
);
+
+ expect(findNamespaceDetailsTable().props('storageTypes')).toStrictEqual(expectedValue);
});
});
@@ -104,6 +133,14 @@ describe('ProjectStorageApp', () => {
it('shows default text for total usage', () => {
expect(findUsagePercentage().text()).toBe(TOTAL_USAGE_DEFAULT_TEXT);
});
+
+ it('passes empty array to project details table', () => {
+ expect(findProjectDetailsTable().props('storageTypes')).toStrictEqual([]);
+ });
+
+ it('passes empty array to namespace details table', () => {
+ expect(findNamespaceDetailsTable().props('storageTypes')).toStrictEqual([]);
+ });
});
describe('with apollo fetching error', () => {
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
index 37fc9602315..364517a474f 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
@@ -1,15 +1,36 @@
-import { GlTableLite, GlPopover } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlTableLite } from '@gitlab/ui';
+import { mount, Wrapper } from '@vue/test-utils'; // eslint-disable-line no-unused-vars
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ProjectStorageDetail from '~/usage_quotas/storage/components/project_storage_detail.vue';
-import { containerRegistryPopoverId, containerRegistryId } from '~/usage_quotas/storage/constants';
import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { projectData, projectHelpLinks } from '../mock_data';
describe('ProjectStorageDetail', () => {
+ /** @type { Wrapper } */
let wrapper;
- const { storageTypes } = projectData.storage;
+ const generateStorageType = (props) => {
+ return {
+ id: 'id',
+ name: 'name',
+ description: 'description',
+ helpPath: '/help-path',
+ detailsPath: '/details-link',
+ value: 42,
+ ...props,
+ };
+ };
+
+ const storageTypes = [
+ generateStorageType({ id: 'one' }),
+ generateStorageType({ id: 'two' }),
+ generateStorageType({
+ id: 'three',
+ warning: {
+ content: 'warning message',
+ },
+ }),
+ ];
+
const defaultProps = { storageTypes };
const createComponent = (props = {}) => {
@@ -26,23 +47,7 @@ describe('ProjectStorageDetail', () => {
);
};
- const generateStorageType = (id = 'buildArtifacts') => {
- return {
- storageType: {
- id,
- name: 'Test Name',
- description: 'Test Description',
- helpPath: '/test-type',
- },
- value: 400000,
- };
- };
-
const findTable = () => wrapper.findComponent(GlTableLite);
- const findPopoverById = (id) =>
- wrapper.findAllComponents(GlPopover).filter((p) => p.attributes('data-testid') === id);
- const findContainerRegistryPopover = () => findPopoverById(containerRegistryPopoverId);
- const findContainerRegistryWarningIcon = () => wrapper.find(`#${containerRegistryPopoverId}`);
beforeEach(() => {
createComponent();
@@ -51,33 +56,23 @@ describe('ProjectStorageDetail', () => {
describe('with storage types', () => {
it.each(storageTypes)(
'renders table row correctly %o',
- ({ storageType: { id, name, description } }) => {
+ ({ id, name, value, description, helpPath, warning }) => {
expect(wrapper.findByTestId(`${id}-name`).text()).toBe(name);
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id);
- expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
- projectHelpLinks[id],
- );
+ expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(helpPath);
+ expect(wrapper.findByTestId(`${id}-value`).text()).toContain(numberToHumanSize(value, 1));
+
+ expect(wrapper.findByTestId(`${id}-warning-icon`).exists()).toBe(Boolean(warning));
+ expect(wrapper.findByTestId(`${id}-popover`).exists()).toBe(Boolean(warning));
},
);
-
- it('should render items in order from the biggest usage size to the smallest', () => {
- const rows = findTable().find('tbody').findAll('tr');
- // Cloning array not to mutate the source
- const sortedStorageTypes = [...storageTypes].sort((a, b) => b.value - a.value);
-
- sortedStorageTypes.forEach((storageType, i) => {
- const rowUsageAmount = rows.wrappers[i].find('td:last-child').text();
- const expectedUsageAmount = numberToHumanSize(storageType.value, 1);
- expect(rowUsageAmount).toBe(expectedUsageAmount);
- });
- });
});
describe('with details links', () => {
it.each(storageTypes)('each $storageType.id', (item) => {
- const shouldExist = Boolean(item.storageType.detailsPath && item.value);
- const detailsLink = wrapper.findByTestId(`${item.storageType.id}-details-link`);
+ const shouldExist = Boolean(item.detailsPath && item.value);
+ const detailsLink = wrapper.findByTestId(`${item.id}-details-link`);
expect(detailsLink.exists()).toBe(shouldExist);
});
});
@@ -95,21 +90,4 @@ describe('ProjectStorageDetail', () => {
expect(findTable().find('td').exists()).toBe(false);
});
});
-
- describe.each`
- description | mockStorageTypes | rendersContainerRegistryPopover
- ${'without any storage type that has popover'} | ${[generateStorageType()]} | ${false}
- ${'with container registry storage type'} | ${[generateStorageType(containerRegistryId)]} | ${true}
- `('$description', ({ mockStorageTypes, rendersContainerRegistryPopover }) => {
- beforeEach(() => {
- createComponent({ storageTypes: mockStorageTypes });
- });
-
- it(`does ${
- rendersContainerRegistryPopover ? '' : ' not'
- } render container registry warning icon and popover`, () => {
- expect(findContainerRegistryWarningIcon().exists()).toBe(rendersContainerRegistryPopover);
- expect(findContainerRegistryPopover().exists()).toBe(rendersContainerRegistryPopover);
- });
- });
});
diff --git a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
index 7fef20c900e..fc116211bf0 100644
--- a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
@@ -44,7 +44,6 @@ describe('UsageGraph', () => {
buildArtifactsSize,
lfsObjectsSize,
packagesSize,
- containerRegistrySize,
repositorySize,
wikiSize,
snippetsSize,
@@ -57,14 +56,11 @@ describe('UsageGraph', () => {
expect(types.at(2).text()).toMatchInterpolatedText(
`Packages ${numberToHumanSize(packagesSize)}`,
);
- expect(types.at(3).text()).toMatchInterpolatedText(
- `Container Registry ${numberToHumanSize(containerRegistrySize)}`,
- );
- expect(types.at(4).text()).toMatchInterpolatedText(`LFS ${numberToHumanSize(lfsObjectsSize)}`);
- expect(types.at(5).text()).toMatchInterpolatedText(
+ expect(types.at(3).text()).toMatchInterpolatedText(`LFS ${numberToHumanSize(lfsObjectsSize)}`);
+ expect(types.at(4).text()).toMatchInterpolatedText(
`Snippets ${numberToHumanSize(snippetsSize)}`,
);
- expect(types.at(6).text()).toMatchInterpolatedText(
+ expect(types.at(5).text()).toMatchInterpolatedText(
`Job artifacts ${numberToHumanSize(buildArtifactsSize)}`,
);
});
@@ -102,7 +98,6 @@ describe('UsageGraph', () => {
'0.29411764705882354',
'0.23529411764705882',
'0.17647058823529413',
- '0.14705882352941177',
'0.11764705882352941',
'0.11764705882352941',
'0.041176470588235294',
@@ -121,7 +116,6 @@ describe('UsageGraph', () => {
'0.29411764705882354',
'0.23529411764705882',
'0.17647058823529413',
- '0.14705882352941177',
'0.11764705882352941',
'0.11764705882352941',
'0.041176470588235294',
diff --git a/spec/frontend/usage_quotas/storage/mock_data.js b/spec/frontend/usage_quotas/storage/mock_data.js
index 452fa83b9a7..16c03a13028 100644
--- a/spec/frontend/usage_quotas/storage/mock_data.js
+++ b/spec/frontend/usage_quotas/storage/mock_data.js
@@ -3,95 +3,6 @@ import mockGetProjectStorageStatisticsGraphQLResponse from 'test_fixtures/graphq
export { mockGetProjectStorageStatisticsGraphQLResponse };
export const mockEmptyResponse = { data: { project: null } };
-export const projectData = {
- storage: {
- totalUsage: '13.4 MiB',
- storageTypes: [
- {
- storageType: {
- id: 'containerRegistry',
- name: 'Container Registry',
- description: 'Gitlab-integrated Docker Container Registry for storing Docker Images.',
- helpPath: '/container_registry',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/container_registry',
- },
- value: 3900000,
- },
- {
- storageType: {
- id: 'buildArtifacts',
- name: 'Job artifacts',
- description: 'Job artifacts created by CI/CD.',
- helpPath: '/build-artifacts',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/artifacts',
- },
- value: 400000,
- },
- {
- storageType: {
- id: 'lfsObjects',
- name: 'LFS',
- description: 'Audio samples, videos, datasets, and graphics.',
- helpPath: '/lsf-objects',
- },
- value: 4800000,
- },
- {
- storageType: {
- id: 'packages',
- name: 'Packages',
- description: 'Code packages and container images.',
- helpPath: '/packages',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/packages',
- },
- value: 3800000,
- },
- {
- storageType: {
- id: 'repository',
- name: 'Repository',
- description: 'Git repository.',
- helpPath: '/repository',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/tree/master',
- },
- value: 3900000,
- },
- {
- storageType: {
- id: 'snippets',
- name: 'Snippets',
- description: 'Shared bits of code and text.',
- helpPath: '/snippets',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/snippets',
- },
- value: 0,
- },
- {
- storageType: {
- id: 'wiki',
- name: 'Wiki',
- description: 'Wiki content.',
- helpPath: '/wiki',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/wikis/pages',
- },
- value: 300000,
- },
- ],
- },
-};
-
-export const projectHelpLinks = {
- containerRegistry: '/container_registry',
- usageQuotas: '/usage-quotas',
- buildArtifacts: '/build-artifacts',
- lfsObjects: '/lsf-objects',
- packages: '/packages',
- repository: '/repository',
- snippets: '/snippets',
- wiki: '/wiki',
-};
-
export const defaultProjectProvideValues = {
projectPath: '/project-path',
- helpLinks: projectHelpLinks,
};
diff --git a/spec/frontend/usage_quotas/storage/utils_spec.js b/spec/frontend/usage_quotas/storage/utils_spec.js
index e3a271adc57..dd05e105c26 100644
--- a/spec/frontend/usage_quotas/storage/utils_spec.js
+++ b/spec/frontend/usage_quotas/storage/utils_spec.js
@@ -1,15 +1,9 @@
-import cloneDeep from 'lodash/cloneDeep';
import { PROJECT_STORAGE_TYPES } from '~/usage_quotas/storage/constants';
import {
- parseGetProjectStorageResults,
getStorageTypesFromProjectStatistics,
descendingStorageUsageSort,
} from '~/usage_quotas/storage/utils';
-import {
- mockGetProjectStorageStatisticsGraphQLResponse,
- defaultProjectProvideValues,
- projectData,
-} from './mock_data';
+import { mockGetProjectStorageStatisticsGraphQLResponse } from './mock_data';
describe('getStorageTypesFromProjectStatistics', () => {
const {
@@ -18,15 +12,18 @@ describe('getStorageTypesFromProjectStatistics', () => {
} = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
describe('matches project statistics value with matching storage type', () => {
- const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics);
+ const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ projectStatistics,
+ );
it.each(PROJECT_STORAGE_TYPES)('storage type: $id', ({ id }) => {
- expect(typesWithStats).toContainEqual({
- storageType: expect.objectContaining({
+ expect(typesWithStats).toContainEqual(
+ expect.objectContaining({
id,
+ value: projectStatistics[`${id}Size`],
}),
- value: projectStatistics[`${id}Size`],
- });
+ );
});
});
@@ -38,57 +35,31 @@ describe('getStorageTypesFromProjectStatistics', () => {
};
}, {});
- const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
+ const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ projectStatistics,
+ {},
+ helpLinks,
+ );
typesWithStats.forEach((type) => {
- const key = type.storageType.id;
- expect(type.storageType.helpPath).toBe(helpLinks[key]);
+ expect(type.helpPath).toBe(helpLinks[type.id]);
});
});
it('adds details page path', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
projectStatistics,
- {},
statisticsDetailsPaths,
+ {},
);
typesWithStats.forEach((type) => {
- expect(type.storageType.detailsPath).toBe(statisticsDetailsPaths[type.storageType.id]);
+ expect(type.detailsPath).toBe(statisticsDetailsPaths[type.id]);
});
});
});
-describe('parseGetProjectStorageResults', () => {
- it('parses project statistics correctly', () => {
- expect(
- parseGetProjectStorageResults(
- mockGetProjectStorageStatisticsGraphQLResponse.data,
- defaultProjectProvideValues.helpLinks,
- ),
- ).toMatchObject(projectData);
- });
-
- it('includes storage type with size of 0 in returned value', () => {
- const mockedResponse = cloneDeep(mockGetProjectStorageStatisticsGraphQLResponse.data);
- // ensuring a specific storage type item has size of 0
- mockedResponse.project.statistics.repositorySize = 0;
-
- const response = parseGetProjectStorageResults(
- mockedResponse,
- defaultProjectProvideValues.helpLinks,
- );
-
- expect(response.storage.storageTypes).toEqual(
- expect.arrayContaining([
- {
- storageType: expect.any(Object),
- value: 0,
- },
- ]),
- );
- });
-});
-
describe('descendingStorageUsageSort', () => {
it('sorts items by a given key in descending order', () => {
const items = [{ k: 1 }, { k: 3 }, { k: 2 }];
diff --git a/spec/lib/gitlab/audit/auditor_spec.rb b/spec/lib/gitlab/audit/auditor_spec.rb
index 386d4157e90..1a45235a4e7 100644
--- a/spec/lib/gitlab/audit/auditor_spec.rb
+++ b/spec/lib/gitlab/audit/auditor_spec.rb
@@ -25,22 +25,48 @@ RSpec.describe Gitlab::Audit::Auditor, feature_category: :audit_events do
describe '.audit' do
let(:audit!) { auditor.audit(context) }
+ before do
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(true)
+ end
+
context 'when yaml definition is not defined' do
before do
- allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_return(false)
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(false)
allow(Gitlab::AppLogger).to receive(:warn).and_return(app_logger)
end
- it 'logs a warning when YAML is not defined' do
- expected_warning = {
- message: 'Logging audit events without an event type definition will be deprecated soon ' \
- '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)',
- event_type: name
- }
+ context 'when feature flag raise_error_for_missing_audit_event_yml is enabled' do
+ before do
+ stub_feature_flags(raise_error_for_missing_audit_event_yml: true)
+ end
- audit!
+ it 'raises an error' do
+ expected_error = "Audit event type YML file is not defined for audit_operation. " \
+ "Please read https://docs.gitlab.com/ee/development/audit_event_guide/" \
+ "#how-to-instrument-new-audit-events for adding a new audit event"
+
+ expect { audit! }.to raise_error(StandardError, expected_error)
+ end
+ end
- expect(Gitlab::AppLogger).to have_received(:warn).with(expected_warning)
+ context 'when feature flag raise_error_for_missing_audit_event_yml is disabled' do
+ before do
+ stub_feature_flags(raise_error_for_missing_audit_event_yml: false)
+ end
+
+ it 'logs a warning when YAML is not defined' do
+ expected_warning = {
+ message: 'Logging audit events without an event type definition will be deprecated soon ' \
+ '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)',
+ event_type: name
+ }
+
+ audit!
+
+ expect(Gitlab::AppLogger).to have_received(:warn).with(expected_warning)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/block_spec.rb b/spec/lib/gitlab/ci/interpolation/block_spec.rb
index 4a8709df3dc..e4ccfbdfd63 100644
--- a/spec/lib/gitlab/ci/interpolation/block_spec.rb
+++ b/spec/lib/gitlab/ci/interpolation/block_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
end
let(:ctx) do
- { inputs: { data: 'abc' }, env: { 'ENV' => 'dev' } }
+ { inputs: { data: 'abcdef' }, env: { 'ENV' => 'dev' } }
end
it 'knows its content' do
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
end
it 'properly evaluates the access pattern' do
- expect(subject.value).to eq 'abc'
+ expect(subject.value).to eq 'abcdef'
end
describe '.match' do
@@ -35,5 +35,78 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
expect { |b| described_class.match('$[[]]', &b) }
.to yield_with_args('$[[]]', '')
end
+
+ context 'when functions are specified in the block' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 | func1 ]] $[[ access2 | func1 | func2(0,1) ]]', &b) }
+ .to yield_successive_args(['$[[ access1 | func1 ]]', 'access1 | func1'],
+ ['$[[ access2 | func1 | func2(0,1) ]]', 'access2 | func1 | func2(0,1)'])
+ end
+ end
+ end
+
+ describe 'when functions are specified in the block' do
+ let(:function_string1) { 'truncate(1,5)' }
+ let(:data) { "inputs.data | #{function_string1}" }
+ let(:access_value) { 'abcdef' }
+
+ it 'returns the modified value' do
+ expect(subject).to be_valid
+ expect(subject.value).to eq('bcdef')
+ end
+
+ context 'when there is an access error' do
+ let(:data) { "inputs.undefined | #{function_string1}" }
+
+ it 'returns the access error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `undefined`')
+ end
+ end
+
+ context 'when there is a function error' do
+ let(:data) { 'inputs.data | undefined' }
+
+ it 'returns the function error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `undefined`/)
+ end
+ end
+
+ context 'when multiple functions are specified' do
+ let(:function_string2) { 'truncate(2,2)' }
+ let(:data) { "inputs.data | #{function_string1} | #{function_string2}" }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+
+ context 'when the data has inconsistent spacing' do
+ let(:data) { "inputs.data|#{function_string1} | #{function_string2} " }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+ end
+
+ context 'when a stack of functions errors in the middle' do
+ let(:function_string2) { 'truncate(2)' }
+
+ it 'does not modify the value' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `truncate\(2\)`/)
+ expect(subject.instance_variable_get(:@value)).to be_nil
+ end
+ end
+
+ context 'when too many functions are specified' do
+ it 'returns error' do
+ stub_const('Gitlab::Ci::Interpolation::Block::MAX_FUNCTIONS', 1)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('too many functions in interpolation block')
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb
new file mode 100644
index 00000000000..1a15938b988
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Functions::Base, feature_category: :pipeline_composition do
+ let(:custom_function_klass) do
+ Class.new(described_class) do
+ def self.function_expression_pattern
+ /.*/
+ end
+
+ def self.name
+ 'test_function'
+ end
+ end
+ end
+
+ it 'defines an expected interface for child classes' do
+ expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError)
+ expect { described_class.name }.to raise_error(NotImplementedError)
+ expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb
new file mode 100644
index 00000000000..69ce30c51ec
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Functions::Truncate, feature_category: :pipeline_composition do
+ it 'matches exactly the truncate function with 2 numeric arguments' do
+ expect(described_class.matches?('truncate(1,2)')).to be_truthy
+ expect(described_class.matches?('truncate( 11 , 222 )')).to be_truthy
+ expect(described_class.matches?('truncate( string , 222 )')).to be_falsey
+ expect(described_class.matches?('truncate(222)')).to be_falsey
+ expect(described_class.matches?('unknown(1,2)')).to be_falsey
+ end
+
+ it 'truncates the given input' do
+ function = described_class.new('truncate(1,2)')
+
+ output = function.execute('test')
+
+ expect(function).to be_valid
+ expect(output).to eq('es')
+ end
+
+ context 'when given a non-string input' do
+ it 'returns an error' do
+ function = described_class.new('truncate(1,2)')
+
+ function.execute(100)
+
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb
new file mode 100644
index 00000000000..6e31d23f764
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::FunctionsStack, feature_category: :pipeline_composition do
+ let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] }
+ let(:input_value) { 'test_input_value' }
+ let(:function_klass) { Gitlab::Ci::Interpolation::Functions::Truncate }
+
+ subject { described_class.new(functions).evaluate(input_value) }
+
+ it 'modifies the given input value according to the function expressions' do
+ expect(subject).to be_success
+ expect(subject.value).to eq('es')
+ end
+
+ context 'when applying a function fails' do
+ let(:input_value) { 666 }
+
+ it 'returns the error given by the failure' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+
+ context 'when function expressions do not match any function' do
+ let(:functions) { ['truncate(0)', 'unknown'] }
+
+ it 'returns an error' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'no function matching `truncate(0)`: check that the function name, arguments, and types are correct',
+ 'no function matching `unknown`: check that the function name, arguments, and types are correct'
+ )
+ end
+ end
+end
diff --git a/spec/presenters/ml/model_presenter_spec.rb b/spec/presenters/ml/model_presenter_spec.rb
new file mode 100644
index 00000000000..dbbd3b57033
--- /dev/null
+++ b/spec/presenters/ml/model_presenter_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
+ let_it_be(:project) { build_stubbed(:project) }
+ let_it_be(:model1) { build_stubbed(:ml_models, project: project) }
+ let_it_be(:model2) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
+
+ describe '#latest_version_name' do
+ subject { model.present.latest_version_name }
+
+ context 'when model has version' do
+ let(:model) { model2 }
+
+ it 'is the version of latest_version' do
+ is_expected.to eq(model2.latest_version.version)
+ end
+ end
+
+ context 'when model has no versions' do
+ let(:model) { model1 }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#latest_package_path' do
+ subject { model.present.latest_package_path }
+
+ context 'when model version does not have package' do
+ let(:model) { model1 }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when latest model version has package' do
+ let(:model) { model2 }
+
+ it { is_expected.to eq("/#{project.full_path}/-/packages/#{model.latest_version.package_id}") }
+ end
+ end
+end
diff --git a/spec/presenters/ml/models_index_presenter_spec.rb b/spec/presenters/ml/models_index_presenter_spec.rb
deleted file mode 100644
index 549700cdd84..00000000000
--- a/spec/presenters/ml/models_index_presenter_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ml::ModelsIndexPresenter, feature_category: :mlops do
- let_it_be(:project) { build_stubbed(:project) }
- let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
- let_it_be(:model2) { build_stubbed(:ml_models, project: project) }
- let_it_be(:models) do
- [model1, model2]
- end
-
- describe '#execute' do
- subject { Gitlab::Json.parse(described_class.new(models).present)['models'] }
-
- it 'presents models correctly' do
- expected_models = [
- {
- 'name' => model1.name,
- 'version' => model1.latest_version.version,
- 'path' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}"
- },
- {
- 'name' => model2.name,
- 'version' => nil,
- 'path' => nil
- }
- ]
-
- is_expected.to match_array(expected_models)
- end
- end
-end
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index fca3c84e534..6055390c8d5 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -272,6 +272,16 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do
let(:mutation) { graphql_mutation(:workItemCreate, input.merge(namespacePath: group.full_path), fields) }
it_behaves_like 'creates work item'
+
+ context 'when the namespace_level_work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(namespace_level_work_items: false)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [
+ Mutations::WorkItems::Create::DISABLED_FF_ERROR
+ ]
+ end
end
context 'when both projectPath and namespacePath are passed' do
diff --git a/spec/requests/projects/ml/models_controller_spec.rb b/spec/requests/projects/ml/models_controller_spec.rb
index b57f5db9b68..8569f2396d3 100644
--- a/spec/requests/projects/ml/models_controller_spec.rb
+++ b/spec/requests/projects/ml/models_controller_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
let_it_be(:user) { project.first_owner }
let_it_be(:model1) { create(:ml_models, :with_versions, project: project) }
let_it_be(:model2) { create(:ml_models, project: project) }
+ let_it_be(:model_in_different_project) { create(:ml_models) }
let(:model_registry_enabled) { true }
@@ -35,10 +36,10 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
index_request
end
- it 'prepares model view using the presenter' do
- expect(::Ml::ModelsIndexPresenter).to receive(:new).and_call_original
-
+ it 'fetches the correct models' do
index_request
+
+ expect(assigns(:models)).to match_array([model1, model2])
end
it 'does not perform N+1 sql queries' do
diff --git a/yarn.lock b/yarn.lock
index 0171b999690..3c47c5ec061 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1127,10 +1127,10 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
-"@gitlab/svgs@3.57.0":
- version "3.57.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.57.0.tgz#45e32f2e735b22c89ac36da2c4f14624bf06ccc0"
- integrity sha512-K65ashJ1Hry+mrM6pBGe0bQ2xR9XVP9qtfNtNM9cl7MMWlSfQbPy+E8oWBnMTqSJhSSMzk6c3VRFErXOV/SgUA==
+"@gitlab/svgs@3.58.0":
+ version "3.58.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.58.0.tgz#cae8483c81e260af6d1d55a25235099683ed76b7"
+ integrity sha512-4aCsp0sVn+XBYJAiO/7IdwVxfINBJ0bRvvuvM1R91KYjs2XFw/rtg1HPQ+9MxZHcD5x/cIdDL6dWwr3XzfFWjw==
"@gitlab/ui@64.20.1":
version "64.20.1"