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:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-10 18:16:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-10 18:16:42 +0300
commite1d966e6543433479a932e1e29ad538cd587699a (patch)
tree8553431489849d866639ddc17ba873a98df0186d
parent8731c2348e508e52cad156bd819b0accbf88d495 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/as-if-foss.gitlab-ci.yml15
-rw-r--r--.gitlab/ci/preflight.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml20
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue24
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_cell.js11
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_header.js36
-rw-r--r--app/assets/javascripts/content_editor/extensions/task_item.js32
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js6
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js4
-rw-r--r--app/assets/javascripts/editor/schema/ci.json12
-rw-r--r--app/assets/javascripts/projects/new/components/new_project_url_select.vue162
-rw-r--r--app/assets/javascripts/projects/project_new.js3
-rw-r--r--app/assets/stylesheets/components/content_editor.scss9
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss14
-rw-r--r--app/assets/stylesheets/page_bundles/wiki.scss4
-rw-r--r--app/controllers/projects/settings/packages_and_registries_controller.rb6
-rw-r--r--app/helpers/groups_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/workers/concerns/gitlab/github_import/object_importer.rb8
-rw-r--r--app/workers/concerns/gitlab/github_import/rescheduling_methods.rb2
-rw-r--r--app/workers/concerns/gitlab/github_import/stage_methods.rb2
-rw-r--r--data/deprecations/16-8-api-lint-ref-removal.yml34
-rw-r--r--doc/administration/audit_event_streaming/audit_event_types.md2
-rw-r--r--doc/administration/reference_architectures/2k_users.md5
-rw-r--r--doc/ci/yaml/index.md12
-rw-r--r--doc/development/integrations/secure.md6
-rw-r--r--doc/development/integrations/secure_partner_integration.md2
-rw-r--r--doc/update/deprecations.md14
-rw-r--r--doc/user/application_security/dependency_scanning/index.md266
-rw-r--r--glfm_specification/output_example_snapshots/html.yml6
-rw-r--r--glfm_specification/output_example_snapshots/prosemirror_json.yml159
-rw-r--r--lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json5
-rw-r--r--lib/gitlab/github_import/user_finder.rb37
-rw-r--r--locale/gitlab.pot50
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/project/import/repo_by_url.rb3
-rw-r--r--qa/qa/page/project/new.rb5
-rwxr-xr-xscripts/setup/generate-as-if-foss-env.rb46
-rw-r--r--spec/features/projects/new_project_spec.rb14
-rw-r--r--spec/features/projects/user_creates_project_spec.rb4
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js42
-rw-r--r--spec/frontend/content_editor/extensions/task_item_spec.js115
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js50
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/negative_tests/image.yml11
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml14
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/positive_tests/image.yml13
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml15
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js69
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb6
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb18
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb6
-rw-r--r--spec/requests/api/ci/runner/yamls/image-executor_opts-user.yml25
-rw-r--r--spec/requests/api/ci/runner/yamls/service-executor_opts-user.yml27
-rw-r--r--spec/requests/projects/settings/packages_and_registries_controller_spec.rb30
-rw-r--r--spec/scripts/setup/generate_as_if_foss_env_spec.rb82
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb27
-rw-r--r--spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb9
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb13
-rw-r--r--yarn.lock35
62 files changed, 1364 insertions, 411 deletions
diff --git a/.gitlab/ci/as-if-foss.gitlab-ci.yml b/.gitlab/ci/as-if-foss.gitlab-ci.yml
index 0c496ebacd8..128a6195c4c 100644
--- a/.gitlab/ci/as-if-foss.gitlab-ci.yml
+++ b/.gitlab/ci/as-if-foss.gitlab-ci.yml
@@ -61,10 +61,23 @@ start-as-if-foss:
ENABLE_RSPEC_FAST_SPEC_HELPER: $ENABLE_RSPEC_FAST_SPEC_HELPER
ENABLE_RSPEC_UNIT: $ENABLE_RSPEC_UNIT
ENABLE_RSPEC_INTEGRATION: $ENABLE_RSPEC_INTEGRATION
+ ENABLE_RSPEC_SYSTEM: $ENABLE_RSPEC_SYSTEM
ENABLE_RSPEC_MIGRATION: $ENABLE_RSPEC_MIGRATION
ENABLE_RSPEC_BACKGROUND_MIGRATION: $ENABLE_RSPEC_BACKGROUND_MIGRATION
- ENABLE_RSPEC_SYSTEM: $ENABLE_RSPEC_SYSTEM
+ ENABLE_RSPEC_FRONTEND_FIXTURE: $ENABLE_RSPEC_FRONTEND_FIXTURE
+ ENABLE_BUILD_ASSETS_IMAGE: $ENABLE_BUILD_ASSETS_IMAGE
+ ENABLE_BUILD_QA_IMAGE: $ENABLE_BUILD_QA_IMAGE
+ ENABLE_COMPILE_PRODUCTION_ASSETS: $ENABLE_COMPILE_PRODUCTION_ASSETS
+ ENABLE_COMPILE_STORYBOOK: $ENABLE_COMPILE_STORYBOOK
+ ENABLE_COMPILE_TEST_ASSETS: $ENABLE_COMPILE_TEST_ASSETS
+ ENABLE_ESLINT: $ENABLE_ESLINT
+ ENABLE_GENERATE_APOLLO_GRAPHQL_SCHEMA: $ENABLE_GENERATE_APOLLO_GRAPHQL_SCHEMA
+ ENABLE_GRAPHQL_SCHEMA_DUMP: $ENABLE_GRAPHQL_SCHEMA_DUMP
ENABLE_JEST: $ENABLE_JEST
+ ENABLE_JEST_INTEGRATION: $ENABLE_JEST_INTEGRATION
+ ENABLE_QA_INTERNAL: $ENABLE_QA_INTERNAL
+ ENABLE_QA_SELECTORS: $ENABLE_QA_SELECTORS
+ ENABLE_STATIC_ANALYSIS: $ENABLE_STATIC_ANALYSIS
trigger:
project: gitlab-org/gitlab-foss
branch: as-if-foss/${CI_COMMIT_REF_NAME}
diff --git a/.gitlab/ci/preflight.gitlab-ci.yml b/.gitlab/ci/preflight.gitlab-ci.yml
index 6723608d0db..f4038d48366 100644
--- a/.gitlab/ci/preflight.gitlab-ci.yml
+++ b/.gitlab/ci/preflight.gitlab-ci.yml
@@ -70,7 +70,7 @@ no-jh-check:
qa:selectors:
extends:
- .qa-preflight-job
- - .qa:rules:ee-and-foss
+ - .qa:rules:selectors
script:
- bundle exec bin/qa Test::Sanity::Selectors
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index ea17372bbdb..4ced188b932 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -870,6 +870,7 @@
when: never
- <<: *if-merge-request-labels-pipeline-expedite
when: never
+ - if: '$ENABLE_BUILD_QA_IMAGE == "true"'
- <<: *if-merge-request-targeting-stable-branch
changes: *setup-test-env-patterns
- <<: *if-merge-request-labels-run-review-app
@@ -913,8 +914,7 @@
.build-images:rules:build-qa-image-as-if-foss:
rules:
- - <<: *if-jh
- when: never
+ - !reference [".strict-ee-only-rules", rules]
- !reference [".build-images:rules:build-qa-image-merge-requests", "rules"]
- <<: *if-default-branch-schedule-nightly
variables:
@@ -974,6 +974,7 @@
when: never
- <<: *if-merge-request-labels-pipeline-expedite
when: never
+ - if: '$ENABLE_BUILD_ASSETS_IMAGE == "true"'
- <<: *if-merge-request-targeting-stable-branch
changes: *setup-test-env-patterns
- <<: *if-ruby-branch
@@ -1197,6 +1198,7 @@
when: never
- <<: *if-merge-request-labels-pipeline-expedite
when: never
+ - if: '$ENABLE_COMPILE_PRODUCTION_ASSETS == "true"'
- <<: *if-merge-request-targeting-stable-branch
changes: *setup-test-env-patterns
- <<: *if-merge-request-labels-run-review-app
@@ -1226,7 +1228,7 @@
.frontend:rules:compile-test-assets:
rules:
- - if: '$ENABLE_RSPEC == "true"'
+ - if: '$ENABLE_COMPILE_TEST_ASSETS == "true"'
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request
changes: *backend-patterns
@@ -1250,7 +1252,12 @@
rules:
- <<: *if-merge-request-labels-pipeline-expedite
when: never
- - if: '$ENABLE_JEST == "true"'
+ - if: '$ENABLE_JEST_INTEGRATION == "true"'
+ - if: '$ENABLE_RSPEC_FRONTEND_FIXTURE == "true"'
+ - if: '$ENABLE_ESLINT == "true"'
+ - if: '$ENABLE_COMPILE_STORYBOOK == "true"'
+ - if: '$ENABLE_GRAPHQL_SCHEMA_DUMP == "true"'
+ - if: '$ENABLE_GENERATE_APOLLO_GRAPHQL_SCHEMA == "true"'
- <<: *if-merge-request-labels-run-all-rspec
- <<: *if-merge-request-labels-frontend-and-feature-flag
- <<: *if-default-refs
@@ -1477,11 +1484,13 @@
.qa:rules:internal:
rules:
+ - if: '$ENABLE_QA_INTERNAL == "true"'
- <<: *if-default-refs
changes: *qa-patterns
-.qa:rules:ee-and-foss:
+.qa:rules:selectors:
rules:
+ - if: '$ENABLE_QA_SELECTORS == "true"'
- <<: *if-default-refs
changes: *code-qa-patterns
@@ -2424,6 +2433,7 @@
.static-analysis:rules:static-analysis:
rules:
+ - if: '$ENABLE_STATIC_ANALYSIS == "true"'
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
- <<: *if-default-refs
diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
index e7a1b058341..11ac024b799 100644
--- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
+++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue
@@ -7,7 +7,7 @@ import { __, n__ } from '~/locale';
const TABLE_CELL_HEADER = 'th';
const TABLE_CELL_BODY = 'td';
-function getDropdownItems({ selectedRect, cellType, rowspan = 1, colspan = 1 }) {
+function getDropdownItems({ selectedRect, cellType, rowspan = 1, colspan = 1, align = 'left' }) {
const totalRows = selectedRect?.map.height;
const totalCols = selectedRect?.map.width;
const isTableBodyCell = cellType === TABLE_CELL_BODY;
@@ -20,9 +20,21 @@ function getDropdownItems({ selectedRect, cellType, rowspan = 1, colspan = 1 })
const showDeleteRowOption = totalRows > selectedRows + 1 && isTableBodyCell;
const showDeleteColumnOption = totalCols > selectedCols;
+ const isTableBodyHeader = cellType === TABLE_CELL_HEADER;
+ const showAlignLeftOption = isTableBodyHeader && (align === 'center' || align === 'right');
+ const showAlignCenterOption = isTableBodyHeader && align !== 'center';
+ const showAlignRightOption = isTableBodyHeader && align !== 'right';
+
return [
{
items: [
+ showAlignLeftOption && { text: __('Align column left'), value: 'alignColumnLeft' },
+ showAlignCenterOption && { text: __('Align column center'), value: 'alignColumnCenter' },
+ showAlignRightOption && { text: __('Align column right'), value: 'alignColumnRight' },
+ ].filter(Boolean),
+ },
+ {
+ items: [
{ text: __('Insert column before'), value: 'addColumnBefore' },
{ text: __('Insert column after'), value: 'addColumnAfter' },
isTableBodyCell && { text: __('Insert row before'), value: 'addRowBefore' },
@@ -93,6 +105,7 @@ export default {
cellType: this.cellType,
rowspan: this.node.attrs.rowspan,
colspan: this.node.attrs.colspan,
+ align: this.node.attrs.align,
});
},
},
@@ -129,7 +142,7 @@ export default {
runCommand({ value: command }) {
this.hideDropdown();
- this.editor.chain()[command]().run();
+ this.editor.chain()[command](this.getPos()).run();
},
hideDropdown() {
@@ -143,6 +156,7 @@ export default {
:as="cellType"
:rowspan="node.attrs.rowspan || 1"
:colspan="node.attrs.colspan || 1"
+ :align="node.attrs.align || 'left'"
dir="auto"
class="gl-m-0! gl-p-0! gl-relative"
@click="hideDropdown"
@@ -168,6 +182,10 @@ export default {
@action="runCommand"
/>
</span>
- <node-view-content as="div" class="gl-p-5 gl-min-w-10" />
+ <node-view-content
+ as="div"
+ class="gl-p-5 gl-min-w-10"
+ :style="{ 'text-align': node.attrs.align || 'left' }"
+ />
</node-view-wrapper>
</template>
diff --git a/app/assets/javascripts/content_editor/extensions/table_cell.js b/app/assets/javascripts/content_editor/extensions/table_cell.js
index 9f437ce066c..53dba4fd960 100644
--- a/app/assets/javascripts/content_editor/extensions/table_cell.js
+++ b/app/assets/javascripts/content_editor/extensions/table_cell.js
@@ -5,6 +5,17 @@ import TableCellBodyWrapper from '../components/wrappers/table_cell_body.vue';
export default TableCell.extend({
content: 'block+',
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ align: {
+ default: 'left',
+ parseHTML: (element) => element.getAttribute('align') || element.style.textAlign || 'left',
+ renderHTML: () => '',
+ },
+ };
+ },
+
addNodeView() {
return VueNodeViewRenderer(TableCellBodyWrapper);
},
diff --git a/app/assets/javascripts/content_editor/extensions/table_header.js b/app/assets/javascripts/content_editor/extensions/table_header.js
index 045fd03199b..ca2a0eb5cfd 100644
--- a/app/assets/javascripts/content_editor/extensions/table_header.js
+++ b/app/assets/javascripts/content_editor/extensions/table_header.js
@@ -1,9 +1,45 @@
import { TableHeader } from '@tiptap/extension-table-header';
import { VueNodeViewRenderer } from '@tiptap/vue-2';
+import { CellSelection } from '@tiptap/pm/tables';
import TableCellHeaderWrapper from '../components/wrappers/table_cell_header.vue';
export default TableHeader.extend({
content: 'block+',
+
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ align: {
+ default: 'left',
+ parseHTML: (element) => element.getAttribute('align') || element.style.textAlign || 'left',
+ renderHTML: () => '',
+ },
+ };
+ },
+
+ addCommands() {
+ return {
+ ...this.parent?.(),
+ alignColumn: (pos, align) => ({ commands }) => {
+ commands.selectColumn(pos);
+ commands.updateAttributes('tableHeader', { align });
+ commands.updateAttributes('tableCell', { align });
+ },
+ alignColumnLeft: (pos) => ({ commands }) => commands.alignColumn(pos, 'left'),
+ alignColumnCenter: (pos) => ({ commands }) => commands.alignColumn(pos, 'center'),
+ alignColumnRight: (pos) => ({ commands }) => commands.alignColumn(pos, 'right'),
+ selectColumn: (pos) => ({ tr, dispatch }) => {
+ if (dispatch) {
+ const position = tr.doc.resolve(pos);
+ const colSelection = CellSelection.colSelection(position);
+ tr.setSelection(colSelection);
+ }
+
+ return true;
+ },
+ };
+ },
+
addNodeView() {
return VueNodeViewRenderer(TableCellHeaderWrapper);
},
diff --git a/app/assets/javascripts/content_editor/extensions/task_item.js b/app/assets/javascripts/content_editor/extensions/task_item.js
index 849fd55034e..1e19878be9b 100644
--- a/app/assets/javascripts/content_editor/extensions/task_item.js
+++ b/app/assets/javascripts/content_editor/extensions/task_item.js
@@ -19,9 +19,17 @@ export default TaskItem.extend({
return checkbox?.checked;
},
- renderHTML: (attributes) => ({
- 'data-checked': attributes.checked,
- }),
+ renderHTML: (attributes) => attributes.checked && { 'data-checked': true },
+ keepOnSplit: false,
+ },
+ inapplicable: {
+ default: false,
+ parseHTML: (element) => {
+ const checkbox = element.querySelector('input[type=checkbox].task-list-item-checkbox');
+
+ return typeof checkbox?.dataset.inapplicable !== 'undefined';
+ },
+ renderHTML: (attributes) => attributes.inapplicable && { 'data-inapplicable': true },
keepOnSplit: false,
},
};
@@ -33,6 +41,24 @@ export default TaskItem.extend({
tag: 'li.task-list-item',
priority: PARSE_HTML_PRIORITY_HIGHEST,
},
+ {
+ tag: 'li.task-list-item.inapplicable s',
+ skip: true,
+ priority: PARSE_HTML_PRIORITY_HIGHEST,
+ },
];
},
+
+ addNodeView() {
+ const nodeView = this.parent?.();
+ return ({ node, ...args }) => {
+ const nodeViewInstance = nodeView({ node, ...args });
+
+ if (node.attrs.inapplicable) {
+ nodeViewInstance.dom.querySelector('input[type=checkbox]').disabled = true;
+ }
+
+ return nodeViewInstance;
+ };
+ },
});
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index 972b4acf523..3b759de57f2 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -228,7 +228,11 @@ const defaultSerializerConfig = {
[TableHeader.name]: renderTableCell,
[TableRow.name]: renderTableRow,
[TaskItem.name]: preserveUnchanged((state, node) => {
- state.write(`[${node.attrs.checked ? 'x' : ' '}] `);
+ let symbol = ' ';
+ if (node.attrs.inapplicable) symbol = '~';
+ else if (node.attrs.checked) symbol = 'x';
+
+ state.write(`[${symbol}] `);
if (!node.textContent) state.write('&nbsp;');
state.renderContent(node);
}),
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 87959a44560..2734879e4c4 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -2,8 +2,8 @@ import { uniq, isString, omit, isFunction } from 'lodash';
import { removeLastSlashInUrlPath, removeUrlProtocol } from '../../lib/utils/url_utility';
const defaultAttrs = {
- td: { colspan: 1, rowspan: 1, colwidth: null },
- th: { colspan: 1, rowspan: 1, colwidth: null },
+ td: { colspan: 1, rowspan: 1, colwidth: null, align: 'left' },
+ th: { colspan: 1, rowspan: 1, colwidth: null, align: 'left' },
};
const defaultIgnoreAttrs = ['sourceMarkdown', 'sourceMapKey'];
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index 7cedd00e491..0245c73d17c 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -528,6 +528,12 @@
"type": "string",
"minLength": 1,
"description": "Image architecture to pull."
+ },
+ "user": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255,
+ "description": "Username or UID to use for the container."
}
}
},
@@ -603,6 +609,12 @@
"type": "string",
"minLength": 1,
"description": "Image architecture to pull."
+ },
+ "user": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255,
+ "description": "Username or UID to use for the container."
}
}
},
diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
index 84a2ddfce07..c9a502bb6d2 100644
--- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue
+++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
@@ -1,20 +1,11 @@
<script>
-import {
- GlButton,
- GlButtonGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlDropdownSectionHeader,
- GlSearchBoxByType,
- GlTruncate,
-} from '@gitlab/ui';
+import { GlButton, GlButtonGroup, GlTruncate, GlCollapsibleListbox, GlIcon } from '@gitlab/ui';
import { joinPaths, PATH_SEPARATOR } from '~/lib/utils/url_utility';
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
-import { s__ } from '~/locale';
+import { __, s__, n__ } from '~/locale';
import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql';
import eventHub from '../event_hub';
@@ -22,12 +13,9 @@ export default {
components: {
GlButton,
GlButtonGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlDropdownSectionHeader,
- GlSearchBoxByType,
GlTruncate,
+ GlCollapsibleListbox,
+ GlIcon,
},
mixins: [Tracking.mixin()],
apollo: {
@@ -91,12 +79,61 @@ export default {
!this.groupPathToFilterBy
);
},
- hasNoMatches() {
- return !this.hasGroupMatches && !this.hasNamespaceMatches;
+ items() {
+ return this.groupsItems.concat(this.namespaceItems);
+ },
+ groupsItems() {
+ if (this.hasGroupMatches) {
+ return [
+ {
+ text: __('Groups'),
+ options: this.filteredGroups.map((group) => ({
+ value: group.id,
+ text: group.fullPath,
+ })),
+ },
+ ];
+ }
+
+ return [];
+ },
+ allItems() {
+ return this.filteredGroups.concat(this.currentUser.namespace);
+ },
+ namespaceItems() {
+ if (this.hasNamespaceMatches && this.userNamespaceUniqueId)
+ return [
+ {
+ text: __('Users'),
+ options: [
+ {
+ value: this.userNamespace.id,
+ text: this.userNamespace.fullPath,
+ },
+ ],
+ },
+ ];
+ return [];
},
dropdownPlaceholderClass() {
return this.selectedNamespace.id ? '' : 'gl-text-gray-500!';
},
+ dropdownText() {
+ if (this.selectedNamespace && this.selectedNamespace?.fullPath) {
+ return this.selectedNamespace.fullPath;
+ }
+ return null;
+ },
+ loading() {
+ return this.$apollo.queries.currentUser.loading;
+ },
+ searchSummary() {
+ return n__(
+ 'ProjectsNew|%d group or namespace found',
+ 'ProjectsNew|%d groups or namespaces found',
+ this.items.length,
+ );
+ },
},
created() {
eventHub.$on('select-template', this.handleSelectTemplate);
@@ -109,15 +146,18 @@ export default {
if (this.shouldSkipQuery) {
this.shouldSkipQuery = false;
}
- this.$refs.search.focusInput();
- },
- handleDropdownItemClick(namespace) {
- eventHub.$emit('update-visibility', {
- name: namespace.name,
- visibility: namespace.visibility,
- showPath: namespace.webUrl,
- editPath: joinPaths(namespace.webUrl, '-', 'edit'),
- });
+ },
+ handleDropdownItemClick(namespaceId) {
+ const namespace = this.allItems.find((item) => item.id === namespaceId);
+
+ if (namespace) {
+ eventHub.$emit('update-visibility', {
+ name: namespace.name,
+ visibility: namespace.visibility,
+ showPath: namespace.webUrl,
+ editPath: joinPaths(namespace.webUrl, '-', 'edit'),
+ });
+ }
this.setNamespace(namespace);
},
handleSelectTemplate(id, fullPath) {
@@ -137,6 +177,12 @@ export default {
this.track('activate_form_input', { label: this.trackLabel, property: 'project_path' });
}
},
+ onSearch(query) {
+ this.search = query;
+ },
+ },
+ i18n: {
+ emptySearchResult: __('No matches found'),
},
emptyNameSpace: {
id: undefined,
@@ -154,48 +200,38 @@ export default {
>{{ rootUrl }}</gl-button
>
- <gl-dropdown
- class="js-group-namespace-dropdown gl-flex-grow-1"
- :toggle-class="`gl-rounded-top-right-base! gl-rounded-bottom-right-base! gl-w-20 ${dropdownPlaceholderClass}`"
+ <gl-collapsible-listbox
+ searchable
+ fluid-width
+ :searching="loading"
+ :items="items"
+ class="js-group-namespace-dropdown group-namespace-dropdown gl-flex-grow-1"
+ :toggle-text="dropdownText"
+ :no-results-text="$options.i18n.emptySearchResult"
data-testid="select-namespace-dropdown"
@show="trackDropdownShow"
@shown="handleDropdownShown"
+ @select="handleDropdownItemClick"
+ @search="onSearch"
>
- <template #button-text>
- <gl-truncate
- v-if="selectedNamespace.fullPath"
- :text="selectedNamespace.fullPath"
- position="start"
- with-tooltip
- />
+ <template #toggle>
+ <gl-button
+ class="gl-flex-basis-full! gl-rounded-left-none! gl-w-20"
+ :class="dropdownPlaceholderClass"
+ >
+ <gl-truncate
+ :text="dropdownText"
+ position="start"
+ class="gl-overflow-hidden gl-mr-auto"
+ with-tooltip
+ />
+ <gl-icon class="gl-button-icon dropdown-chevron gl-mr-0! gl-ml-2!" name="chevron-down" />
+ </gl-button>
</template>
- <gl-search-box-by-type
- ref="search"
- v-model.trim="search"
- :is-loading="$apollo.queries.currentUser.loading"
- data-testid="select-namespace-dropdown-search-field"
- />
- <template v-if="!$apollo.queries.currentUser.loading">
- <template v-if="hasGroupMatches">
- <gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
- <gl-dropdown-item
- v-for="group of filteredGroups"
- :key="group.id"
- @click="handleDropdownItemClick(group)"
- >
- {{ group.fullPath }}
- </gl-dropdown-item>
- </template>
- <template v-if="hasNamespaceMatches && userNamespaceUniqueId">
- <gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
- <gl-dropdown-item @click="handleDropdownItemClick(userNamespace)">
- {{ userNamespace.fullPath }}
- </gl-dropdown-item>
- </template>
- <gl-dropdown-text v-if="hasNoMatches">{{ __('No matches found') }}</gl-dropdown-text>
+ <template #search-summary-sr-only>
+ {{ searchSummary }}
</template>
- </gl-dropdown>
-
+ </gl-collapsible-listbox>
<input type="hidden" name="project[selected_namespace_id]" :value="selectedNamespace.id" />
<input
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 2b5e2dcb301..9e71e662d70 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -70,7 +70,8 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
};
const selectedNamespaceId = () => document.querySelector('[name="project[selected_namespace_id]"]');
-const dropdownButton = () => document.querySelector('.js-group-namespace-dropdown > button');
+const dropdownButton = () =>
+ document.querySelector('.js-group-namespace-dropdown .gl-new-dropdown-custom-toggle > button');
const namespaceButton = () => document.querySelector('.js-group-namespace-button');
const namespaceError = () => document.querySelector('.js-group-namespace-error');
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 97f2add4e77..c654eb16af5 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -35,6 +35,10 @@
background-color: transparent;
}
+ th[align] *, td[align] * {
+ text-align: inherit;
+ }
+
td,
th,
li,
@@ -149,6 +153,11 @@
padding: $gl-spacing-scale-1 $gl-spacing-scale-3 0 0;
margin: 0;
}
+
+ &[data-inapplicable] * {
+ text-decoration: line-through;
+ color: $gl-text-color-disabled;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 649ceb95731..2558ddec9b9 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -937,3 +937,17 @@
width: 100%;
}
}
+
+.group-namespace-dropdown .gl-new-dropdown-custom-toggle {
+ display: flex;
+ flex: auto;
+
+ .gl-button-text {
+ display: flex;
+ @include gl-w-full;
+ }
+}
+
+.group-namespace-dropdown .gl-new-dropdown-item-text-wrapper {
+ word-break: break-word;
+}
diff --git a/app/assets/stylesheets/page_bundles/wiki.scss b/app/assets/stylesheets/page_bundles/wiki.scss
index 2c47c01b89a..6d85a4da035 100644
--- a/app/assets/stylesheets/page_bundles/wiki.scss
+++ b/app/assets/stylesheets/page_bundles/wiki.scss
@@ -101,7 +101,7 @@
}
.active > .wiki-list {
- background-color: $gray-50;
+ background-color: var(--gray-50, $gray-50);
}
.wiki-list {
@@ -110,7 +110,7 @@
@include gl-rounded-base;
&:hover {
- background: $gray-50;
+ background: var(--gray-50, $gray-50);
.wiki-list-create-child-button {
display: block;
diff --git a/app/controllers/projects/settings/packages_and_registries_controller.rb b/app/controllers/projects/settings/packages_and_registries_controller.rb
index fd4dbdab95f..5c352866c8d 100644
--- a/app/controllers/projects/settings/packages_and_registries_controller.rb
+++ b/app/controllers/projects/settings/packages_and_registries_controller.rb
@@ -7,12 +7,12 @@ module Projects
before_action :authorize_admin_project!
before_action :packages_and_registries_settings_enabled!
+ before_action :set_feature_flag_packages_protected_packages, only: :show
feature_category :package_registry
urgency :low
def show
- push_frontend_feature_flag(:packages_protected_packages, project)
end
def cleanup_tags
@@ -31,6 +31,10 @@ module Projects
render_404 unless Gitlab.config.registry.enabled &&
can?(current_user, :admin_container_image, project)
end
+
+ def set_feature_flag_packages_protected_packages
+ push_frontend_feature_flag(:packages_protected_packages, project)
+ end
end
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 55857d40698..96ae7be5fdc 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -37,7 +37,7 @@ module GroupsHelper
group.try(:avatar_url) || ActionController::Base.helpers.image_path('no_group_avatar.png')
end
- def group_title(group, name = nil, url = nil)
+ def group_title(group)
@has_group_title = true
full_title = []
@@ -56,11 +56,6 @@ module GroupsHelper
full_title << breadcrumb_list_item(group_title_link(group))
push_to_schema_breadcrumb(simple_sanitize(group.name), group_path(group))
- if name
- full_title << ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path js-breadcrumb-item-text')
- push_to_schema_breadcrumb(simple_sanitize(name), url)
- end
-
full_title.join.html_safe
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index cb8c86a7d29..add01c28d60 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -797,7 +797,7 @@ module ProjectsHelper
def build_namespace_breadcrumb_link(project)
if project.group
- group_title(project.group, nil, nil)
+ group_title(project.group)
else
owner = project.namespace.owner
name = simple_sanitize(owner.name)
diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb
index 15156e1deef..22bbc563272 100644
--- a/app/workers/concerns/gitlab/github_import/object_importer.rb
+++ b/app/workers/concerns/gitlab/github_import/object_importer.rb
@@ -61,6 +61,10 @@ module Gitlab
rescue ActiveRecord::RecordInvalid, NotRetriableError, NoMethodError => e
# We do not raise exception to prevent job retry
track_exception(project, e)
+ rescue UserFinder::FailedToObtainLockError
+ warn(project.id, message: 'Failed to obtaing lock for user finder. Retrying later.')
+
+ raise
rescue StandardError => e
track_and_raise_exception(project, e)
end
@@ -92,6 +96,10 @@ module Gitlab
Logger.info(log_attributes(project_id, extra))
end
+ def warn(project_id, extra = {})
+ Logger.warn(log_attributes(project_id, extra))
+ end
+
def log_attributes(project_id, extra = {})
extra.merge(
project_id: project_id,
diff --git a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
index e2808f45821..61c5aff6592 100644
--- a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb
@@ -38,7 +38,7 @@ module Gitlab
def try_import(...)
import(...)
true
- rescue RateLimitError
+ rescue RateLimitError, UserFinder::FailedToObtainLockError
false
end
diff --git a/app/workers/concerns/gitlab/github_import/stage_methods.rb b/app/workers/concerns/gitlab/github_import/stage_methods.rb
index 5f6812ab84f..e5c3a9a5d2f 100644
--- a/app/workers/concerns/gitlab/github_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/stage_methods.rb
@@ -85,7 +85,7 @@ module Gitlab
RefreshImportJidWorker.perform_in_the_future(project.id, jid)
import(client, project)
- rescue RateLimitError
+ rescue RateLimitError, UserFinder::FailedToObtainLockError
self.class.perform_in(client.rate_limit_resets_in, project.id)
end
diff --git a/data/deprecations/16-8-api-lint-ref-removal.yml b/data/deprecations/16-8-api-lint-ref-removal.yml
new file mode 100644
index 00000000000..cd99938a607
--- /dev/null
+++ b/data/deprecations/16-8-api-lint-ref-removal.yml
@@ -0,0 +1,34 @@
+# ----- DELETE EVERYTHING ABOVE THIS LINE -----
+
+- title: "Block usage of ref and sha together in `GET /projects/:id/ci/lint`"
+ # The milestones for the deprecation announcement, and the removal.
+ removal_milestone: "17.0"
+ announcement_milestone: "16.8"
+ # Change breaking_change to false if needed.
+ breaking_change: true
+ # The stage and GitLab username of the person reporting the change,
+ # and a link to the deprecation issue
+ reporter: dhershkovitch
+ stage: verify
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430322
+ body: | # (required) Don't change this line.
+ Due to a problem with ambiguity, we've deprecated the use of both `ref` and `sha` in the same API call to `GET /projects/:id/ci/lint`. Make sure your API calls to this endpoint use only `ref` or `sha`, but not both. In GitLab 17.0, using them in the same call will no longer be possible to ensure the correct ref or SHA is linted.
+
+# ==============================
+# OPTIONAL END-OF-SUPPORT FIELDS
+# ==============================
+#
+# If an End of Support period applies:
+# 1) Share this announcement in the `#spt_managers` Support channel in Slack
+# 2) Mention `@gitlab-com/support` in this merge request.
+#
+ # When support for this feature ends, in XX.YY milestone format.
+ end_of_support_milestone:
+ # Array of tiers the feature is currently available to,
+ # like [Free, Silver, Gold, Core, Premium, Ultimate]
+ tiers:
+ # Links to documentation and thumbnail image
+ documentation_url:
+ image_url:
+ # Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
+ video_url:
diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md
index d4f9586a9b3..ae6d042b89f 100644
--- a/doc/administration/audit_event_streaming/audit_event_types.md
+++ b/doc/administration/audit_event_streaming/audit_event_types.md
@@ -181,7 +181,7 @@ Audit event types belong to the following product categories.
| [`ci_group_variable_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a group's CI variable is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`ci_group_variable_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a group's CI variable is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`ci_instance_variable_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131882) | When an instance level CI variable is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/8070) |
-| [`ci_instance_variable_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131882) | When an instance level CI varialbe is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/8070) |
+| [`ci_instance_variable_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131882) | When an instance level CI variable is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/8070) |
| [`ci_instance_variable_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131882) | When an instance level CI variable is changed| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/8070) |
| [`ci_variable_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a CI variable is created at a project level| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`ci_variable_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a project's CI variable is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index e856a6a48a8..4c020f4d698 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -24,7 +24,7 @@ For a full list of reference architectures, see
| Load balancer<sup>3</sup> | 1 | 2 vCPU, 1.8 GB memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
| PostgreSQL<sup>1</sup> | 1 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | `m5.large` | `D2s v3` |
| Redis<sup>2</sup> | 1 | 1 vCPU, 3.75 GB memory | `n1-standard-1` | `m5.large` | `D2s v3` |
-| Gitaly | 1 | 4 vCPU, 15 GB memory<sup>5</sup> | `n1-standard-4` | `m5.xlarge` | `D4s v3` |
+| Gitaly<sup>5</sup> | 1 | 4 vCPU, 15 GB memory<sup>5</sup> | `n1-standard-4` | `m5.xlarge` | `D4s v3` |
| Sidekiq<sup>6</sup> | 1 | 4 vCPU, 15 GB memory | `n1-standard-4` | `m5.xlarge` | `D4s v3` |
| GitLab Rails<sup>6</sup> | 2 | 8 vCPU, 7.2 GB memory | `n1-highcpu-8` | `c5.2xlarge` | `F8s v2` |
| Monitoring node | 1 | 2 vCPU, 1.8 GB memory | `n1-highcpu-2` | `c5.large` | `F2s v2` |
@@ -35,7 +35,6 @@ For a full list of reference architectures, see
2. Can be optionally run on reputable third-party external PaaS Redis solutions. See [Provide your own Redis instance](#provide-your-own-redis-instance) and [Recommended cloud providers and services](index.md#recommended-cloud-providers-and-services) for more information.
3. Can be optionally run on reputable third-party load balancing services (LB PaaS). See [Recommended cloud providers and services](index.md#recommended-cloud-providers-and-services) for more information.
4. Should be run on reputable Cloud Provider or Self Managed solutions. See [Configure the object storage](#configure-the-object-storage) for more information.
-4. Should be run on reputable Cloud Provider or Self Managed solutions. More information can be found in the [Configure the object storage](#configure-the-object-storage) section.
5. Gitaly specifications are based on the use of normal-sized repositories in good health.
However, if you have large monorepos (larger than several gigabytes) this can **significantly** impact Git and Gitaly performance and an increase of specifications will likely be required.
Refer to [large monorepos](index.md#large-monorepos) for more information.
@@ -52,7 +51,7 @@ skinparam linetype ortho
card "**External Load Balancer**" as elb #6a9be7
-collections "**GitLab Rails** x3" as gitlab #32CD32
+collections "**GitLab Rails** x2" as gitlab #32CD32
card "**Prometheus**" as monitor #7FFFD4
card "**Gitaly**" as gitaly #FF8C00
card "**PostgreSQL**" as postgres #4EA7FF
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 27f6113345f..5f0e379cce5 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -2511,7 +2511,8 @@ image:
#### `image:docker`
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+> - `user` input option [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137907) in GitLab 16.8.
Use `image:docker` to pass options to the Docker executor of a GitLab Runner.
@@ -2524,6 +2525,7 @@ A hash of options for the Docker executor, which can include:
- `platform`: Selects the architecture of the image to pull. When not specified,
the default is the same platform as the host runner.
+- `user`: Specify the username or UID to use when running the container.
**Example of `image:docker`**:
@@ -2534,11 +2536,13 @@ arm-sql-job:
name: super/sql:experimental
docker:
platform: arm64/v8
+ user: dave
```
**Additional details**:
- `image:docker:platform` maps to the [`docker pull --platform` option](https://docs.docker.com/engine/reference/commandline/pull/#options).
+- `image:docker:user` maps to the [`docker run --user` option](https://docs.docker.com/engine/reference/commandline/run/#options).
#### `image:pull_policy`
@@ -4428,7 +4432,8 @@ In this example, GitLab launches two containers for the job:
#### `services:docker`
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27919) in GitLab 16.7. Requires GitLab Runner 16.7 or later.
+> - `user` input option [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137907) in GitLab 16.8.
Use `services:docker` to pass options to the Docker executor of a GitLab Runner.
@@ -4441,6 +4446,7 @@ A hash of options for the Docker executor, which can include:
- `platform`: Selects the architecture of the image to pull. When not specified,
the default is the same platform as the host runner.
+- `user`: Specify the username or UID to use when running the container.
**Example of `services:docker`**:
@@ -4452,11 +4458,13 @@ arm-sql-job:
- name: super/sql:experimental
docker:
platform: arm64/v8
+ user: dave
```
**Additional details**:
- `services:docker:platform` maps to the [`docker pull --platform` option](https://docs.docker.com/engine/reference/commandline/pull/#options).
+- `services:docker:user` maps to the [`docker run --user` option](https://docs.docker.com/engine/reference/commandline/run/#options).
#### `services:pull_policy`
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index 66244c2d053..6709c748994 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -285,12 +285,12 @@ See the [`logutil` README](https://gitlab.com/gitlab-org/security-products/analy
The report is a JSON document that combines vulnerabilities with possible remediations.
-This documentation gives an overview of the report JSON format,
-as well as recommendations and examples to help integrators set its fields.
+This documentation gives an overview of the report JSON format, recommendations, and examples to
+help integrators set its fields.
The format is extensively described in the documentation of
[SAST](../../user/application_security/sast/index.md#output),
[DAST](../../user/application_security/dast/proxy-based.md#reports),
-[Dependency Scanning](../../user/application_security/dependency_scanning/index.md#reports-json-format),
+[Dependency Scanning](../../user/application_security/dependency_scanning/index.md#output),
and [Container Scanning](../../user/application_security/container_scanning/index.md#reports-json-format)
You can find the schemas for these scanners here:
diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md
index 8c27351f347..d0a2c8b828f 100644
--- a/doc/development/integrations/secure_partner_integration.md
+++ b/doc/development/integrations/secure_partner_integration.md
@@ -88,7 +88,7 @@ and complete an integration with the Secure stage.
- Your report artifact must be in one of our currently supported formats.
For more information, see the [documentation on reports](secure.md#report).
- Documentation for [SAST output](../../user/application_security/sast/index.md#output).
- - Documentation for [Dependency Scanning reports](../../user/application_security/dependency_scanning/index.md#reports-json-format).
+ - Documentation for [Dependency Scanning reports](../../user/application_security/dependency_scanning/index.md#output).
- Documentation for [Container Scanning reports](../../user/application_security/container_scanning/index.md#reports-json-format).
- See this [example secure job definition that also defines the artifact created](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml).
- If you need a new kind of scan or report, [create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new#)
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index f1a319b863f..0b939cb60b5 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -227,6 +227,20 @@ Because Cloud Native Buildpacks do not support automatic testing, the Auto Test
<div class="deprecation breaking-change" data-milestone="17.0">
+### Block usage of ref and sha together in `GET /projects/:id/ci/lint`
+
+<div class="deprecation-notes">
+- Announced in GitLab <span class="milestone">16.8</span>
+- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/430322).
+</div>
+
+Due to a problem with ambiguity, we've deprecated the use of both `ref` and `sha` in the same API call to `GET /projects/:id/ci/lint`. Make sure your API calls to this endpoint use only `ref` or `sha`, but not both. In GitLab 17.0, using them in the same call will no longer be possible to ensure the correct ref or SHA is linted.
+
+</div>
+
+<div class="deprecation breaking-change" data-milestone="17.0">
+
### Breaking change to the Maven repository group permissions
<div class="deprecation-notes">
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index de66aba57a8..953dc96ce87 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -906,130 +906,38 @@ set the variable `DS_IMAGE_SUFFIX` to `"-fips"`.
Dependency scanning for Gradle projects and auto-remediation for Yarn projects are not supported in FIPS mode.
-## Reports JSON format
+## Output
-The dependency scanning tool emits a JSON report file. For more information, see the
-[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dependency-scanning-report-format.json).
+Dependency Scanning produces the following output:
-Here's an example dependency scanning report:
+- **Dependency scanning report**: Contains details of all vulnerabilities detected in dependencies.
+- **CycloneDX Software Bill of Materials**: Software Bill of Materials (SBOM) for each supported
+ lock or build file detected.
-```json
-{
- "version": "2.0",
- "vulnerabilities": [
- {
- "id": "51e83874-0ff6-4677-a4c5-249060554eae",
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://nodesecurity.io/advisories/534"
- },
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- }
- ]
- },
- {
- "id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment, therefore, has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [
- {
- "fixes": [
- {
- "id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
- }
- ],
- "summary": "Upgrade saml2-js",
- "diff": "ZGlmZiAtLWdpdCBhL...OR0d1ZUc2THh3UT09Cg==" // some content is omitted for brevity
- }
- ]
-}
-```
+### Dependency scanning report
+
+Dependency scanning outputs a report containing details of all vulnerabilities. The report is
+processed internally and the results are shown in the UI. The report is also output as an artifact
+of the dependency scanning job, named `gl-dependency-scanning-report.json`.
+
+For more details of the dependency scanning report, see:
+
+- [Example dependency scanning report](#example-vulnerability-report).
+- [Dependency scanning report schema](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dependency-scanning-report-format.json).
### CycloneDX Software Bill of Materials
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350509) in GitLab 14.8 in [Beta](../../../policy/experiment-beta-support.md#beta).
> - Generally available in GitLab 15.7.
-In addition to the [JSON report file](#reports-json-format), the [Gemnasium](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium)
-Dependency Scanning tool outputs a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM) for
-each supported lock or build file it detects. These CycloneDX SBOMs are named
-`gl-sbom-<package-type>-<package-manager>.cdx.json`, and are saved in the same directory
-as the detected lock or build files.
+Dependency Scanning outputs a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM)
+for each supported lock or build file it detects.
+
+The CycloneDX SBOMs are:
+
+- Named `gl-sbom-<package-type>-<package-manager>.cdx.json`.
+- Available as job artifacts of the dependency scanning job.
+- Saved in the same directory as the detected lock or build files.
For example, if your project has the following structure:
@@ -1063,12 +971,16 @@ Then the Gemnasium scanner generates the following CycloneDX SBOMs:
└── gl-sbom-go-go.cdx.json
```
-You can download CycloneDX SBOMs [the same way as other job artifacts](../../../ci/jobs/job_artifacts.md#download-job-artifacts).
+#### Merging multiple CycloneDX SBOMs
-### Merging multiple CycloneDX SBOMs
+You can use a CI/CD job to merge the multiple CycloneDX SBOMs into a single SBOM. GitLab uses
+[CycloneDX Properties](https://cyclonedx.org/use-cases/#properties--name-value-store) to store
+implementation-specific details in the metadata of each CycloneDX SBOM, such as the location of
+build and lock files. If multiple CycloneDX SBOMs are merged together, this information is removed
+from the resulting merged file.
-You can use a CI/CD job to merge multiple CycloneDX SBOMs into a single SBOM.
-For example:
+For example, the following `.gitlab-ci.yml` extract demonstrates how the Cyclone SBOM files can be
+merged, and the resulting file validated.
```yaml
stages:
@@ -1110,11 +1022,6 @@ merge cyclonedx sboms:
- gl-sbom-all.cdx.json
```
-GitLab uses [CycloneDX Properties](https://cyclonedx.org/use-cases/#properties--name-value-store)
-to store implementation-specific details in the metadata of each CycloneDX SBOM,
-such as the location of build and lock files. If multiple CycloneDX SBOMs are merged together,
-this information is removed from the resulting merged file.
-
## Versioning and release process
Check the [Release Process documentation](../../../development/sec/analyzer_development_guide.md#versioning-and-release-process).
@@ -1332,3 +1239,114 @@ environment variable due to a possible exploit documented by [CVE-2018-20225](ht
intended to obtain a private package from a private index. This only affects use of the `PIP_EXTRA_INDEX_URL` option, and exploitation
requires that the package does not already exist in the public index (and thus the attacker can put the package there with an arbitrary
version number).
+
+## Example vulnerability report
+
+The following is an example vulnerability report output by dependency scanning:
+
+```json
+{
+ "version": "2.0",
+ "vulnerabilities": [
+ {
+ "id": "51e83874-0ff6-4677-a4c5-249060554eae",
+ "category": "dependency_scanning",
+ "name": "Regular Expression Denial of Service",
+ "message": "Regular Expression Denial of Service in debug",
+ "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest versions.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://nodesecurity.io/advisories/534"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/issues/501"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/pull/504"
+ }
+ ]
+ },
+ {
+ "id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
+ "category": "dependency_scanning",
+ "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
+ "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
+ "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment, therefore, has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
+ "severity": "Unknown",
+ "solution": "Upgrade to fixed version.\r\n",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
+ "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
+ "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-11429",
+ "value": "CVE-2017-11429",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
+ },
+ {
+ "url": "https://github.com/Clever/saml2/issues/127"
+ },
+ {
+ "url": "https://www.kb.cert.org/vuls/id/475445"
+ }
+ ]
+ }
+ ],
+ "remediations": [
+ {
+ "fixes": [
+ {
+ "id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
+ }
+ ],
+ "summary": "Upgrade saml2-js",
+ "diff": "ZGlmZiAtLWdpdCBhL...OR0d1ZUc2THh3UT09Cg==" // some content is omitted for brevity
+ }
+ ]
+}
+```
diff --git a/glfm_specification/output_example_snapshots/html.yml b/glfm_specification/output_example_snapshots/html.yml
index 4992d40d31b..f5cfa95ca52 100644
--- a/glfm_specification/output_example_snapshots/html.yml
+++ b/glfm_specification/output_example_snapshots/html.yml
@@ -7639,7 +7639,7 @@
<task-button></task-button><input type="checkbox" class="task-list-item-checkbox" disabled> incomplete</li>
</ul>
wysiwyg: |-
- <ul dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">incomplete</p></div></li></ul>
+ <ul dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">incomplete</p></div></li></ul>
07_01_00__gitlab_official_specification_markdown__task_list_items__002:
canonical: |
<ul>
@@ -8477,7 +8477,7 @@
<task-button></task-button><input type="checkbox" class="task-list-item-checkbox" disabled> example</li>
</ol>
wysiwyg: |-
- <ol dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">hello</p></div></li><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">world</p></div></li><li dir="auto" data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">example</p></div></li></ol>
+ <ol dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">hello</p></div></li><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">world</p></div></li><li dir="auto" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">example</p></div></li></ol>
08_04_46__gitlab_internal_extension_markdown__migrated_golden_master_examples__reference_for_project_wiki__001:
canonical: |
TODO: Write canonical HTML for this example
@@ -8828,7 +8828,7 @@
<task-button></task-button><input type="checkbox" class="task-list-item-checkbox" checked disabled> bar</li>
</ul>
wysiwyg: |-
- <ul dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">foo</p></div></li><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">bar</p></div></li></ul>
+ <ul dir="auto" start="1" parens="false" bullet="*" data-type="taskList"><li dir="auto" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p dir="auto">foo</p></div></li><li dir="auto" data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p dir="auto">bar</p></div></li></ul>
09_04_00__gfm_undocumented_extensions_and_more_robust_test__task_lists__002:
canonical: |
<ul>
diff --git a/glfm_specification/output_example_snapshots/prosemirror_json.yml b/glfm_specification/output_example_snapshots/prosemirror_json.yml
index 95e7003a202..ba2cbc8322c 100644
--- a/glfm_specification/output_example_snapshots/prosemirror_json.yml
+++ b/glfm_specification/output_example_snapshots/prosemirror_json.yml
@@ -2947,7 +2947,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -3011,7 +3012,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -3229,7 +3231,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -3881,7 +3884,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5244,7 +5248,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5263,7 +5268,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5287,7 +5293,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5306,7 +5313,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5344,7 +5352,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5363,7 +5372,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5387,7 +5397,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5406,7 +5417,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5444,7 +5456,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5468,7 +5481,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5505,7 +5519,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5556,7 +5571,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5575,7 +5591,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5599,7 +5616,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5618,7 +5636,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5673,7 +5692,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5692,7 +5712,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5716,7 +5737,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5735,7 +5757,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5759,7 +5782,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5778,7 +5802,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5834,7 +5859,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5853,7 +5879,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5877,7 +5904,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5896,7 +5924,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5914,7 +5943,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5933,7 +5963,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5971,7 +6002,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -5990,7 +6022,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -20562,7 +20595,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": false
+ "checked": false,
+ "inapplicable": false
},
"content": [
{
@@ -20596,7 +20630,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -21270,7 +21305,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -21292,7 +21328,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -21314,7 +21351,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -23006,7 +23044,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -23023,7 +23062,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
@@ -23040,7 +23080,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": false
+ "checked": false,
+ "inapplicable": false
},
"content": [
{
@@ -23111,7 +23152,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23130,7 +23172,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23154,7 +23197,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23178,7 +23222,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23211,7 +23256,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23235,7 +23281,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23775,7 +23822,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23794,7 +23842,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23818,7 +23867,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23865,7 +23915,8 @@
"attrs": {
"colspan": 1,
"rowspan": 1,
- "colwidth": null
+ "colwidth": null,
+ "align": "left"
},
"content": [
{
@@ -23913,7 +23964,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": false
+ "checked": false,
+ "inapplicable": false
},
"content": [
{
@@ -23930,7 +23982,8 @@
{
"type": "taskItem",
"attrs": {
- "checked": true
+ "checked": true,
+ "inapplicable": false
},
"content": [
{
diff --git a/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json b/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json
index a31374650e6..1098da0111a 100644
--- a/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json
+++ b/lib/gitlab/ci/config/entry/schemas/imageable/executor_opts.json
@@ -10,6 +10,11 @@
"type": "string",
"minLength": 1,
"maxLength": 64
+ },
+ "user": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 255
}
},
"additionalProperties": false
diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb
index 4bf2d8a0aca..925b0c7a019 100644
--- a/lib/gitlab/github_import/user_finder.rb
+++ b/lib/gitlab/github_import/user_finder.rb
@@ -12,21 +12,18 @@ module Gitlab
# Lookups are cached even if no ID was found to remove the need for querying
# the database when most queries are not going to return results anyway.
class UserFinder
+ include Gitlab::ExclusiveLeaseHelpers
+
attr_reader :project, :client
- # The base cache key to use for caching user IDs for a given GitHub user
- # ID.
+ # The base cache key to use for caching user IDs for a given GitHub user ID.
ID_CACHE_KEY = 'github-import/user-finder/user-id/%s'
- # The base cache key to use for caching user IDs for a given GitHub email
- # address.
- ID_FOR_EMAIL_CACHE_KEY =
- 'github-import/user-finder/id-for-email/%s'
+ # The base cache key to use for caching user IDs for a given GitHub email address.
+ ID_FOR_EMAIL_CACHE_KEY = 'github-import/user-finder/id-for-email/%s'
- # The base cache key to use for caching the Email addresses of GitHub
- # usernames.
- EMAIL_FOR_USERNAME_CACHE_KEY =
- 'github-import/user-finder/email-for-username/%s'
+ # The base cache key to use for caching the Email addresses of GitHub usernames.
+ EMAIL_FOR_USERNAME_CACHE_KEY = 'github-import/user-finder/email-for-username/%s'
# The base cache key to use for caching the user ETAG response headers
USERNAME_ETAG_CACHE_KEY = 'github-import/user-finder/user-etag/%s'
@@ -218,6 +215,10 @@ module Gitlab
private
+ def lease_key
+ "gitlab:github_import:user_finder:#{project.id}"
+ end
+
def read_email_from_cache(username)
Gitlab::Cache::Import::Caching.read(email_cache_key(username))
end
@@ -232,10 +233,20 @@ module Gitlab
end
def fetch_email_from_github(username, etag: nil)
- log(EMAIL_API_CALL_LOGGING_MESSAGE[etag.present?], username: username)
- user = client.user(username, { headers: { 'If-None-Match' => etag }.compact })
+ in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30) do |retried|
+ # when retried, check the cache again as the other process that had the lease may have fetched the email
+ if retried
+ email = read_email_from_cache(username)
- user[:email] || '' if user
+ next email if email
+ end
+
+ log(EMAIL_API_CALL_LOGGING_MESSAGE[etag.present?], username: username)
+
+ # Only make a rate-limited API call if the ETAG is not available })
+ user = client.user(username, { headers: { 'If-None-Match' => etag }.compact })
+ user[:email] || '' if user
+ end
end
def cache_email!(username, email)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 83ac72332a9..7166b5b7461 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4342,15 +4342,15 @@ msgstr ""
msgid "Admin|CI/CD"
msgstr ""
-msgid "Admin|Code Suggestions"
-msgstr ""
-
msgid "Admin|Credentials"
msgstr ""
msgid "Admin|Deploy Keys"
msgstr ""
+msgid "Admin|Duo Pro"
+msgstr ""
+
msgid "Admin|Geo"
msgstr ""
@@ -4822,6 +4822,15 @@ msgstr ""
msgid "Algorithm"
msgstr ""
+msgid "Align column center"
+msgstr ""
+
+msgid "Align column left"
+msgstr ""
+
+msgid "Align column right"
+msgstr ""
+
msgid "All"
msgstr ""
@@ -12071,7 +12080,7 @@ msgstr ""
msgid "CodeSuggestions|%{link_start}What are code suggestions?%{link_end}"
msgstr ""
-msgid "CodeSuggestions|A user can be assigned a Code Suggestion seat only once each billable month."
+msgid "CodeSuggestions|A user can be assigned a Duo Pro seat only once each billable month."
msgstr ""
msgid "CodeSuggestions|Code Suggestions"
@@ -12080,19 +12089,22 @@ msgstr ""
msgid "CodeSuggestions|Code Suggestions add-on"
msgstr ""
-msgid "CodeSuggestions|Code Suggestions seats used"
+msgid "CodeSuggestions|Duo Pro"
msgstr ""
-msgid "CodeSuggestions|Enhance your coding experience with intelligent recommendations. %{linkStart}Code Suggestions%{linkEnd} uses generative AI to suggest code while you're developing."
+msgid "CodeSuggestions|Duo Pro add-on"
msgstr ""
-msgid "CodeSuggestions|Introducing the Code Suggestions add-on"
+msgid "CodeSuggestions|Duo Pro seats used"
msgstr ""
-msgid "CodeSuggestions|Introducing the Code&nbsp;Suggestions add&#8209;on"
+msgid "CodeSuggestions|Enhance your coding experience with intelligent recommendations. %{linkStart}Duo Pro%{linkEnd} offers features that use generative AI to suggest code."
msgstr ""
-msgid "CodeSuggestions|Manage seat assignments for Code Suggestions across your instance."
+msgid "CodeSuggestions|Introducing Duo Pro"
+msgstr ""
+
+msgid "CodeSuggestions|Manage seat assignments for Duo Pro across your instance."
msgstr ""
msgid "CodeSuggestions|Projects in this group can use Code Suggestions"
@@ -23537,6 +23549,9 @@ msgstr ""
msgid "GroupSettings|After the instance reaches the user cap, any user who is added or requests access must be approved by an administrator. Leave empty for an unlimited user cap. If you change the user cap to unlimited, you must re-enable %{project_sharing_docs_link_start}project sharing%{link_end} and %{group_sharing_docs_link_start}group sharing%{link_end}. Increasing the user cap does not automatically approve pending users."
msgstr ""
+msgid "GroupSettings|An experiment is a feature that is in the process of being developed. It is not production-ready. We encourage users to try experimental features and provide feedback. %{link_start}Learn more%{link_end}."
+msgstr ""
+
msgid "GroupSettings|Analytics"
msgstr ""
@@ -26925,6 +26940,9 @@ msgstr ""
msgid "It seems that there is currently no available data for code coverage"
msgstr ""
+msgid "It seems your question relates to GitLab documentation. Unfortunately, this feature is not yet available in this GitLab instance. Your feedback is welcome."
+msgstr ""
+
msgid "It's you"
msgstr ""
@@ -38843,6 +38861,11 @@ msgstr ""
msgid "Projects with write access"
msgstr ""
+msgid "ProjectsNew|%d group or namespace found"
+msgid_plural "ProjectsNew|%d groups or namespaces found"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
msgstr ""
@@ -45693,6 +45716,9 @@ msgstr ""
msgid "Settings|Unable to load the merge request options settings. Try reloading the page."
msgstr ""
+msgid "Settings|What is experiment?"
+msgstr ""
+
msgid "Severity"
msgstr ""
@@ -52544,9 +52570,6 @@ msgstr ""
msgid "UsageQuota|Buy storage"
msgstr ""
-msgid "UsageQuota|Code Suggestions"
-msgstr ""
-
msgid "UsageQuota|Code packages and container images."
msgstr ""
@@ -52568,6 +52591,9 @@ msgstr ""
msgid "UsageQuota|Dependency proxy"
msgstr ""
+msgid "UsageQuota|Duo Pro"
+msgstr ""
+
msgid "UsageQuota|Filter charts by year"
msgstr ""
diff --git a/package.json b/package.json
index 1b279793f27..3fb5f6a5075 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.74.0",
- "@gitlab/ui": "^72.4.0",
+ "@gitlab/ui": "^72.5.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20231211152737",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/qa/qa/page/project/import/repo_by_url.rb b/qa/qa/page/project/import/repo_by_url.rb
index 1e12ec1541d..f767351012c 100644
--- a/qa/qa/page/project/import/repo_by_url.rb
+++ b/qa/qa/page/project/import/repo_by_url.rb
@@ -7,7 +7,6 @@ module QA
class RepoByURL < Page::Base
view 'app/assets/javascripts/projects/new/components/new_project_url_select.vue' do
element 'select-namespace-dropdown'
- element 'select-namespace-dropdown-search-field'
end
view 'app/views/projects/_new_project_fields.html.haml' do
@@ -40,7 +39,7 @@ module QA
def choose_namespace(namespace)
retry_on_exception do
click_element 'select-namespace-dropdown'
- fill_element 'select-namespace-dropdown-search-field', namespace
+ fill_element '.gl-listbox-search-input', namespace
click_button namespace
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index d61f9cf0218..3019557f78b 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -29,7 +29,6 @@ module QA
view 'app/assets/javascripts/projects/new/components/new_project_url_select.vue' do
element 'select-namespace-dropdown'
- element 'select-namespace-dropdown-search-field'
end
view 'app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue' do
@@ -53,8 +52,8 @@ module QA
return if find_element('select-namespace-dropdown').text.end_with?(namespace)
click_element 'select-namespace-dropdown'
- fill_element 'select-namespace-dropdown-search-field', namespace
- select_item(namespace, css: '.gl-dropdown-item')
+ fill_element '.gl-listbox-search-input', namespace
+ select_item(namespace, css: '.gl-new-dropdown-item')
end
def click_import_project
diff --git a/scripts/setup/generate-as-if-foss-env.rb b/scripts/setup/generate-as-if-foss-env.rb
index 9e93b470558..21f1822503c 100755
--- a/scripts/setup/generate-as-if-foss-env.rb
+++ b/scripts/setup/generate-as-if-foss-env.rb
@@ -6,10 +6,26 @@ require 'gitlab' unless Object.const_defined?(:Gitlab)
require 'set' # rubocop:disable Lint/RedundantRequireStatement -- Ruby 3.1 and earlier needs this. Drop this line after Ruby 3.2+ is only supported.
class GenerateAsIfFossEnv
+ FOSS_JOBS = Set.new(%w[
+ build-assets-image
+ build-qa-image
+ compile-production-assets
+ compile-storybook
+ compile-test-assets
+ eslint
+ generate-apollo-graphql-schema
+ graphql-schema-dump
+ jest
+ jest-integration
+ qa:internal
+ qa:selectors
+ static-analysis
+ ]).freeze
+
def initialize
@client = Gitlab.client(endpoint: ENV['CI_API_V4_URL'], private_token: '')
@rspec_jobs = Set.new
- @jest_jobs = Set.new
+ @other_jobs = Set.new
end
def variables
@@ -24,7 +40,7 @@ class GenerateAsIfFossEnv
private
- attr_reader :client, :rspec_jobs, :jest_jobs
+ attr_reader :client, :rspec_jobs, :other_jobs
def generate_variables
scan_jobs
@@ -32,12 +48,12 @@ class GenerateAsIfFossEnv
{
START_AS_IF_FOSS: 'true',
RUBY_VERSION: ENV['RUBY_VERSION']
- }.merge(rspec_variables).merge(jest_variables)
+ }.merge(rspec_variables).merge(other_jobs_variables)
end
def scan_jobs
each_job do |job|
- detect_rspec(job) || detect_jest(job)
+ detect_rspec(job) || detect_other_jobs(job)
end
end
@@ -48,32 +64,32 @@ class GenerateAsIfFossEnv
end
def detect_rspec(job)
- rspec_type = job.name[/^rspec ([\w\-]+)/, 1]
+ rspec_type = job.name[/^rspec(?:-all)? ([\w\-]+)/, 1]
rspec_jobs << rspec_type if rspec_type
end
- def detect_jest(job)
- jest_type = job.name[/^jest([\w\-]*)/, 1]
-
- jest_jobs << jest_type if jest_type
+ def detect_other_jobs(job)
+ other_jobs << job.name if FOSS_JOBS.member?(job.name)
end
def rspec_variables
return {} if rspec_jobs.empty?
rspec_jobs.inject({ ENABLE_RSPEC: 'true' }) do |result, rspec|
- result.merge("ENABLE_RSPEC_#{rspec.upcase.tr('-', '_')}": 'true')
+ result.merge("ENABLE_RSPEC_#{job_name_to_variable_name(rspec)}": 'true')
end
end
- def jest_variables
- return {} if jest_jobs.empty?
-
- jest_jobs.inject({ ENABLE_JEST: 'true' }) do |result, jest|
- result.merge("ENABLE_JEST#{jest.upcase.tr('-', '_')}": 'true')
+ def other_jobs_variables
+ other_jobs.inject({}) do |result, job_name|
+ result.merge("ENABLE_#{job_name_to_variable_name(job_name)}": 'true')
end
end
+
+ def job_name_to_variable_name(name)
+ name.upcase.tr('-: ', '_')
+ end
end
GenerateAsIfFossEnv.new.display if $PROGRAM_NAME == __FILE__
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index a0df18ea6f3..d6b27d8c618 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
+ include ListboxHelpers
+
before do
stub_application_setting(import_sources: Gitlab::ImportSources.values)
end
@@ -311,7 +313,7 @@ RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
it 'does not select the user namespace' do
click_on 'Pick a group or namespace'
- expect(page).to have_button user.username
+ expect_listbox_item(user.username)
end
end
@@ -359,28 +361,28 @@ RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
it 'enables the correct visibility options' do
click_button public_group.full_path
- click_button user.username
+ select_listbox_item user.username
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
click_button user.username
- click_button public_group.full_path
+ select_listbox_item public_group.full_path
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
click_button public_group.full_path
- click_button internal_group.full_path
+ select_listbox_item internal_group.full_path
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
click_button internal_group.full_path
- click_button private_group.full_path
+ select_listbox_item private_group.full_path
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
@@ -467,7 +469,7 @@ RSpec.describe 'New project', :js, feature_category: :groups_and_projects do
)
click_on 'Pick a group or namespace'
- click_on user.username
+ select_listbox_item user.username
click_on 'Create project'
expect(page).to have_css('#import-project-pane.active')
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index a590d637801..96156f14cfc 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'User creates a project', :js, feature_category: :groups_and_projects do
+ include ListboxHelpers
+
let(:user) { create(:user) }
before do
@@ -110,7 +112,7 @@ RSpec.describe 'User creates a project', :js, feature_category: :groups_and_proj
fill_in :project_path, with: 'a-subgroup-project'
click_on 'Pick a group or namespace'
- click_button subgroup.full_path
+ select_listbox_item subgroup.full_path
click_button('Create project')
diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
index 94628f2b2c5..9f233f2f412 100644
--- a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
@@ -149,6 +149,10 @@ describe('content/components/wrappers/table_cell_base', () => {
},
);
+ it('does not show alignment options for table cells', () => {
+ expect(findDropdown().text()).not.toContain('Align');
+ });
+
describe("when current row is the table's header", () => {
beforeEach(async () => {
// Remove 2 rows condition
@@ -180,6 +184,44 @@ describe('content/components/wrappers/table_cell_base', () => {
});
describe.each`
+ currentAlignment | visibleOptions | newAlignment | command
+ ${'left'} | ${['center', 'right']} | ${'center'} | ${'alignColumnCenter'}
+ ${'center'} | ${['left', 'right']} | ${'right'} | ${'alignColumnRight'}
+ ${'right'} | ${['left', 'center']} | ${'left'} | ${'alignColumnLeft'}
+ `(
+ 'when align=$currentAlignment',
+ ({ currentAlignment, visibleOptions, newAlignment, command }) => {
+ beforeEach(async () => {
+ Object.assign(node.attrs, { align: currentAlignment });
+
+ createWrapper({ cellType: 'th' });
+
+ await nextTick();
+ });
+
+ visibleOptions.forEach((alignment) => {
+ it(`shows "Align column ${alignment}" option`, () => {
+ expect(findDropdown().text()).toContain(`Align column ${alignment}`);
+ });
+ });
+
+ it(`does not show "Align column ${currentAlignment}" option`, () => {
+ expect(findDropdown().text()).not.toContain(`Align column ${currentAlignment}`);
+ });
+
+ it('allows changing alignment', async () => {
+ const mocks = mockChainedCommands(editor, [command, 'run']);
+
+ await wrapper
+ .findByRole('button', { name: `Align column ${newAlignment}` })
+ .trigger('click');
+
+ expect(mocks[command]).toHaveBeenCalled();
+ });
+ },
+ );
+
+ describe.each`
attrs | rect
${{ rowspan: 2 }} | ${{ top: 0, left: 0, bottom: 2, right: 1 }}
${{ colspan: 2 }} | ${{ top: 0, left: 0, bottom: 1, right: 2 }}
diff --git a/spec/frontend/content_editor/extensions/task_item_spec.js b/spec/frontend/content_editor/extensions/task_item_spec.js
new file mode 100644
index 00000000000..a38a68112cd
--- /dev/null
+++ b/spec/frontend/content_editor/extensions/task_item_spec.js
@@ -0,0 +1,115 @@
+import TaskList from '~/content_editor/extensions/task_list';
+import TaskItem from '~/content_editor/extensions/task_item';
+import { createTestEditor, createDocBuilder } from '../test_utils';
+
+describe('content_editor/extensions/task_item', () => {
+ let tiptapEditor;
+ let doc;
+ let p;
+ let taskList;
+ let taskItem;
+
+ beforeEach(() => {
+ tiptapEditor = createTestEditor({ extensions: [TaskList, TaskItem] });
+
+ ({
+ builders: { doc, p, taskList, taskItem },
+ } = createDocBuilder({
+ tiptapEditor,
+ names: {
+ taskItem: { nodeType: TaskItem.name },
+ taskList: { nodeType: TaskList.name },
+ },
+ }));
+ });
+
+ it('renders a regular task item for non-inapplicable items', () => {
+ const initialDoc = doc(taskList(taskItem(p('foo'))));
+
+ tiptapEditor.commands.setContent(initialDoc.toJSON());
+
+ expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
+ <li
+ data-checked="false"
+ dir="auto"
+ >
+ <label>
+ <input
+ type="checkbox"
+ />
+ <span />
+ </label>
+ <div>
+ <p
+ dir="auto"
+ >
+ foo
+ </p>
+ </div>
+ </li>
+ `);
+ });
+
+ it('renders task item as disabled if it is inapplicable', () => {
+ const initialDoc = doc(taskList(taskItem({ inapplicable: true }, p('foo'))));
+
+ tiptapEditor.commands.setContent(initialDoc.toJSON());
+
+ expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
+ <li
+ data-checked="false"
+ data-inapplicable="true"
+ dir="auto"
+ >
+ <label>
+ <input
+ disabled=""
+ type="checkbox"
+ />
+ <span />
+ </label>
+ <div>
+ <p
+ dir="auto"
+ >
+ foo
+ </p>
+ </div>
+ </li>
+ `);
+ });
+
+ it('ignores any <s> tags in the task item', () => {
+ tiptapEditor.commands.setContent(`
+ <ul dir="auto" class="task-list">
+ <li class="task-list-item inapplicable">
+ <input disabled="" data-inapplicable="" class="task-list-item-checkbox" type="checkbox">
+ <s>foo</s>
+ </li>
+ </ul>
+ `);
+
+ expect(tiptapEditor.view.dom.querySelector('li')).toMatchInlineSnapshot(`
+ <li
+ data-checked="false"
+ data-inapplicable="true"
+ dir="auto"
+ >
+ <label>
+ <input
+ disabled=""
+ type="checkbox"
+ />
+ <span />
+ </label>
+ <div>
+ <p
+ dir="auto"
+ >
+ foo
+ </p>
+ </div>
+ </li>
+ `);
+ });
+});
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index c329a12bcc4..4ae39f7a5a7 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -660,6 +660,24 @@ var a = 0;
);
});
+ it('correctly serializes a task list with inapplicable items', () => {
+ expect(
+ serialize(
+ taskList(
+ taskItem({ checked: true }, paragraph('list item 1')),
+ taskItem({ checked: true, inapplicable: true }, paragraph('list item 2')),
+ taskItem(paragraph('list item 3')),
+ ),
+ ),
+ ).toBe(
+ `
+* [x] list item 1
+* [~] list item 2
+* [ ] list item 3
+ `.trim(),
+ );
+ });
+
it('correctly serializes bullet task list with different bullet styles', () => {
expect(
serialize(
@@ -1080,6 +1098,38 @@ _An elephant at sunset_
);
});
+ it('correctly serializes a table with inline content with alignment', () => {
+ expect(
+ serialize(
+ table(
+ // each table cell must contain at least one paragraph
+ tableRow(
+ tableHeader({ align: 'center' }, paragraph('header')),
+ tableHeader({ align: 'right' }, paragraph('header')),
+ tableHeader({ align: 'left' }, paragraph('header')),
+ ),
+ tableRow(
+ tableCell(paragraph('cell')),
+ tableCell(paragraph('cell')),
+ tableCell(paragraph('cell')),
+ ),
+ tableRow(
+ tableCell(paragraph('cell')),
+ tableCell(paragraph('cell')),
+ tableCell(paragraph('cell')),
+ ),
+ ),
+ ).trim(),
+ ).toBe(
+ `
+| header | header | header |
+|:------:|-------:|--------|
+| cell | cell | cell |
+| cell | cell | cell |
+ `.trim(),
+ );
+ });
+
it('correctly serializes a table with a pipe in a cell', () => {
expect(
serialize(
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/image.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/image.yml
index ad37cd6c3ba..d6bc3cccf41 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/image.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/image.yml
@@ -26,6 +26,17 @@ invalid_image_platform:
docker:
platform: ["arm64"] # The expected value is a string, not an array
+invalid_image_user:
+ image:
+ name: alpine:latest
+ docker:
+ user: ["dave"] # The expected value is a string, not an array
+
+empty_image_user:
+ image:
+ name: alpine:latest
+ docker:
+ user: ""
invalid_image_executor_opts:
image:
name: alpine:latest
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
index e14ac9ca86e..fd05d2606e5 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
@@ -50,3 +50,17 @@ invalid_service_platform:
- name: mysql:5.7
docker:
platform: ["arm64"] # The expected value is a string, not an array
+
+invalid_service_user:
+ script: echo "Specifying user."
+ services:
+ - name: mysql:5.7
+ docker:
+ user: ["dave"] # The expected value is a string, not an array
+
+empty_service_user:
+ script: echo "Specifying user"
+ services:
+ - name: alpine:latest
+ docker:
+ user: ""
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/image.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/image.yml
index 4c2559d0800..020cce80fd3 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/image.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/image.yml
@@ -30,6 +30,19 @@ valid_image_with_docker:
docker:
platform: linux/amd64
+valid_image_with_docker_user:
+ image:
+ name: ubuntu:latest
+ docker:
+ user: ubuntu
+
+valid_image_with_docker_multiple_options:
+ image:
+ name: ubuntu:latest
+ docker:
+ platform: linux/arm64
+ user: ubuntu
+
valid_image_full:
image:
name: alpine:latest
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
index 1d19ee52cc3..0f45b075f53 100644
--- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
+++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
@@ -36,3 +36,18 @@ services_platform_string:
- name: mysql:5.7
docker:
platform: arm64
+
+services_with_docker_user:
+ script: echo "Specifying platform."
+ services:
+ - name: mysql:5.7
+ docker:
+ user: ubuntu
+
+services_with_docker_multiple_options:
+ script: echo "Specifying platform."
+ services:
+ - name: mysql:5.7
+ docker:
+ platform: linux/arm64
+ user: ubuntu
diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js
index ceac4435282..be923c1f643 100644
--- a/spec/frontend/projects/new/components/new_project_url_select_spec.js
+++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js
@@ -1,13 +1,12 @@
import {
GlButton,
- GlDropdown,
- GlDropdownItem,
- GlDropdownSectionHeader,
+ GlCollapsibleListbox,
+ GlListboxItem,
GlTruncate,
GlSearchBoxByType,
} from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -99,16 +98,17 @@ describe('NewProjectUrlSelect component', () => {
};
const findButtonLabel = () => wrapper.findComponent(GlButton);
- const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
const findSelectedPath = () => wrapper.findComponent(GlTruncate);
const findHiddenNamespaceInput = () => wrapper.find(`[name="${defaultProvide.inputName}`);
+ const findAllListboxItems = () => wrapper.findAllComponents(GlListboxItem);
+ const findToggleButton = () => findDropdown().findComponent(GlButton);
const findHiddenSelectedNamespaceInput = () =>
wrapper.find('[name="project[selected_namespace_id]"]');
const clickDropdownItem = async () => {
- wrapper.findComponent(GlDropdownItem).vm.$emit('click');
- await nextTick();
+ await findAllListboxItems().at(0).trigger('click');
};
const showDropdown = async () => {
@@ -135,7 +135,7 @@ describe('NewProjectUrlSelect component', () => {
});
it('renders a dropdown without the class', () => {
- expect(findDropdown().props('toggleClass')).not.toContain('gl-text-gray-500!');
+ expect(findToggleButton().classes()).not.toContain('gl-text-gray-500!');
});
it('renders a hidden input with the given namespace id', () => {
@@ -165,7 +165,7 @@ describe('NewProjectUrlSelect component', () => {
});
it('renders a dropdown with the class', () => {
- expect(findDropdown().props('toggleClass')).toContain('gl-text-gray-500!');
+ expect(findToggleButton().classes()).toContain('gl-text-gray-500!');
});
it("renders a hidden input with the user's namespace id", () => {
@@ -179,28 +179,22 @@ describe('NewProjectUrlSelect component', () => {
});
});
- it('focuses on the input when the dropdown is opened', async () => {
- wrapper = mountComponent();
-
- await showDropdown();
-
- expect(focusInputSpy).toHaveBeenCalledTimes(1);
- });
-
it('renders expected dropdown items', async () => {
wrapper = mountComponent({ mountFn: mount });
await showDropdown();
- const listItems = wrapper.findAll('li');
-
- expect(listItems).toHaveLength(6);
- expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
- expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[0].fullPath);
- expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
- expect(listItems.at(3).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
- expect(listItems.at(4).findComponent(GlDropdownSectionHeader).text()).toBe('Users');
- expect(listItems.at(5).text()).toBe(data.currentUser.namespace.fullPath);
+ const { fullPath: text, id: value } = data.currentUser.namespace;
+ const userOptions = [{ text, value }];
+ const groupOptions = data.currentUser.groups.nodes.map((node) => ({
+ text: node.fullPath,
+ value: node.id,
+ }));
+
+ expect(findDropdown().props('items')).toEqual([
+ { text: 'Groups', options: groupOptions },
+ { text: 'Users', options: userOptions },
+ ]);
});
it('does not render users section when user namespace id is not provided', async () => {
@@ -211,8 +205,12 @@ describe('NewProjectUrlSelect component', () => {
await showDropdown();
- expect(wrapper.findAllComponents(GlDropdownSectionHeader)).toHaveLength(1);
- expect(wrapper.findAllComponents(GlDropdownSectionHeader).at(0).text()).toBe('Groups');
+ const groupOptions = data.currentUser.groups.nodes.map((node) => ({
+ text: node.fullPath,
+ value: node.id,
+ }));
+
+ expect(findDropdown().props('items')).toEqual([{ text: 'Groups', options: groupOptions }]);
});
describe('query fetching', () => {
@@ -248,12 +246,15 @@ describe('NewProjectUrlSelect component', () => {
});
it('filters the dropdown items to the selected group and children', () => {
- const listItems = wrapper.findAll('li');
+ const filteredgroupOptions = data.currentUser.groups.nodes.filter((group) =>
+ group.fullPath.startsWith(fullPath),
+ );
+ const groupOptions = filteredgroupOptions.map((node) => ({
+ text: node.fullPath,
+ value: node.id,
+ }));
- expect(listItems).toHaveLength(3);
- expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
- expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
- expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
+ expect(findDropdown().props('items')).toEqual([{ text: 'Groups', options: groupOptions }]);
});
it('sets the selection to the group', () => {
@@ -278,7 +279,7 @@ describe('NewProjectUrlSelect component', () => {
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
await waitForPromises();
- expect(wrapper.find('li').text()).toBe('No matches found');
+ expect(wrapper.find('[data-testid="listbox-no-results-text"]').text()).toBe('No matches found');
});
it('emits `update-visibility` event to update the visibility radio options', async () => {
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index f8c0d69be2e..3854437483d 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
let(:pull_policy) { %w[always if-not-present] }
- let(:executor_opts) { { docker: { platform: 'arm64' } } }
+ let(:executor_opts) { { docker: { platform: 'arm64', user: 'dave' } } }
let(:job) do
create(:ci_build, options: { image: { name: image_name,
@@ -101,7 +101,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
- let(:executor_opts) { { docker: { platform: 'amd64' } } }
+ let(:executor_opts) { { docker: { platform: 'amd64', user: 'dave' } } }
let(:pull_policy) { %w[always if-not-present] }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 99a6e25b313..0a82010c20c 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -112,7 +112,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
end
- context "when docker specifies an option" do
+ context "when docker specifies platform" do
let(:config) { { name: 'image:1.0', docker: { platform: 'amd64' } } }
it 'is valid' do
@@ -129,15 +129,73 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
)
end
end
+
+ context "when invalid data type is specified for platform option" do
+ let(:config) { { name: 'image:1.0', docker: { platform: 1 } } }
+
+ it 'raises an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match %r{image executor opts '/docker/platform' must be a valid 'string'}
+ end
+ end
+ end
+
+ context "when docker specifies user" do
+ let(:config) { { name: 'image:1.0', docker: { user: 'dave' } } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: { user: 'dave' }
+ }
+ )
+ end
+ end
+
+ context "when user is a UID" do
+ let(:config) { { name: 'image:1.0', docker: { user: '1001' } } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: { user: '1001' }
+ }
+ )
+ end
+ end
+ end
+
+ context "when invalid data type is specified for user option" do
+ let(:config) { { name: 'image:1.0', docker: { user: 1 } } }
+
+ it 'raises an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match %r{image executor opts '/docker/user' must be a valid 'string'}
+ end
+ end
end
context "when docker specifies an invalid option" do
- let(:config) { { name: 'image:1.0', docker: { platform: 1 } } }
+ let(:config) { { name: 'image:1.0', docker: { unknown_key: 'foo' } } }
it 'is not valid' do
expect(entry).not_to be_valid
expect(entry.errors.first)
- .to match %r{image executor opts '/docker/platform' must be a valid 'string'}
+ .to match %r{image executor opts '/docker/unknown_key' must be a valid 'schema'}
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 82747e7b521..8ce0f890b46 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -154,22 +154,45 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
end
context 'when configuration has docker options' do
- let(:config) { { name: 'postgresql:9.5', docker: { platform: 'amd64' } } }
+ context "with platform option" do
+ let(:config) { { name: 'postgresql:9.5', docker: { platform: 'amd64' } } }
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5',
+ executor_opts: {
+ docker: { platform: 'amd64' }
+ }
+ )
+ end
end
end
- describe '#value' do
- it "returns value" do
- expect(entry.value).to eq(
- name: 'postgresql:9.5',
- executor_opts: {
- docker: { platform: 'amd64' }
- }
- )
+ context "with user option" do
+ let(:config) { { name: 'postgresql:9.5', docker: { user: 'dave' } } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5',
+ executor_opts: {
+ docker: { user: 'dave' }
+ }
+ )
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 844a6849c8f..0d91b99b5e3 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1295,10 +1295,12 @@ module Gitlab
name: ruby:2.7
docker:
platform: linux/amd64
+ user: dave
services:
- name: postgres:11.9
docker:
platform: linux/amd64
+ user: john
YAML
end
@@ -1313,9 +1315,9 @@ module Gitlab
options: {
script: ["exit 0"],
image: { name: "ruby:2.7",
- executor_opts: { docker: { platform: 'linux/amd64' } } },
+ executor_opts: { docker: { platform: 'linux/amd64', user: 'dave' } } },
services: [{ name: "postgres:11.9",
- executor_opts: { docker: { platform: 'linux/amd64' } } }]
+ executor_opts: { docker: { platform: 'linux/amd64', user: 'john' } } }]
},
allow_failure: false,
when: "on_success",
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index a394b4eba13..31118c798e4 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -211,6 +211,7 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
let(:username) { 'kittens' }
let(:user) { {} }
let(:etag) { 'etag' }
+ let(:lease_name) { "gitlab:github_import:user_finder:#{project.id}" }
let(:cache_key) { described_class::EMAIL_FOR_USERNAME_CACHE_KEY % username }
let(:etag_cache_key) { described_class::USERNAME_ETAG_CACHE_KEY % username }
let(:email_fetched_for_project_key) do
@@ -305,6 +306,9 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes an API call' do
expect(client).to receive(:user).with(username, { headers: {} }).and_return({ email: email }).once
+ expect(finder).to receive(:in_lock).with(
+ lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
+ ).and_call_original
email_for_github_username
end
@@ -315,6 +319,14 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
end
it_behaves_like 'returns and caches the email'
+
+ context 'when retried' do
+ before do
+ allow(finder).to receive(:in_lock).and_yield(true)
+ end
+
+ it_behaves_like 'returns and caches the email'
+ end
end
context 'if the response does not contain an email' do
@@ -344,6 +356,9 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes a non-rate-limited API call' do
expect(client).to receive(:user).with(username, { headers: { 'If-None-Match' => etag } }).once
+ expect(finder).to receive(:in_lock).with(
+ lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
+ ).and_call_original
email_for_github_username
end
@@ -413,6 +428,9 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes a non-rate-limited API call' do
expect(client).to receive(:user).with(username, { headers: { 'If-None-Match' => etag } }).once
+ expect(finder).to receive(:in_lock).with(
+ lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
+ ).and_call_original
email_for_github_username
end
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index 3d6d86335eb..e118ef9a384 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -932,7 +932,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
name: 'ruby',
executor_opts: {
docker: {
- platform: 'amd64'
+ platform: 'amd64',
+ user: 'dave'
}
}
}
@@ -948,7 +949,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
'image' => { 'name' => 'ruby',
'executor_opts' => {
'docker' => {
- 'platform' => 'amd64'
+ 'platform' => 'amd64',
+ 'user' => 'dave'
}
},
'pull_policy' => nil,
diff --git a/spec/requests/api/ci/runner/yamls/image-executor_opts-user.yml b/spec/requests/api/ci/runner/yamls/image-executor_opts-user.yml
new file mode 100644
index 00000000000..9fb56b941c9
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/image-executor_opts-user.yml
@@ -0,0 +1,25 @@
+gitlab_ci:
+ rspec:
+ image:
+ name: alpine:latest
+ docker:
+ user: dave
+ script: echo Hello World
+
+request_response:
+ image:
+ name: alpine:latest
+ entrypoint: null
+ executor_opts:
+ docker:
+ user: dave
+ ports: []
+ pull_policy: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services: []
+
diff --git a/spec/requests/api/ci/runner/yamls/service-executor_opts-user.yml b/spec/requests/api/ci/runner/yamls/service-executor_opts-user.yml
new file mode 100644
index 00000000000..ea824110e63
--- /dev/null
+++ b/spec/requests/api/ci/runner/yamls/service-executor_opts-user.yml
@@ -0,0 +1,27 @@
+gitlab_ci:
+ rspec:
+ services:
+ - name: docker:dind
+ docker:
+ user: john
+ script: echo Hello World
+
+request_response:
+ image: null
+ steps:
+ - name: script
+ script: ["echo Hello World"]
+ timeout: 3600
+ when: on_success
+ allow_failure: false
+ services:
+ - name: docker:dind
+ alias: null
+ command: null
+ entrypoint: null
+ executor_opts:
+ docker:
+ user: john
+ ports: []
+ pull_policy: null
+ variables: []
diff --git a/spec/requests/projects/settings/packages_and_registries_controller_spec.rb b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb
index 2806beadd4e..c660be0f3bf 100644
--- a/spec/requests/projects/settings/packages_and_registries_controller_spec.rb
+++ b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb
@@ -16,6 +16,36 @@ RSpec.describe Projects::Settings::PackagesAndRegistriesController, feature_cate
stub_container_registry_config(enabled: container_registry_enabled)
end
+ describe 'GET #show' do
+ context 'when user is authorized' do
+ let(:user) { project.creator }
+
+ subject { get namespace_project_settings_packages_and_registries_path(user.namespace, project) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'pushes the feature flag "packages_protected_packages" to the view' do
+ subject
+
+ expect(response.body).to have_pushed_frontend_feature_flags(packagesProtectedPackages: true)
+ end
+
+ context 'when feature flag "packages_protected_packages" is disabled' do
+ before do
+ stub_feature_flags(packages_protected_packages: false)
+ end
+
+ it 'does not push the feature flag "packages_protected_packages" to the view' do
+ subject
+
+ expect(response.body).not_to have_pushed_frontend_feature_flags(packagesProtectedPackages: true)
+ end
+ end
+ end
+ end
+
describe 'GET #cleanup_tags' do
subject { get cleanup_image_tags_namespace_project_settings_packages_and_registries_path(user.namespace, project) }
diff --git a/spec/scripts/setup/generate_as_if_foss_env_spec.rb b/spec/scripts/setup/generate_as_if_foss_env_spec.rb
index bd4c741ffad..e437ce24e79 100644
--- a/spec/scripts/setup/generate_as_if_foss_env_spec.rb
+++ b/spec/scripts/setup/generate_as_if_foss_env_spec.rb
@@ -21,13 +21,25 @@ RSpec.describe GenerateAsIfFossEnv, feature_category: :tooling do
let(:jobs) do
[
'rspec fast_spec_helper',
- 'rspec unit',
- 'rspec integration',
- 'rspec system',
- 'rspec migration',
- 'rspec background-migration',
+ 'rspec unit pg14',
+ 'rspec integration pg14',
+ 'rspec system pg14',
+ 'rspec migration pg14',
+ 'rspec background-migration pg14',
+ 'rspec-all frontend_fixture',
+ 'build-assets-image',
+ 'build-qa-image',
+ 'compile-production-assets',
+ 'compile-storybook',
+ 'compile-test-assets',
+ 'eslint',
+ 'generate-apollo-graphql-schema',
+ 'graphql-schema-dump',
'jest',
- 'jest-integration'
+ 'jest-integration',
+ 'qa:internal',
+ 'qa:selectors',
+ 'static-analysis'
]
end
@@ -56,8 +68,20 @@ RSpec.describe GenerateAsIfFossEnv, feature_category: :tooling do
ENABLE_RSPEC_SYSTEM: 'true',
ENABLE_RSPEC_MIGRATION: 'true',
ENABLE_RSPEC_BACKGROUND_MIGRATION: 'true',
+ ENABLE_RSPEC_FRONTEND_FIXTURE: 'true',
+ ENABLE_BUILD_ASSETS_IMAGE: 'true',
+ ENABLE_BUILD_QA_IMAGE: 'true',
+ ENABLE_COMPILE_PRODUCTION_ASSETS: 'true',
+ ENABLE_COMPILE_STORYBOOK: 'true',
+ ENABLE_COMPILE_TEST_ASSETS: 'true',
+ ENABLE_ESLINT: 'true',
+ ENABLE_GENERATE_APOLLO_GRAPHQL_SCHEMA: 'true',
+ ENABLE_GRAPHQL_SCHEMA_DUMP: 'true',
ENABLE_JEST: 'true',
- ENABLE_JEST_INTEGRATION: 'true'
+ ENABLE_JEST_INTEGRATION: 'true',
+ ENABLE_QA_INTERNAL: 'true',
+ ENABLE_QA_SELECTORS: 'true',
+ ENABLE_STATIC_ANALYSIS: 'true'
})
end
end
@@ -76,9 +100,53 @@ RSpec.describe GenerateAsIfFossEnv, feature_category: :tooling do
ENABLE_RSPEC_SYSTEM=true
ENABLE_RSPEC_MIGRATION=true
ENABLE_RSPEC_BACKGROUND_MIGRATION=true
+ ENABLE_RSPEC_FRONTEND_FIXTURE=true
+ ENABLE_BUILD_ASSETS_IMAGE=true
+ ENABLE_BUILD_QA_IMAGE=true
+ ENABLE_COMPILE_PRODUCTION_ASSETS=true
+ ENABLE_COMPILE_STORYBOOK=true
+ ENABLE_COMPILE_TEST_ASSETS=true
+ ENABLE_ESLINT=true
+ ENABLE_GENERATE_APOLLO_GRAPHQL_SCHEMA=true
+ ENABLE_GRAPHQL_SCHEMA_DUMP=true
ENABLE_JEST=true
ENABLE_JEST_INTEGRATION=true
+ ENABLE_QA_INTERNAL=true
+ ENABLE_QA_SELECTORS=true
+ ENABLE_STATIC_ANALYSIS=true
ENV
end
end
+
+ describe '.gitlab/ci/rules.gitlab-ci.yml' do
+ include_context 'when there are all jobs'
+
+ let(:rules_yaml) do
+ File.read(File.expand_path('../../../.gitlab/ci/rules.gitlab-ci.yml', __dir__))
+ end
+
+ it 'uses all the ENABLE variables' do
+ generate.variables.each_key do |variable|
+ next unless variable.start_with?('ENABLE_')
+
+ expect(rules_yaml).to include("- if: '$#{variable} == \"true\"'")
+ end
+ end
+ end
+
+ describe '.gitlab/ci/as-if-foss.gitlab-ci.yml' do
+ include_context 'when there are all jobs'
+
+ let(:ci_yaml) do
+ File.read(File.expand_path('../../../.gitlab/ci/as-if-foss.gitlab-ci.yml', __dir__))
+ end
+
+ it 'uses all the ENABLE variables' do
+ generate.variables.each_key do |variable|
+ next unless variable.start_with?('ENABLE_')
+
+ expect(ci_yaml).to include("#{variable}: $#{variable}")
+ end
+ end
+ end
end
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index bba855f5095..f62c08cb7da 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -269,6 +269,33 @@ RSpec.describe Gitlab::GithubImport::ObjectImporter, :aggregate_failures, featur
expect(import_failures.first.external_identifiers).to eq(github_identifiers.with_indifferent_access)
end
end
+
+ context 'when FailedToObtainLockError is raised' do
+ let(:exception) { Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError.new }
+
+ before do
+ expect(importer_class).to receive(:new)
+ .with(instance_of(MockRepresentation), project, client)
+ .and_return(importer_instance)
+
+ expect(importer_instance).to receive(:execute).and_raise(exception)
+ end
+
+ it 'logs the error and raises an exception' do
+ expect(Gitlab::GithubImport::Logger).to receive(:warn).with(
+ {
+ external_identifiers: github_identifiers,
+ message: 'Failed to obtaing lock for user finder. Retrying later.',
+ project_id: project.id,
+ importer: 'klass_name'
+ }
+ )
+
+ expect do
+ worker.import(project, client, { 'number' => 10, 'github_id' => 1 })
+ end.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ end
+ end
end
describe '#increment_object_counter?' do
diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
index c08b85e6161..7b8c4fab0c6 100644
--- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
@@ -100,6 +100,15 @@ RSpec.describe Gitlab::GithubImport::ReschedulingMethods, feature_category: :imp
expect(worker.try_import(10, 20)).to eq(false)
end
+
+ it 'returns false when the import fails due to the FailedToObtainLockError' do
+ expect(worker)
+ .to receive(:import)
+ .with(10, 20)
+ .and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+
+ expect(worker.try_import(10, 20)).to eq(false)
+ end
end
describe '#notify_waiter' do
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index 37e686f9f92..c63c27cd138 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -174,6 +174,19 @@ RSpec.describe Gitlab::GithubImport::StageMethods, feature_category: :importers
worker.try_import(client, project)
end
+
+ it 'reschedules the worker if FailedToObtainLockError was raised' do
+ error = Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ client = double(:client, rate_limit_resets_in: 10)
+
+ expect(Gitlab::GithubImport::RefreshImportJidWorker).to receive(:perform_in_the_future).with(project.id, 'jid')
+
+ expect(worker).to receive(:import).with(client, project).and_raise(error)
+
+ expect(worker.class).to receive(:perform_in).with(10, project.id)
+
+ worker.try_import(client, project)
+ end
end
describe '#find_project' do
diff --git a/yarn.lock b/yarn.lock
index 7ea0b5e25d9..4e1ddb7920c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1199,17 +1199,24 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b"
integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==
-"@floating-ui/core@^1.2.6":
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.6.tgz#d21ace437cc919cdd8f1640302fa8851e65e75c0"
- integrity sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==
+"@floating-ui/core@^1.3.1":
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a"
+ integrity sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==
+ dependencies:
+ "@floating-ui/utils" "^0.2.0"
-"@floating-ui/dom@1.2.9", "@floating-ui/dom@^1.2.9":
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.9.tgz#b9ed1c15d30963419a6736f1b7feb350dd49c603"
- integrity sha512-sosQxsqgxMNkV3C+3UqTS6LxP7isRLwX8WMepp843Rb3/b0Wz8+MdUkxJksByip3C2WwLugLHN1b4ibn//zKwQ==
+"@floating-ui/dom@1.4.3", "@floating-ui/dom@^1.2.9":
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.3.tgz#0854a3297ea03894932381f3ea1403fab3a6e602"
+ integrity sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==
dependencies:
- "@floating-ui/core" "^1.2.6"
+ "@floating-ui/core" "^1.3.1"
+
+"@floating-ui/utils@^0.2.0":
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
+ integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@gitlab/application-sdk-browser@^0.2.11":
version "0.2.11"
@@ -1274,12 +1281,12 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.74.0.tgz#b6b41be65b9e70378c0cef0435f96edd5467e759"
integrity sha512-eHoywPSLrYb+I/IYGapei2Tum5vLtgWkFxN0fxmUUAnBnxFSA+67aheI33kQVV3WjANuZGkglfPBX3QAmN8BLA==
-"@gitlab/ui@^72.4.0":
- version "72.4.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-72.4.0.tgz#035e2aa31c456900d2230edeb66b9eebc78fcb21"
- integrity sha512-6mgMHMEvIZ0jDnP8tDLgBmnrOCNvZ6rTmA68O5Xz9SQNuaTGfwuAc4xTql5fg9k8Ts5Jf9YU8x/IHCCVBAgvAg==
+"@gitlab/ui@^72.5.0":
+ version "72.5.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-72.5.0.tgz#ceb658391d171fac9a74089e01337a569fe15815"
+ integrity sha512-UTEJUMzIt/jRDUmKL4yHBORFpNISffJn2beYIlP1LcYsY3J2ehOh1JaMQtnukgcEL3kIk0ijnTCjygN5Djatmw==
dependencies:
- "@floating-ui/dom" "1.2.9"
+ "@floating-ui/dom" "1.4.3"
bootstrap-vue "2.23.1"
echarts "^5.3.2"
iframe-resizer "^4.3.2"