diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-05 18:08:40 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-05 18:08:40 +0000 |
commit | 0b789f95a35aa3e4b99ce12fc98bd3cce6555602 (patch) | |
tree | 886c728abbd90836e6846fcf4bb789be243c5253 | |
parent | dad16033c2b7cfd54ffe20ca5cc1d844e9e41be6 (diff) | |
download | gitlab-ce-0b789f95a35aa3e4b99ce12fc98bd3cce6555602.tar.gz |
Add latest changes from gitlab-org/gitlab@master
58 files changed, 587 insertions, 176 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ae9fa84a927..292efacb872 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1f98d5a94c880e3e556ae3ace095f83e44f002fb +86aa7ee82a5dd241fd7d4b33435da0a7ecad12b0 diff --git a/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue b/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue new file mode 100644 index 00000000000..7f6e5dc4f35 --- /dev/null +++ b/app/assets/javascripts/admin/background_migrations/components/database_listbox.vue @@ -0,0 +1,51 @@ +<script> +import { GlListbox } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { setUrlParams, visitUrl } from '~/lib/utils/url_utility'; + +export default { + name: 'BackgroundMigrationsDatabaseListbox', + i18n: { + database: s__('BackgroundMigrations|Database'), + }, + components: { + GlListbox, + }, + props: { + databases: { + type: Array, + required: true, + }, + selectedDatabase: { + type: String, + required: true, + }, + }, + data() { + return { + selected: this.selectedDatabase, + }; + }, + methods: { + selectDatabase(database) { + visitUrl(setUrlParams({ database })); + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center" data-testid="database-listbox"> + <label id="label" class="gl-font-weight-bold gl-mr-4 gl-mb-0">{{ + $options.i18n.database + }}</label> + <gl-listbox + v-model="selected" + :items="databases" + right + :toggle-text="selectedDatabase" + aria-labelledby="label" + @select="selectDatabase" + /> + </div> +</template> diff --git a/app/assets/javascripts/admin/background_migrations/index.js b/app/assets/javascripts/admin/background_migrations/index.js new file mode 100644 index 00000000000..4ddd8f17c9a --- /dev/null +++ b/app/assets/javascripts/admin/background_migrations/index.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import * as Sentry from '@sentry/browser'; +import Translate from '~/vue_shared/translate'; +import BackgroundMigrationsDatabaseListbox from './components/database_listbox.vue'; + +Vue.use(Translate); + +export const initBackgroundMigrationsApp = () => { + const el = document.getElementById('js-database-listbox'); + + if (!el) { + return false; + } + + const { selectedDatabase } = el.dataset; + let { databases } = el.dataset; + + try { + databases = JSON.parse(databases).map((database) => ({ + value: database, + text: database, + })); + } catch (e) { + Sentry.captureException(e); + } + + return new Vue({ + el, + render(createElement) { + return createElement(BackgroundMigrationsDatabaseListbox, { + props: { + databases, + selectedDatabase, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue index 210f259b20f..518ddd7a09c 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/code_block.vue @@ -72,7 +72,7 @@ export default { async applySelectedLanguage(language) { this.selectedLanguage = language; - await codeBlockLanguageLoader.loadLanguages([language.syntax]); + await codeBlockLanguageLoader.loadLanguage(language.syntax); this.tiptapEditor.commands.setCodeBlock({ language: this.selectedLanguage.syntax }); }, diff --git a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue index e8829d00986..1390b9b2daf 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/code_block.vue @@ -1,9 +1,10 @@ <script> import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'; import { __ } from '~/locale'; +import codeBlockLanguageLoader from '../../services/code_block_language_loader'; export default { - name: 'FrontMatter', + name: 'CodeBlock', components: { NodeViewWrapper, NodeViewContent, @@ -13,6 +14,16 @@ export default { type: Object, required: true, }, + updateAttributes: { + type: Function, + required: true, + }, + }, + async mounted() { + const lang = codeBlockLanguageLoader.findLanguageBySyntax(this.node.attrs.language); + await codeBlockLanguageLoader.loadLanguage(lang.syntax); + + this.updateAttributes({ language: this.node.attrs.language }); }, i18n: { frontmatter: __('frontmatter'), @@ -22,6 +33,7 @@ export default { <template> <node-view-wrapper class="content-editor-code-block gl-relative code highlight" as="pre"> <span + v-if="node.attrs.isFrontmatter" data-testid="frontmatter-label" class="gl-absolute gl-top-0 gl-right-3" contenteditable="false" diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index 61f379fc0a2..cc4ba84a29d 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -1,6 +1,8 @@ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'; import { textblockTypeInputRule } from '@tiptap/core'; -import codeBlockLanguageLoader from '../services/code_block_language_loader'; +import { VueNodeViewRenderer } from '@tiptap/vue-2'; +import languageLoader from '../services/code_block_language_loader'; +import CodeBlockWrapper from '../components/wrappers/code_block.vue'; const extractLanguage = (element) => element.getAttribute('lang'); export const backtickInputRegex = /^```([a-z]+)?[\s\n]$/; @@ -9,14 +11,6 @@ export const tildeInputRegex = /^~~~([a-z]+)?[\s\n]$/; export default CodeBlockLowlight.extend({ isolating: true, exitOnArrowDown: false, - - addOptions() { - return { - ...this.parent?.(), - languageLoader: codeBlockLanguageLoader, - }; - }, - addAttributes() { return { language: { @@ -30,7 +24,6 @@ export default CodeBlockLowlight.extend({ }; }, addInputRules() { - const { languageLoader } = this.options; const getAttributes = (match) => languageLoader?.loadLanguageFromInputRule(match) || {}; return [ @@ -65,4 +58,8 @@ export default CodeBlockLowlight.extend({ ['code', {}, 0], ]; }, + + addNodeView() { + return new VueNodeViewRenderer(CodeBlockWrapper); + }, }); diff --git a/app/assets/javascripts/content_editor/extensions/diagram.js b/app/assets/javascripts/content_editor/extensions/diagram.js index d192b815092..f9dfeb92e9a 100644 --- a/app/assets/javascripts/content_editor/extensions/diagram.js +++ b/app/assets/javascripts/content_editor/extensions/diagram.js @@ -14,6 +14,9 @@ export default CodeBlockHighlight.extend({ return element.dataset.diagram; }, }, + isDiagram: { + default: true, + }, }; }, diff --git a/app/assets/javascripts/content_editor/extensions/frontmatter.js b/app/assets/javascripts/content_editor/extensions/frontmatter.js index 9842027e192..2ec22158106 100644 --- a/app/assets/javascripts/content_editor/extensions/frontmatter.js +++ b/app/assets/javascripts/content_editor/extensions/frontmatter.js @@ -1,10 +1,18 @@ -import { VueNodeViewRenderer } from '@tiptap/vue-2'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; -import FrontmatterWrapper from '../components/wrappers/frontmatter.vue'; import CodeBlockHighlight from './code_block_highlight'; export default CodeBlockHighlight.extend({ name: 'frontmatter', + + addAttributes() { + return { + ...this.parent?.(), + isFrontmatter: { + default: true, + }, + }; + }, + parseHTML() { return [ { @@ -24,9 +32,6 @@ export default CodeBlockHighlight.extend({ }, }; }, - addNodeView() { - return new VueNodeViewRenderer(FrontmatterWrapper); - }, addInputRules() { return []; diff --git a/app/assets/javascripts/content_editor/services/code_block_language_loader.js b/app/assets/javascripts/content_editor/services/code_block_language_loader.js index 0d5a8c91907..1afaf4bfef6 100644 --- a/app/assets/javascripts/content_editor/services/code_block_language_loader.js +++ b/app/assets/javascripts/content_editor/services/code_block_language_loader.js @@ -40,25 +40,21 @@ const codeBlockLanguageLoader = { loadLanguageFromInputRule(match) { const { syntax } = this.findLanguageBySyntax(match[1]); - this.loadLanguages([syntax]); + this.loadLanguage(syntax); return { language: syntax }; }, - loadLanguages(languageList = []) { - const loaders = languageList - .filter( - (languageName) => !this.isLanguageLoaded(languageName) && languageName in languageLoader, - ) - .map((languageName) => { - return languageLoader[languageName]() - .then(({ default: language }) => { - this.lowlight.registerLanguage(languageName, language); - }) - .catch(() => false); - }); + async loadLanguage(languageName) { + if (this.isLanguageLoaded(languageName)) return false; - return Promise.all(loaders); + try { + const { default: language } = await languageLoader[languageName](); + this.lowlight.registerLanguage(languageName, language); + return true; + } catch { + return false; + } }, }; diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js index b993851a92f..5078668c620 100644 --- a/app/assets/javascripts/content_editor/services/content_editor.js +++ b/app/assets/javascripts/content_editor/services/content_editor.js @@ -3,12 +3,11 @@ import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } fro /* eslint-disable no-underscore-dangle */ export class ContentEditor { - constructor({ tiptapEditor, serializer, deserializer, assetResolver, eventHub, languageLoader }) { + constructor({ tiptapEditor, serializer, deserializer, assetResolver, eventHub }) { this._tiptapEditor = tiptapEditor; this._serializer = serializer; this._deserializer = deserializer; this._eventHub = eventHub; - this._languageLoader = languageLoader; this._assetResolver = assetResolver; } @@ -49,7 +48,7 @@ export class ContentEditor { } async setSerializedContent(serializedContent) { - const { _tiptapEditor: editor, _eventHub: eventHub, _languageLoader: languageLoader } = this; + const { _tiptapEditor: editor, _eventHub: eventHub } = this; const { doc, tr } = editor.state; const selection = TextSelection.create(doc, 0, doc.content.size); @@ -58,12 +57,8 @@ export class ContentEditor { const result = await this.deserialize(serializedContent); if (Object.keys(result).length !== 0) { - const { document, languages } = result; - - await languageLoader.loadLanguages(languages); - tr.setSelection(selection) - .replaceSelectionWith(document, false) + .replaceSelectionWith(result.document, false) .setMeta('preventUpdate', true); editor.view.dispatch(tr); } diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index adb1398b2c4..db76073510e 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -62,7 +62,6 @@ import createGlApiMarkdownDeserializer from './gl_api_markdown_deserializer'; import createRemarkMarkdownDeserializer from './remark_markdown_deserializer'; import createAssetResolver from './asset_resolver'; import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts'; -import languageLoader from './code_block_language_loader'; const createTiptapEditor = ({ extensions = [], ...options } = {}) => new Editor({ @@ -96,7 +95,7 @@ export const createContentEditor = ({ BulletList, Code, ColorChip, - CodeBlockHighlight.configure({ lowlight, languageLoader }), + CodeBlockHighlight.configure({ lowlight }), DescriptionItem, DescriptionList, Details, @@ -160,7 +159,6 @@ export const createContentEditor = ({ serializer, eventHub, deserializer, - languageLoader, assetResolver, }); }; diff --git a/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js index 3742d14bfd1..dcd56e55268 100644 --- a/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js @@ -18,7 +18,6 @@ export default ({ render }) => { return { deserialize: async ({ schema, content }) => { const html = await render(content); - const languages = []; if (!html) return {}; @@ -28,11 +27,7 @@ export default ({ render }) => { // append original source as a comment that nodes can access body.append(document.createComment(content)); - body.querySelectorAll('pre').forEach((preElement) => { - languages.push(preElement.getAttribute('lang')); - }); - - return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), languages }; + return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body) }; }, }; }; diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js index ef0a6ce0fac..770de1df0d0 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -35,10 +35,10 @@ const factorySpecs = { pre: { block: 'codeBlock', skipChildren: true, - getContent: ({ hastNodeText }) => hastNodeText, + getContent: ({ hastNodeText }) => hastNodeText.replace(/\n$/, ''), getAttrs: (hastNode) => { const languageClass = hastNode.children[0]?.properties.className?.[0]; - const language = isString(languageClass) ? languageClass.replace('language-', '') : ''; + const language = isString(languageClass) ? languageClass.replace('language-', '') : null; return { language }; }, @@ -81,7 +81,7 @@ export default () => { }), }); - return { document, languages: [] }; + return { document }; }, }; }; diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index eaf31a2b396..1352211b927 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -170,23 +170,6 @@ } ] }, - "cobertura": { - "description": "Path for file(s) that should be parsed as Cobertura XML coverage report", - "oneOf": [ - { - "type": "string", - "description": "Path to a single XML file" - }, - { - "type": "array", - "description": "A list of paths to XML files that will automatically be merged into one report", - "items": { - "type": "string" - }, - "minItems": 1 - } - ] - }, "coverage_report": { "type": "object", "description": "Used to collect coverage reports from the job.", diff --git a/app/assets/javascripts/pages/admin/background_migrations/index.js b/app/assets/javascripts/pages/admin/background_migrations/index.js new file mode 100644 index 00000000000..4c59613140b --- /dev/null +++ b/app/assets/javascripts/pages/admin/background_migrations/index.js @@ -0,0 +1,3 @@ +import { initBackgroundMigrationsApp } from '~/admin/background_migrations'; + +initBackgroundMigrationsApp(); diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index dff6a44f9a3..8f13783da66 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -54,7 +54,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end def service_usage_data - @service_ping_data_present = Rails.cache.exist?('usage_data') + @service_ping_data_present = prerecorded_service_ping_data.present? end def update @@ -64,7 +64,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def usage_data respond_to do |format| format.html do - usage_data_json = Gitlab::Json.pretty_generate(Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values, cached: true)) + usage_data_json = Gitlab::Json.pretty_generate(service_ping_data) render html: Gitlab::Highlight.highlight('payload.json', usage_data_json, language: 'json') end @@ -72,7 +72,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController format.json do Gitlab::UsageDataCounters::ServiceUsageDataCounter.count(:download_payload_click) - render json: Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values, cached: true).to_json + render json: service_ping_data.to_json end end end @@ -307,6 +307,14 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def valid_setting_panels VALID_SETTING_PANELS end + + def service_ping_data + prerecorded_service_ping_data || Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values) + end + + def prerecorded_service_ping_data + Rails.cache.fetch(Gitlab::Usage::ServicePingReport::CACHE_KEY) || ::RawUsageData.for_current_reporting_cycle.first&.payload + end end Admin::ApplicationSettingsController.prepend_mod_with('Admin::ApplicationSettingsController') diff --git a/app/controllers/admin/background_migrations_controller.rb b/app/controllers/admin/background_migrations_controller.rb index 16e53c8bd0c..fc1ac163449 100644 --- a/app/controllers/admin/background_migrations_controller.rb +++ b/app/controllers/admin/background_migrations_controller.rb @@ -53,9 +53,9 @@ class Admin::BackgroundMigrationsController < Admin::ApplicationController end def base_model - database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME + @selected_database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME - Gitlab::Database.database_base_models[database] + Gitlab::Database.database_base_models[@selected_database] end def batched_migration_class diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb index dddf9a3ee97..cb818ac5e92 100644 --- a/app/graphql/types/container_repository_type.rb +++ b/app/graphql/types/container_repository_type.rb @@ -21,6 +21,7 @@ module Types field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.' field :tags_count, GraphQL::Types::Int, null: false, description: 'Number of tags associated with this image.' field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.' + field :last_cleanup_deleted_tags_count, GraphQL::Types::Int, null: true, description: 'Number of deleted tags from the last cleanup.' def can_delete Ability.allowed?(current_user, :update_container_image, object) diff --git a/app/models/raw_usage_data.rb b/app/models/raw_usage_data.rb index 6fe3b26b58b..a6844eb8616 100644 --- a/app/models/raw_usage_data.rb +++ b/app/models/raw_usage_data.rb @@ -1,9 +1,16 @@ # frozen_string_literal: true class RawUsageData < ApplicationRecord + REPORTING_CADENCE = 7.days.freeze + validates :payload, presence: true validates :recorded_at, presence: true, uniqueness: true + scope :for_current_reporting_cycle, -> do + where('created_at >= ?', REPORTING_CADENCE.ago.beginning_of_day) + .order(created_at: :desc) + end + def update_version_metadata!(usage_data_id:) self.update_columns(sent_at: Time.current, version_usage_data_id_value: usage_data_id) end diff --git a/app/views/admin/background_migrations/_migration.html.haml b/app/views/admin/background_migrations/_migration.html.haml index 2419af2e746..33fcfbd6790 100644 --- a/app/views/admin/background_migrations/_migration.html.haml +++ b/app/views/admin/background_migrations/_migration.html.haml @@ -12,14 +12,14 @@ = gl_badge_tag migration.status_name.to_s.humanize, { size: :sm, variant: batched_migration_status_badge_variant(migration) } %td{ role: 'cell', data: { label: _('Action') } } - if migration.active? - = button_to pause_admin_background_migration_path(migration), + = button_to pause_admin_background_migration_path(migration, database: params[:database]), class: 'gl-button btn btn-icon has-tooltip', title: _('Pause'), 'aria-label' => _('Pause') do = sprite_icon('pause', css_class: 'gl-button-icon gl-icon') - elsif migration.paused? - = button_to resume_admin_background_migration_path(migration), + = button_to resume_admin_background_migration_path(migration, database: params[:database]), class: 'gl-button btn btn-icon has-tooltip', title: _('Resume'), 'aria-label' => _('Resume') do = sprite_icon('play', css_class: 'gl-button-icon gl-icon') - elsif migration.failed? - = button_to retry_admin_background_migration_path(migration), + = button_to retry_admin_background_migration_path(migration, database: params[:database]), class: 'gl-button btn btn-icon has-tooltip', title: _('Retry'), 'aria-label' => _('Retry') do = sprite_icon('retry', css_class: 'gl-button-icon gl-icon') diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml index 089bcfbf51e..b2b66a94970 100644 --- a/app/views/admin/background_migrations/index.html.haml +++ b/app/views/admin/background_migrations/index.html.haml @@ -1,13 +1,25 @@ -- page_title _('Background Migrations') +- page_title s_('BackgroundMigrations|Background Migrations') + +.gl-display-flex.gl-sm-flex-direction-column.gl-sm-align-items-flex-end.gl-pb-5.gl-border-b-1.gl-border-b-solid.gl-border-b-gray-100 + .gl-flex-grow-1.gl-mr-7 + %h3= s_('BackgroundMigrations|Background Migrations') + %p.light.gl-mb-0 + - learnmore_link = help_page_path('development/database/batched_background_migrations') + - learnmore_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: learnmore_link } + = html_escape(s_('BackgroundMigrations|Background migrations are used to perform data migrations whenever a migration exceeds the time limits in our guidelines. %{linkStart}Learn more%{linkEnd}')) % { linkStart: learnmore_link_start, linkEnd: '</a>'.html_safe } + + - if @databases.size > 1 + .gl-display-flex.gl-align-items-center.gl-flex-grow-0.gl-flex-basis-0.gl-sm-mt-0.gl-mt-5 + #js-database-listbox{ data: { databases: @databases, selected_database: @selected_database } } = gl_tabs_nav do - = gl_tab_link_to admin_background_migrations_path, item_active: @current_tab == 'queued' do + = gl_tab_link_to admin_background_migrations_path({ tab: nil, database: params[:database] }), item_active: @current_tab == 'queued' do = _('Queued') = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['queued']) - = gl_tab_link_to admin_background_migrations_path(tab: 'failed'), item_active: @current_tab == 'failed' do + = gl_tab_link_to admin_background_migrations_path({ tab: 'failed', database: params[:database] }), item_active: @current_tab == 'failed' do = _('Failed') = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['failed']) - = gl_tab_link_to admin_background_migrations_path(tab: 'finished'), item_active: @current_tab == 'finished' do + = gl_tab_link_to admin_background_migrations_path({ tab: 'finished', database: params[:database] }), item_active: @current_tab == 'finished' do = _('Finished') = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['finished']) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 10b32c3f03f..eb06030ddb1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -9833,6 +9833,7 @@ A container repository. | <a id="containerrepositoryexpirationpolicycleanupstatus"></a>`expirationPolicyCleanupStatus` | [`ContainerRepositoryCleanupStatus`](#containerrepositorycleanupstatus) | Tags cleanup status for the container repository. | | <a id="containerrepositoryexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. | | <a id="containerrepositoryid"></a>`id` | [`ID!`](#id) | ID of the container repository. | +| <a id="containerrepositorylastcleanupdeletedtagscount"></a>`lastCleanupDeletedTagsCount` | [`Int`](#int) | Number of deleted tags from the last cleanup. | | <a id="containerrepositorylocation"></a>`location` | [`String!`](#string) | URL of the container repository. | | <a id="containerrepositorymigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. | | <a id="containerrepositoryname"></a>`name` | [`String!`](#string) | Name of the container repository. | @@ -9855,6 +9856,7 @@ Details of a container repository. | <a id="containerrepositorydetailsexpirationpolicycleanupstatus"></a>`expirationPolicyCleanupStatus` | [`ContainerRepositoryCleanupStatus`](#containerrepositorycleanupstatus) | Tags cleanup status for the container repository. | | <a id="containerrepositorydetailsexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. | | <a id="containerrepositorydetailsid"></a>`id` | [`ID!`](#id) | ID of the container repository. | +| <a id="containerrepositorydetailslastcleanupdeletedtagscount"></a>`lastCleanupDeletedTagsCount` | [`Int`](#int) | Number of deleted tags from the last cleanup. | | <a id="containerrepositorydetailslocation"></a>`location` | [`String!`](#string) | URL of the container repository. | | <a id="containerrepositorydetailsmigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. | | <a id="containerrepositorydetailsname"></a>`name` | [`String!`](#string) | Name of the container repository. | diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md index 874c3c2b196..486e37407ad 100644 --- a/doc/ci/yaml/artifacts_reports.md +++ b/doc/ci/yaml/artifacts_reports.md @@ -80,34 +80,17 @@ GitLab can display the results of one or more reports in: - The [security dashboard](../../user/application_security/security_dashboard/index.md). - The [Project Vulnerability report](../../user/application_security/vulnerability_report/index.md). -## `artifacts:reports:cobertura` (DEPRECATED) +## `artifacts:reports:coverage_report` -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) in GitLab 12.9. -> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) in GitLab 14.9. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344533) in GitLab 14.10. -WARNING: -This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) -in GitLab 14.9 and planned for removal in GitLab 15.0. The alternative `artifacts:reports:coverage_report` -is available GitLab 14.10. +Use `coverage_report` to collect coverage report in Cobertura format. The `cobertura` report collects [Cobertura coverage XML files](../../user/project/merge_requests/test_coverage_visualization.md). -The collected Cobertura coverage reports upload to GitLab as an artifact. - -GitLab can display the results of one or more reports in the merge request -[diff annotations](../../user/project/merge_requests/test_coverage_visualization.md). Cobertura was originally developed for Java, but there are many third-party ports for other languages such as JavaScript, Python, and Ruby. -## `artifacts:reports:coverage_report` - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344533) in GitLab 14.10. - -Use `coverage_report` to collect coverage report in Cobertura format, similar to `artifacts:reports:cobertura`. - -NOTE: -`artifacts:reports:coverage_report` cannot be used at the same time with `artifacts:reports:cobertura`. - ```yaml artifacts: reports: diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 434693a0e11..5f1e02c9da6 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -698,7 +698,7 @@ Properties of customer critical merge requests: - It is required that the reviewer(s) and maintainer(s) involved with a customer critical merge request are engaged as soon as this decision is made. - It is required to prioritize work for those involved on a customer critical merge request so that they have the time available necessary to focus on it. - It is required to adhere to GitLab [values](https://about.gitlab.com/handbook/values/) and processes when working on customer critical merge requests, taking particular note of family and friends first/work second, definition of done, iteration, and release when it's ready. -- Customer critical merge requests are required to not reduce security, introduce data-loss risk, reduce availability, nor break existing functionality per the process for [prioritizing technical decisions](https://about.gitlab.com/handbook/engineering/principles/#prioritizing-technical-decisions). +- Customer critical merge requests are required to not reduce security, introduce data-loss risk, reduce availability, nor break existing functionality per the process for [prioritizing technical decisions](https://about.gitlab.com/handbook/engineering/development/principles/#prioritizing-technical-decisions). - On customer critical requests, it is _recommended_ that those involved _consider_ coordinating synchronously (Zoom, Slack) in addition to asynchronously (merge requests comments) if they believe this may reduce the elapsed time to merge even though this _may_ sacrifice [efficiency](https://about.gitlab.com/company/culture/all-remote/asynchronous/#evaluating-efficiency.md). - After a customer critical merge request is merged, a retrospective must be completed with the intention of reducing the frequency of future customer critical merge requests. diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md index fd9378f6d7d..28609153ed3 100644 --- a/doc/development/dangerbot.md +++ b/doc/development/dangerbot.md @@ -66,7 +66,7 @@ continue to apply. However, there are a few things that deserve special emphasis Danger is a powerful tool and flexible tool, but not always the most appropriate way to solve a given problem or workflow. -First, be aware of the GitLab [commitment to dogfooding](https://about.gitlab.com/handbook/engineering/principles/#dogfooding). +First, be aware of the GitLab [commitment to dogfooding](https://about.gitlab.com/handbook/engineering/development/principles/#dogfooding). The code we write for Danger is GitLab-specific, and it **may not** be most appropriate place to implement functionality that addresses a need we encounter. Our users, customers, and even our own satellite projects, such as [Gitaly](https://gitlab.com/gitlab-org/gitaly), diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index 9f222f157a1..0affc11de02 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -539,7 +539,7 @@ When writing about licenses: - Do not use variations such as **cloud license**, **offline license**, or **legacy license**. - Do not use interchangeably with **subscription**: - - A license grants users access to the subscription they purchased, and contains information such as the number of seats and subscription dates. + - A license grants users access to the subscription they purchased, and contains information such as the number of seats they purchased and subscription dates. - A subscription is the subscription tier that the user purchases. Use: diff --git a/doc/development/fe_guide/design_anti_patterns.md b/doc/development/fe_guide/design_anti_patterns.md index 76825d6ff18..b7238bb2813 100644 --- a/doc/development/fe_guide/design_anti_patterns.md +++ b/doc/development/fe_guide/design_anti_patterns.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Anti-patterns may seem like good approaches at first, but it has been shown that they bring more ills than benefits. These should generally be avoided. -Throughout the GitLab codebase, there may be historic uses of these anti-patterns. Please [use discretion](https://about.gitlab.com/handbook/engineering/principles/#balance-refactoring-and-velocity) +Throughout the GitLab codebase, there may be historic uses of these anti-patterns. Please [use discretion](https://about.gitlab.com/handbook/engineering/development/principles/#balance-refactoring-and-velocity) when figuring out whether or not to refactor, when touching code that uses one of these legacy patterns. NOTE: diff --git a/doc/development/fips_compliance.md b/doc/development/fips_compliance.md index 64dfbc9c10d..389c01e9a3f 100644 --- a/doc/development/fips_compliance.md +++ b/doc/development/fips_compliance.md @@ -98,6 +98,20 @@ virtual machine: fips-mode-setup --disable ``` +#### Detect FIPS enablement in code + +You can query `GitLab::FIPS` in Ruby code to determine if the instance is FIPS-enabled: + +```ruby +def default_min_key_size(name) + if Gitlab::FIPS.enabled? + Gitlab::SSHPublicKey.supported_sizes(name).select(&:positive?).min || -1 + else + 0 + end +end +``` + ## Set up a FIPS-enabled cluster You can use the [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to spin diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 2fe295bfedb..cb1e224f062 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -12,7 +12,7 @@ which itself includes files under [`.gitlab/ci/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/.gitlab/ci) for easier maintenance. -We're striving to [dogfood](https://about.gitlab.com/handbook/engineering/principles/#dogfooding) +We're striving to [dogfood](https://about.gitlab.com/handbook/engineering/development/principles/#dogfooding) GitLab [CI/CD features and best-practices](../ci/yaml/index.md) as much as possible. diff --git a/doc/development/ruby_upgrade.md b/doc/development/ruby_upgrade.md index a208a93e300..551f8dd081e 100644 --- a/doc/development/ruby_upgrade.md +++ b/doc/development/ruby_upgrade.md @@ -272,4 +272,4 @@ and merged back independently. - **Give yourself enough time to fix problems ahead of a milestone release.** GitLab moves fast. As a Ruby upgrade requires many MRs to be sent and reviewed, make sure all changes are merged at least a week before the 22nd. This gives us extra time to act if something breaks. If in doubt, it is better to -postpone the upgrade to the following month, as we [prioritize availability over velocity](https://about.gitlab.com/handbook/engineering/principles/#prioritizing-technical-decisions). +postpone the upgrade to the following month, as we [prioritize availability over velocity](https://about.gitlab.com/handbook/engineering/development/principles/#prioritizing-technical-decisions). diff --git a/doc/development/service_ping/implement.md b/doc/development/service_ping/implement.md index 1bbc65a0300..48d72431ddc 100644 --- a/doc/development/service_ping/implement.md +++ b/doc/development/service_ping/implement.md @@ -289,6 +289,8 @@ Enabled by default in GitLab 13.7 and later. To increment the values, the related feature `usage_data_<event_name>` must be enabled. + Feature flags are required for this API and they can't be removed, they can be set to `default_enabled: true`. + ```plaintext POST /usage_data/increment_counter ``` diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md index 0aadde7f7c2..13962ad0376 100644 --- a/doc/topics/git/troubleshooting_git.md +++ b/doc/topics/git/troubleshooting_git.md @@ -214,7 +214,7 @@ apply more than one: ```shell omnibus_gitconfig['system'] = { # Set the http.postBuffer size, in bytes - "http" => ["postBuffer => 524288000"] + "http" => ["postBuffer = 524288000"] } ``` diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md index 156802d2164..c237452c01d 100644 --- a/doc/user/project/merge_requests/test_coverage_visualization.md +++ b/doc/user/project/merge_requests/test_coverage_visualization.md @@ -28,7 +28,7 @@ between pipeline completion and the visualization loading on the page. For the coverage analysis to work, you have to provide a properly formatted [Cobertura XML](https://cobertura.github.io/cobertura/) report to -[`artifacts:reports:cobertura`](../../../ci/yaml/artifacts_reports.md#artifactsreportscobertura-deprecated). +[`artifacts:reports:coverage_report`](../../../ci/yaml/artifacts_reports.md#artifactsreportscoverage_report). This format was originally developed for Java, but most coverage analysis frameworks for other languages have plugins to add support for it, like: diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb index 606814c72ad..d5d204bb995 100644 --- a/lib/gitlab/ci/config/entry/reports.rb +++ b/lib/gitlab/ci/config/entry/reports.rb @@ -15,7 +15,7 @@ module Gitlab ALLOWED_KEYS = %i[junit codequality sast secret_detection dependency_scanning container_scanning dast performance browser_performance load_performance license_scanning metrics lsif - dotenv cobertura terraform accessibility + dotenv terraform accessibility requirements coverage_fuzzing api_fuzzing cluster_image_scanning coverage_report].freeze @@ -45,13 +45,10 @@ module Gitlab validates :metrics, array_of_strings_or_string: true validates :lsif, array_of_strings_or_string: true validates :dotenv, array_of_strings_or_string: true - validates :cobertura, array_of_strings_or_string: true validates :terraform, array_of_strings_or_string: true validates :accessibility, array_of_strings_or_string: true validates :requirements, array_of_strings_or_string: true end - - validates :config, mutually_exclusive_keys: [:coverage_report, :cobertura] end def value diff --git a/lib/gitlab/ci/templates/MATLAB.gitlab-ci.yml b/lib/gitlab/ci/templates/MATLAB.gitlab-ci.yml index 83f9f7e533b..64a063388b2 100644 --- a/lib/gitlab/ci/templates/MATLAB.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/MATLAB.gitlab-ci.yml @@ -84,7 +84,9 @@ test_artifacts: artifacts: reports: junit: "./artifacts/results.xml" - cobertura: "./artifacts/cobertura.xml" + coverage_report: + coverage_format: cobertura + path: "./artifacts/cobertura.xml" paths: - "./artifacts" diff --git a/lib/gitlab/usage/service_ping_report.rb b/lib/gitlab/usage/service_ping_report.rb index 3e653b186a0..e73200cbd4a 100644 --- a/lib/gitlab/usage/service_ping_report.rb +++ b/lib/gitlab/usage/service_ping_report.rb @@ -3,6 +3,8 @@ module Gitlab module Usage class ServicePingReport + CACHE_KEY = 'usage_data' + class << self def for(output:, cached: false) case output.to_sym @@ -26,7 +28,7 @@ module Gitlab end def all_metrics_values(cached) - Rails.cache.fetch('usage_data', force: !cached, expires_in: 2.weeks) do + Rails.cache.fetch(CACHE_KEY, force: !cached, expires_in: 2.weeks) do Gitlab::UsageData.data end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b1824e17c03..d90273ef8db 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5545,6 +5545,15 @@ msgstr "" msgid "Background color" msgstr "" +msgid "BackgroundMigrations|Background Migrations" +msgstr "" + +msgid "BackgroundMigrations|Background migrations are used to perform data migrations whenever a migration exceeds the time limits in our guidelines. %{linkStart}Learn more%{linkEnd}" +msgstr "" + +msgid "BackgroundMigrations|Database" +msgstr "" + msgid "Badges" msgstr "" @@ -11327,9 +11336,18 @@ msgstr "" msgid "DastProfiles|New site profile" msgstr "" +msgid "DastProfiles|No scanner profile selected" +msgstr "" + +msgid "DastProfiles|No scanner profile selected." +msgstr "" + msgid "DastProfiles|No scanner profiles created yet" msgstr "" +msgid "DastProfiles|No site profile selected" +msgstr "" + msgid "DastProfiles|No site profiles created yet" msgstr "" @@ -11378,9 +11396,27 @@ msgstr "" msgid "DastProfiles|Scanner name" msgstr "" +msgid "DastProfiles|Scanner profile" +msgstr "" + +msgid "DastProfiles|Scanner profiles define the configuration details of a security scanner. %{linkStart}Learn more%{linkEnd}." +msgstr "" + +msgid "DastProfiles|Select a scanner profile to run a DAST scan" +msgstr "" + +msgid "DastProfiles|Select a site profile to run a DAST scan" +msgstr "" + msgid "DastProfiles|Select branch" msgstr "" +msgid "DastProfiles|Select scanner profile" +msgstr "" + +msgid "DastProfiles|Select site profile" +msgstr "" + msgid "DastProfiles|Show debug messages" msgstr "" @@ -11393,6 +11429,9 @@ msgstr "" msgid "DastProfiles|Site name" msgstr "" +msgid "DastProfiles|Site profiles define the attributes and configuration details of your deployed application, website, or API. %{linkStart}Learn more%{linkEnd}." +msgstr "" + msgid "DastProfiles|Site type" msgstr "" diff --git a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb index 2434fecf51a..2b48945137c 100644 --- a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb +++ b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb @@ -29,7 +29,8 @@ module QA it( 'is determined based on forward:pipeline_variables condition', :aggregate_failures, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360745' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360745', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361400', type: :investigating } ) do # Is inheritable when true expect(child1_pipeline).to have_variable(key: key, value: value), diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index a18ebe9c9a0..874eabd1733 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -66,6 +66,26 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set sign_in(admin) end + context 'when there are recent ServicePing reports' do + it 'attempts to use prerecorded data' do + create(:raw_usage_data) + + expect(Gitlab::Usage::ServicePingReport).not_to receive(:for) + + get :usage_data, format: :json + end + end + + context 'when there are NO recent ServicePing reports' do + it 'calculates data on the fly' do + allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_call_original + + get :usage_data, format: :json + + expect(Gitlab::Usage::ServicePingReport).to have_received(:for) + end + end + it 'returns HTML data' do get :usage_data, format: :html @@ -368,4 +388,37 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set expect(response).to redirect_to("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf") end end + + describe 'GET #service_usage_data' do + before do + stub_usage_data_connections + stub_database_flavor_check + sign_in(admin) + end + + it 'assigns truthy value if there are recent ServicePing reports in database' do + create(:raw_usage_data) + + get :service_usage_data, format: :html + + expect(assigns(:service_ping_data_present)).to be_truthy + expect(response).to have_gitlab_http_status(:ok) + end + + it 'assigns truthy value if there are recent ServicePing reports in cache', :use_clean_rails_memory_store_caching do + Rails.cache.write('usage_data', true) + + get :service_usage_data, format: :html + + expect(assigns(:service_ping_data_present)).to be_truthy + expect(response).to have_gitlab_http_status(:ok) + end + + it 'assigns falsey value if there are NO recent ServicePing reports' do + get :service_usage_data, format: :html + + expect(assigns(:service_ping_data_present)).to be_falsey + expect(response).to have_gitlab_http_status(:ok) + end + end end diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb index 032e3cc9dc0..fcb5910b85e 100644 --- a/spec/features/admin/admin_sees_background_migrations_spec.rb +++ b/spec/features/admin/admin_sees_background_migrations_spec.rb @@ -77,6 +77,17 @@ RSpec.describe "Admin > Admin sees background migrations" do end end + it 'can fire an action with a database param' do + visit admin_background_migrations_path(database: 'main') + + within '#content-body' do + tab = find_link 'Failed' + tab.click + + expect(page).to have_selector("[method='post'][action='/admin/background_migrations/#{failed_migration.id}/retry?database=main']") + end + end + it 'can view and retry them' do visit admin_background_migrations_path @@ -120,4 +131,83 @@ RSpec.describe "Admin > Admin sees background migrations" do expect(page).to have_content(finished_migration.status_name.to_s) end end + + it 'can change tabs and retain database param' do + visit admin_background_migrations_path(database: 'ci') + + within '#content-body' do + tab = find_link 'Finished' + expect(tab[:class]).not_to include('gl-tab-nav-item-active') + + tab.click + + expect(page).to have_current_path(admin_background_migrations_path(tab: 'finished', database: 'ci')) + expect(tab[:class]).to include('gl-tab-nav-item-active') + end + end + + it 'can view documentation from Learn more link' do + visit admin_background_migrations_path + + within '#content-body' do + expect(page).to have_link('Learn more', href: help_page_path('development/database/batched_background_migrations')) + end + end + + describe 'selected database toggle', :js do + context 'when multi database is not enabled' do + before do + allow(Gitlab::Database).to receive(:db_config_names).and_return(['main']) + end + + it 'does not render the database listbox' do + visit admin_background_migrations_path + + expect(page).not_to have_selector('[data-testid="database-listbox"]') + end + end + + context 'when multi database is enabled' do + before do + allow(Gitlab::Database).to receive(:db_config_names).and_return(%w[main ci]) + end + + it 'does render the database listbox' do + visit admin_background_migrations_path + + expect(page).to have_selector('[data-testid="database-listbox"]') + end + + it 'defaults to main when no parameter is passed' do + visit admin_background_migrations_path + + listbox = page.find('[data-testid="database-listbox"]') + + expect(listbox).to have_text('main') + end + + it 'shows correct database when a parameter is passed' do + visit admin_background_migrations_path(database: 'ci') + + listbox = page.find('[data-testid="database-listbox"]') + + expect(listbox).to have_text('ci') + end + + it 'updates the path to correct database when clicking on listbox option' do + visit admin_background_migrations_path + + listbox = page.find('[data-testid="database-listbox"]') + expect(listbox).to have_text('main') + + listbox.find('button').click + listbox.find('li', text: 'ci').click + wait_for_requests + + expect(page).to have_current_path(admin_background_migrations_path(database: 'ci')) + listbox = page.find('[data-testid="database-listbox"]') + expect(listbox).to have_text('ci') + end + end + end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 68ad1e25b47..32ae9c00c9e 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -834,10 +834,9 @@ RSpec.describe 'Admin updates settings' do stub_database_flavor_check end - context 'when service data cached', :clean_gitlab_redis_cache do + context 'when service data cached', :use_clean_rails_memory_store_caching do before do - allow(Rails.cache).to receive(:exist?).with('usage_data').and_return(true) - + visit usage_data_admin_application_settings_path visit service_usage_data_admin_application_settings_path end diff --git a/spec/fixtures/api/schemas/graphql/container_repository.json b/spec/fixtures/api/schemas/graphql/container_repository.json index 04e67f73844..2bb598a14cb 100644 --- a/spec/fixtures/api/schemas/graphql/container_repository.json +++ b/spec/fixtures/api/schemas/graphql/container_repository.json @@ -1,6 +1,6 @@ { "type": "object", - "required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "expirationPolicyCleanupStatus", "project"], + "required": ["id", "name", "path", "location", "createdAt", "updatedAt", "tagsCount", "canDelete", "expirationPolicyCleanupStatus", "project", "lastCleanupDeletedTagsCount"], "properties": { "id": { "type": "string" @@ -38,6 +38,9 @@ }, "project": { "type": "object" + }, + "lastCleanupDeletedTagsCount": { + "type": ["string", "null"] } } } diff --git a/spec/frontend/admin/background_migrations/components/database_listbox_spec.js b/spec/frontend/admin/background_migrations/components/database_listbox_spec.js new file mode 100644 index 00000000000..3778943872e --- /dev/null +++ b/spec/frontend/admin/background_migrations/components/database_listbox_spec.js @@ -0,0 +1,57 @@ +import { GlListbox } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import BackgroundMigrationsDatabaseListbox from '~/admin/background_migrations/components/database_listbox.vue'; +import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; +import { MOCK_DATABASES, MOCK_SELECTED_DATABASE } from '../mock_data'; + +jest.mock('~/lib/utils/url_utility', () => ({ + visitUrl: jest.fn(), + setUrlParams: jest.fn(), +})); + +describe('BackgroundMigrationsDatabaseListbox', () => { + let wrapper; + + const defaultProps = { + databases: MOCK_DATABASES, + selectedDatabase: MOCK_SELECTED_DATABASE, + }; + + const createComponent = (props = {}) => { + wrapper = shallowMount(BackgroundMigrationsDatabaseListbox, { + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findGlListbox = () => wrapper.findComponent(GlListbox); + + describe('template always', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders GlListbox', () => { + expect(findGlListbox().exists()).toBe(true); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent(); + }); + + it('selecting a listbox item fires visitUrl with the database param', () => { + findGlListbox().vm.$emit('select', MOCK_DATABASES[1].value); + + expect(setUrlParams).toHaveBeenCalledWith({ database: MOCK_DATABASES[1].value }); + expect(visitUrl).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/admin/background_migrations/mock_data.js b/spec/frontend/admin/background_migrations/mock_data.js new file mode 100644 index 00000000000..fbb1718f6b8 --- /dev/null +++ b/spec/frontend/admin/background_migrations/mock_data.js @@ -0,0 +1,6 @@ +export const MOCK_DATABASES = [ + { value: 'main', text: 'main' }, + { value: 'ci', text: 'ci' }, +]; + +export const MOCK_SELECTED_DATABASE = 'main'; diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js index 35f5b381c75..3a15ea45f40 100644 --- a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js +++ b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js @@ -124,7 +124,7 @@ describe('content_editor/components/bubble_menus/code_block', () => { describe('when dropdown item is clicked', () => { beforeEach(async () => { - jest.spyOn(codeBlockLanguageLoader, 'loadLanguages').mockResolvedValue(); + jest.spyOn(codeBlockLanguageLoader, 'loadLanguage').mockResolvedValue(); findDropdownItems().at(1).vm.$emit('click'); @@ -132,7 +132,7 @@ describe('content_editor/components/bubble_menus/code_block', () => { }); it('loads language', () => { - expect(codeBlockLanguageLoader.loadLanguages).toHaveBeenCalledWith(['java']); + expect(codeBlockLanguageLoader.loadLanguage).toHaveBeenCalledWith('java'); }); it('sets code block', () => { diff --git a/spec/frontend/content_editor/components/wrappers/frontmatter_spec.js b/spec/frontend/content_editor/components/wrappers/code_block_spec.js index 415f1314a36..a564959a3a6 100644 --- a/spec/frontend/content_editor/components/wrappers/frontmatter_spec.js +++ b/spec/frontend/content_editor/components/wrappers/code_block_spec.js @@ -1,20 +1,33 @@ +import { nextTick } from 'vue'; import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'; import { shallowMount } from '@vue/test-utils'; -import FrontmatterWrapper from '~/content_editor/components/wrappers/frontmatter.vue'; +import CodeBlockWrapper from '~/content_editor/components/wrappers/code_block.vue'; +import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader'; -describe('content/components/wrappers/frontmatter', () => { +jest.mock('~/content_editor/services/code_block_language_loader'); + +describe('content/components/wrappers/code_block', () => { + const language = 'yaml'; let wrapper; + let updateAttributesFn; + + const createWrapper = async (nodeAttrs = { language }) => { + updateAttributesFn = jest.fn(); - const createWrapper = async (nodeAttrs = { language: 'yaml' }) => { - wrapper = shallowMount(FrontmatterWrapper, { + wrapper = shallowMount(CodeBlockWrapper, { propsData: { node: { attrs: nodeAttrs, }, + updateAttributes: updateAttributesFn, }, }); }; + beforeEach(() => { + codeBlockLanguageLoader.findLanguageBySyntax.mockReturnValue({ syntax: language }); + }); + afterEach(() => { wrapper.destroy(); }); @@ -38,11 +51,21 @@ describe('content/components/wrappers/frontmatter', () => { }); it('renders label indicating that code block is frontmatter', () => { - createWrapper(); + createWrapper({ isFrontmatter: true, language }); const label = wrapper.find('[data-testid="frontmatter-label"]'); expect(label.text()).toEqual('frontmatter:yaml'); expect(label.classes()).toEqual(['gl-absolute', 'gl-top-0', 'gl-right-3']); }); + + it('loads code block’s syntax highlight language', async () => { + createWrapper(); + + expect(codeBlockLanguageLoader.loadLanguage).toHaveBeenCalledWith(language); + + await nextTick(); + + expect(updateAttributesFn).toHaveBeenCalledWith({ language }); + }); }); diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js index 02e5b1dc271..fc8460c7f84 100644 --- a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js +++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js @@ -1,4 +1,5 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; +import languageLoader from '~/content_editor/services/code_block_language_loader'; import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; const CODE_BLOCK_HTML = `<pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"> @@ -9,20 +10,20 @@ const CODE_BLOCK_HTML = `<pre class="code highlight js-syntax-highlight language </code> </pre>`; +jest.mock('~/content_editor/services/code_block_language_loader'); + describe('content_editor/extensions/code_block_highlight', () => { let parsedCodeBlockHtmlFixture; let tiptapEditor; let doc; let codeBlock; - let languageLoader; const parseHTML = (html) => new DOMParser().parseFromString(html, 'text/html'); const preElement = () => parsedCodeBlockHtmlFixture.querySelector('pre'); beforeEach(() => { - languageLoader = { loadLanguages: jest.fn() }; tiptapEditor = createTestEditor({ - extensions: [CodeBlockHighlight.configure({ languageLoader })], + extensions: [CodeBlockHighlight], }); ({ @@ -70,6 +71,8 @@ describe('content_editor/extensions/code_block_highlight', () => { const language = 'javascript'; beforeEach(() => { + languageLoader.loadLanguageFromInputRule.mockReturnValueOnce({ language }); + triggerNodeInputRule({ tiptapEditor, inputRuleText: `${inputRule}${language} `, @@ -83,7 +86,9 @@ describe('content_editor/extensions/code_block_highlight', () => { }); it('loads language when language loader is available', () => { - expect(languageLoader.loadLanguages).toHaveBeenCalledWith([language]); + expect(languageLoader.loadLanguageFromInputRule).toHaveBeenCalledWith( + expect.arrayContaining([`${inputRule}${language} `, language]), + ); }); }); }); diff --git a/spec/frontend/content_editor/extensions/diagram_spec.js b/spec/frontend/content_editor/extensions/diagram_spec.js new file mode 100644 index 00000000000..b8d9e0b5aeb --- /dev/null +++ b/spec/frontend/content_editor/extensions/diagram_spec.js @@ -0,0 +1,16 @@ +import Diagram from '~/content_editor/extensions/diagram'; +import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; + +describe('content_editor/extensions/diagram', () => { + it('inherits from code block highlight extension', () => { + expect(Diagram.parent).toBe(CodeBlockHighlight); + }); + + it('sets isDiagram attribute to true by default', () => { + expect(Diagram.config.addAttributes()).toEqual( + expect.objectContaining({ + isDiagram: { default: true }, + }), + ); + }); +}); diff --git a/spec/frontend/content_editor/extensions/frontmatter_spec.js b/spec/frontend/content_editor/extensions/frontmatter_spec.js index 4f80c2cb81a..9bd29070858 100644 --- a/spec/frontend/content_editor/extensions/frontmatter_spec.js +++ b/spec/frontend/content_editor/extensions/frontmatter_spec.js @@ -22,6 +22,10 @@ describe('content_editor/extensions/frontmatter', () => { })); }); + it('inherits from code block highlight extension', () => { + expect(Frontmatter.parent).toBe(CodeBlockHighlight); + }); + it('does not insert a frontmatter block when executing code block input rule', () => { const expectedDoc = doc(codeBlock({ language: 'plaintext' }, '')); const inputRuleText = '``` '; @@ -31,6 +35,14 @@ describe('content_editor/extensions/frontmatter', () => { expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); + it('sets isFrontmatter attribute to true by default', () => { + expect(Frontmatter.config.addAttributes()).toEqual( + expect.objectContaining({ + isFrontmatter: { default: true }, + }), + ); + }); + it.each` command | result | resultDesc ${'toggleCodeBlock'} | ${() => doc(codeBlock(''))} | ${'code block element'} diff --git a/spec/frontend/content_editor/services/code_block_language_loader_spec.js b/spec/frontend/content_editor/services/code_block_language_loader_spec.js index 55f4e0ed4e3..943de327762 100644 --- a/spec/frontend/content_editor/services/code_block_language_loader_spec.js +++ b/spec/frontend/content_editor/services/code_block_language_loader_spec.js @@ -53,23 +53,19 @@ describe('content_editor/services/code_block_language_loader', () => { }); }); - describe('loadLanguages', () => { + describe('loadLanguage', () => { it('loads highlight.js language packages identified by a list of languages', async () => { - const languages = ['javascript', 'ruby']; + const language = 'javascript'; - await languageLoader.loadLanguages(languages); + await languageLoader.loadLanguage(language); - languages.forEach((language) => { - expect(lowlight.registerLanguage).toHaveBeenCalledWith(language, expect.any(Function)); - }); + expect(lowlight.registerLanguage).toHaveBeenCalledWith(language, expect.any(Function)); }); describe('when language is already registered', () => { it('does not load the language again', async () => { - const languages = ['javascript']; - - await languageLoader.loadLanguages(languages); - await languageLoader.loadLanguages(languages); + await languageLoader.loadLanguage('javascript'); + await languageLoader.loadLanguage('javascript'); expect(lowlight.registerLanguage).toHaveBeenCalledTimes(1); }); @@ -94,7 +90,7 @@ describe('content_editor/services/code_block_language_loader', () => { expect(languageLoader.isLanguageLoaded(language)).toBe(false); - await languageLoader.loadLanguages([language]); + await languageLoader.loadLanguage(language); expect(languageLoader.isLanguageLoaded(language)).toBe(true); }); diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js index fde4f8f6282..a3553e612ca 100644 --- a/spec/frontend/content_editor/services/content_editor_spec.js +++ b/spec/frontend/content_editor/services/content_editor_spec.js @@ -11,7 +11,6 @@ describe('content_editor/services/content_editor', () => { let contentEditor; let serializer; let deserializer; - let languageLoader; let eventHub; let doc; let p; @@ -28,14 +27,12 @@ describe('content_editor/services/content_editor', () => { serializer = { serialize: jest.fn() }; deserializer = { deserialize: jest.fn() }; - languageLoader = { loadLanguages: jest.fn() }; eventHub = eventHubFactory(); contentEditor = new ContentEditor({ tiptapEditor, serializer, deserializer, eventHub, - languageLoader, }); }); @@ -77,12 +74,6 @@ describe('content_editor/services/content_editor', () => { expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON()); }); - - it('passes deserialized DOM document to language loader', async () => { - await contentEditor.setSerializedContent(testMarkdown); - - expect(languageLoader.loadLanguages).toHaveBeenCalledWith(languages); - }); }); describe('when setSerializedContent fails', () => { diff --git a/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js index e399c1f6bea..5458a42532f 100644 --- a/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js +++ b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js @@ -46,10 +46,6 @@ describe('content_editor/services/gl_api_markdown_deserializer', () => { expect(result.document.toJSON()).toEqual(document.toJSON()); }); - - it('returns languages of code blocks found in the document', () => { - expect(result.languages).toEqual(['javascript']); - }); }); describe('when the render function returns an empty value', () => { diff --git a/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js b/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js index 66ca1587ec6..4521d2c1949 100644 --- a/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js +++ b/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js @@ -273,7 +273,7 @@ two markdown: ` const fn = () => 'GitLab'; `, - doc: doc(codeBlock({ language: '' }, "const fn = () => 'GitLab';\n")), + doc: doc(codeBlock({ language: null }, "const fn = () => 'GitLab';")), }, { markdown: ` @@ -281,14 +281,14 @@ two const fn = () => 'GitLab'; \`\`\`\ `, - doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';\n")), + doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';")), }, { markdown: ` \`\`\` \`\`\`\ `, - doc: doc(codeBlock({ language: '' }, '')), + doc: doc(codeBlock({ language: null }, '')), }, { markdown: ` @@ -298,7 +298,7 @@ two \`\`\`\ `, - doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';\n\n\n")), + doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';\n\n")), }, ])('deserializes %s correctly', async ({ markdown, doc: expectedDoc }) => { const { schema } = tiptapEditor; diff --git a/spec/frontend/editor/schema/ci/json_tests/positive_tests/gitlab-ci.json b/spec/frontend/editor/schema/ci/json_tests/positive_tests/gitlab-ci.json index 89420bbc35f..666a4852957 100644 --- a/spec/frontend/editor/schema/ci/json_tests/positive_tests/gitlab-ci.json +++ b/spec/frontend/editor/schema/ci/json_tests/positive_tests/gitlab-ci.json @@ -97,7 +97,10 @@ "expire_in": "1 week", "reports": { "junit": "result.xml", - "cobertura": "cobertura-coverage.xml", + "coverage_report": { + "coverage_format": "cobertura", + "path": "cobertura-coverage.xml" + }, "codequality": "codequality.json", "sast": "sast.json", "dependency_scanning": "scan.json", @@ -147,7 +150,10 @@ "artifacts": { "reports": { "junit": ["result.xml"], - "cobertura": ["cobertura-coverage.xml"], + "coverage_report": { + "coverage_format": "cobertura", + "path": "cobertura-coverage.xml" + }, "codequality": ["codequality.json"], "sast": ["sast.json"], "dependency_scanning": ["scan.json"], diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb index d94516c6fce..62e72089e09 100644 --- a/spec/graphql/types/container_repository_details_type_spec.rb +++ b/spec/graphql/types/container_repository_details_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags size - project migration_state] + project migration_state last_cleanup_deleted_tags_count] it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') } diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb index 9815449dd68..bc92fa24050 100644 --- a/spec/graphql/types/container_repository_type_spec.rb +++ b/spec/graphql/types/container_repository_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ContainerRepository'] do fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status project - migration_state] + migration_state last_cleanup_deleted_tags_count] it { expect(described_class.graphql_name).to eq('ContainerRepository') } diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index ff3ec6196f0..051cccb4833 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -45,7 +45,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do :load_performance | 'load-performance.json' :lsif | 'lsif.json' :dotenv | 'build.dotenv' - :cobertura | 'cobertura-coverage.xml' :terraform | 'tfplan.json' :accessibility | 'gl-accessibility.json' end @@ -89,18 +88,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do expect(entry.value).to eq({ coverage_report: coverage_report, dast: ['gl-dast-report.json'] }) end end - - context 'and a direct coverage report format is specified' do - let(:config) { { coverage_report: coverage_report, cobertura: 'cobertura-coverage.xml' } } - - it 'is not valid' do - expect(entry).not_to be_valid - end - - it 'reports error' do - expect(entry.errors).to include /please use only one the following keys: coverage_report, cobertura/ - end - end end end diff --git a/spec/models/raw_usage_data_spec.rb b/spec/models/raw_usage_data_spec.rb index 6ff4c6eb19b..95b98279a27 100644 --- a/spec/models/raw_usage_data_spec.rb +++ b/spec/models/raw_usage_data_spec.rb @@ -3,6 +3,31 @@ require 'spec_helper' RSpec.describe RawUsageData do + context 'scopes' do + describe '.for_current_reporting_cycle' do + subject(:recent_service_ping_reports) { described_class.for_current_reporting_cycle } + + before_all do + create(:raw_usage_data, created_at: (described_class::REPORTING_CADENCE + 1.day).ago) + end + + it 'returns nil where no records match filter criteria' do + expect(recent_service_ping_reports).to be_empty + end + + context 'with records matching filtering criteria' do + let_it_be(:fresh_record) { create(:raw_usage_data) } + let_it_be(:record_at_edge_of_time_range) do + create(:raw_usage_data, created_at: described_class::REPORTING_CADENCE.ago) + end + + it 'return records within reporting cycle time range ordered by creation time' do + expect(recent_service_ping_reports).to eq [fresh_record, record_at_edge_of_time_range] + end + end + end + end + describe 'validations' do it { is_expected.to validate_presence_of(:payload) } it { is_expected.to validate_presence_of(:recorded_at) } |