diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-10 18:16:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-10 18:16:42 +0300 |
commit | e1d966e6543433479a932e1e29ad538cd587699a (patch) | |
tree | 8553431489849d866639ddc17ba873a98df0186d | |
parent | 8731c2348e508e52cad156bd819b0accbf88d495 (diff) |
Add latest changes from gitlab-org/gitlab@master
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(' '); 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 << ' · '.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 Suggestions add‑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" |