diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-04 21:09:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-04 21:09:22 +0300 |
commit | 7c221ba5ce130ca50b892e6dd32783e1327718df (patch) | |
tree | 2c11d688f89886c8ea37638061fe450c8b656e47 | |
parent | da602b32e45431234e592e8f7001c3ace1771765 (diff) |
Add latest changes from gitlab-org/gitlab@master
119 files changed, 1196 insertions, 391 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue index 2bad15faa85..dae52a530ac 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_table.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue @@ -23,14 +23,10 @@ import { } from '~/vue_shared/components/paginated_table_with_search_and_tabs/constants'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { convertToSnakeCase } from '~/lib/utils/text_utility'; +import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue'; import getAlertsQuery from '~/graphql_shared/queries/get_alerts.query.graphql'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql'; -import { - ALERTS_STATUS_TABS, - ALERTS_SEVERITY_LABELS, - trackAlertListViewsOptions, -} from '../constants'; -import AlertStatus from './alert_status.vue'; +import { ALERTS_STATUS_TABS, SEVERITY_LEVELS, trackAlertListViewsOptions } from '../constants'; const TH_TEST_ID = { 'data-testid': 'alert-management-severity-sort' }; @@ -96,7 +92,7 @@ export default { sortable: true, }, ], - severityLabels: ALERTS_SEVERITY_LABELS, + severityLabels: SEVERITY_LEVELS, statusTabs: ALERTS_STATUS_TABS, components: { GlAlert, diff --git a/app/assets/javascripts/alert_management/constants.js b/app/assets/javascripts/alert_management/constants.js index b79a64646eb..c98d3865621 100644 --- a/app/assets/javascripts/alert_management/constants.js +++ b/app/assets/javascripts/alert_management/constants.js @@ -1,12 +1,12 @@ import { s__ } from '~/locale'; -export const ALERTS_SEVERITY_LABELS = { - CRITICAL: s__('AlertManagement|Critical'), - HIGH: s__('AlertManagement|High'), - MEDIUM: s__('AlertManagement|Medium'), - LOW: s__('AlertManagement|Low'), - INFO: s__('AlertManagement|Info'), - UNKNOWN: s__('AlertManagement|Unknown'), +export const SEVERITY_LEVELS = { + CRITICAL: s__('severity|Critical'), + HIGH: s__('severity|High'), + MEDIUM: s__('severity|Medium'), + LOW: s__('severity|Low'), + INFO: s__('severity|Info'), + UNKNOWN: s__('severity|Unknown'), }; export const ALERTS_STATUS_TABS = [ @@ -46,20 +46,3 @@ export const trackAlertListViewsOptions = { category: 'Alert Management', action: 'view_alerts_list', }; - -/** - * Tracks snowplow event when user views alert details - */ -export const trackAlertsDetailsViewsOptions = { - category: 'Alert Management', - action: 'view_alert_details', -}; - -/** - * Tracks snowplow event when alert status is updated - */ -export const trackAlertStatusUpdateOptions = { - category: 'Alert Management', - action: 'update_alert_status', - label: 'Status', -}; diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js index b484841ed2c..4b18dee7806 100644 --- a/app/assets/javascripts/alert_management/list.js +++ b/app/assets/javascripts/alert_management/list.js @@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo'; import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; import { parseBoolean } from '~/lib/utils/common_utils'; +import { PAGE_CONFIG } from '~/vue_shared/alert_details/constants'; import AlertManagementList from './components/alert_management_list_wrapper.vue'; Vue.use(VueApollo); @@ -59,6 +60,7 @@ export default () => { populatingAlertsHelpUrl, emptyAlertSvgPath, alertManagementEnabled: parseBoolean(alertManagementEnabled), + trackAlertStatusUpdateOptions: PAGE_CONFIG.OPERATIONS.TRACK_ALERT_STATUS_UPDATE_OPTIONS, userCanEnableAlertManagement: parseBoolean(userCanEnableAlertManagement), }, apolloProvider, diff --git a/app/assets/javascripts/graphql_shared/fragments/alert_note.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/alert_note.fragment.graphql index 74b425717a0..801311301ac 100644 --- a/app/assets/javascripts/graphql_shared/fragments/alert_note.fragment.graphql +++ b/app/assets/javascripts/graphql_shared/fragments/alert_note.fragment.graphql @@ -1,4 +1,4 @@ -#import "~/graphql_shared/fragments/author.fragment.graphql" +#import "./author.fragment.graphql" fragment AlertNote on Note { id diff --git a/app/assets/javascripts/graphql_shared/mutations/update_alert_status.mutation.graphql b/app/assets/javascripts/graphql_shared/mutations/alert_status_update.mutation.graphql index 42dc388c9d1..ba1e607bc10 100644 --- a/app/assets/javascripts/graphql_shared/mutations/update_alert_status.mutation.graphql +++ b/app/assets/javascripts/graphql_shared/mutations/alert_status_update.mutation.graphql @@ -1,4 +1,4 @@ -#import "~/graphql_shared/fragments/alert_note.fragment.graphql" +#import "../fragments/alert_note.fragment.graphql" mutation updateAlertStatus($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) { updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) { diff --git a/app/assets/javascripts/pages/projects/alert_management/details/index.js b/app/assets/javascripts/pages/projects/alert_management/details/index.js index a20f6713c9d..183e07ca1fc 100644 --- a/app/assets/javascripts/pages/projects/alert_management/details/index.js +++ b/app/assets/javascripts/pages/projects/alert_management/details/index.js @@ -1,3 +1,3 @@ -import AlertDetails from '~/alert_management/details'; +import AlertDetails from '~/vue_shared/alert_details'; AlertDetails('#js-alert_details'); diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 1485c43f002..b258c885abb 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -78,11 +78,7 @@ export default { return `ci-badge-${escape(group.name)}`; }, isFadedOut(jobName) { - return ( - this.jobHovered && - this.highlightedJobs.length > 1 && - !this.highlightedJobs.includes(jobName) - ); + return this.highlightedJobs.length > 1 && !this.highlightedJobs.includes(jobName); }, }, }; @@ -126,12 +122,9 @@ export default { :class="{ 'gl-opacity-3': isFadedOut(group.name) }" @pipelineActionRequestComplete="$emit('refreshPipelineGraph')" /> - <job-group-dropdown - v-else - :group="group" - :pipeline-id="pipelineId" - :class="{ 'gl-opacity-3': isFadedOut(group.name) }" - /> + <div v-else :class="{ 'gl-opacity-3': isFadedOut(group.name) }"> + <job-group-dropdown :group="group" :pipeline-id="pipelineId" /> + </div> </div> </template> </main-graph-wrapper> diff --git a/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue index 42c6df44b5d..c1de1ab25c1 100644 --- a/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue +++ b/app/assets/javascripts/reports/codequality_report/grouped_codequality_reports_app.vue @@ -62,7 +62,7 @@ export default { helpPath: this.codequalityHelpPath, }); - this.fetchReports(this.glFeatures.codequalityMrDiff); + this.fetchReports(this.glFeatures.codequalityBackendComparison); }, methods: { ...mapActions(['fetchReports', 'setPaths']), diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue index 5da4cae2439..673c6f4a1eb 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue @@ -21,11 +21,11 @@ import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import Tracking from '~/tracking'; import { toggleContainerClasses } from '~/lib/utils/dom_utils'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; -import alertQuery from '../graphql/queries/details.query.graphql'; -import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql'; -import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants'; -import createIssueMutation from '../graphql/mutations/create_issue_from_alert.mutation.graphql'; -import toggleSidebarStatusMutation from '../graphql/mutations/toggle_sidebar_status.mutation.graphql'; +import alertQuery from '../graphql/queries/alert_details.query.graphql'; +import sidebarStatusQuery from '../graphql/queries/alert_sidebar_status.query.graphql'; +import { SEVERITY_LEVELS } from '../constants'; +import createIssueMutation from '../graphql/mutations/alert_issue_create.mutation.graphql'; +import toggleSidebarStatusMutation from '../graphql/mutations/alert_sidebar_status.mutation.graphql'; import SystemNote from './system_notes/system_note.vue'; import AlertSidebar from './alert_sidebar.vue'; import AlertMetrics from './alert_metrics.vue'; @@ -44,7 +44,7 @@ export default { directives: { SafeHtml: GlSafeHtmlDirective, }, - severityLabels: ALERTS_SEVERITY_LABELS, + severityLabels: SEVERITY_LEVELS, tabsConfig: [ { id: 'overview', @@ -89,6 +89,9 @@ export default { projectIssuesPath: { default: '', }, + trackAlertsDetailsViewsOptions: { + default: null, + }, }, apollo: { alert: { @@ -155,7 +158,9 @@ export default { }, }, mounted() { - this.trackPageViews(); + if (this.trackAlertsDetailsViewsOptions) { + this.trackPageViews(); + } toggleContainerClasses(containerEl, { 'issuable-bulk-update-sidebar': true, 'right-sidebar-expanded': true, @@ -217,7 +222,7 @@ export default { return joinPaths(this.projectIssuesPath, issueId); }, trackPageViews() { - const { category, action } = trackAlertsDetailsViewsOptions; + const { category, action } = this.trackAlertsDetailsViewsOptions; Tracking.event(category, action); }, }, diff --git a/app/assets/javascripts/alert_management/components/alert_metrics.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_metrics.vue index dd4faa03c00..dd4faa03c00 100644 --- a/app/assets/javascripts/alert_management/components/alert_metrics.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_metrics.vue diff --git a/app/assets/javascripts/alert_management/components/alert_sidebar.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue index aa98e34d424..12c58b582c5 100644 --- a/app/assets/javascripts/alert_management/components/alert_sidebar.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_sidebar.vue @@ -1,5 +1,5 @@ <script> -import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql'; +import sidebarStatusQuery from '../graphql/queries/alert_sidebar_status.query.graphql'; import SidebarHeader from './sidebar/sidebar_header.vue'; import SidebarTodo from './sidebar/sidebar_todo.vue'; import SidebarStatus from './sidebar/sidebar_status.vue'; diff --git a/app/assets/javascripts/alert_management/components/alert_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue index 3d46ce07b53..5520f7ff045 100644 --- a/app/assets/javascripts/alert_management/components/alert_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_status.vue @@ -2,8 +2,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { s__ } from '~/locale'; import Tracking from '~/tracking'; -import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql'; -import { trackAlertStatusUpdateOptions } from '../constants'; +import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql'; export default { i18n: { @@ -21,6 +20,11 @@ export default { GlDropdown, GlDropdownItem, }, + inject: { + trackAlertStatusUpdateOptions: { + default: null, + }, + }, props: { projectPath: { type: String, @@ -58,7 +62,9 @@ export default { }, }) .then((resp) => { - this.trackStatusUpdate(status); + if (this.trackAlertStatusUpdateOptions) { + this.trackStatusUpdate(status); + } const errors = resp.data?.updateAlertStatus?.errors || []; if (errors[0]) { @@ -81,7 +87,7 @@ export default { }); }, trackStatusUpdate(status) { - const { category, action, label } = trackAlertStatusUpdateOptions; + const { category, action, label } = this.trackAlertStatusUpdateOptions; Tracking.event(category, action, { label, property: status }); }, }, diff --git a/app/assets/javascripts/alert_management/components/alert_summary_row.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_summary_row.vue index 13835b7e2fa..13835b7e2fa 100644 --- a/app/assets/javascripts/alert_management/components/alert_summary_row.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_summary_row.vue diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue index c39a72a45b9..c39a72a45b9 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignee.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue index 2a999b908f9..2a999b908f9 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue index fd40b5d9f65..fd40b5d9f65 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_header.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_header.vue diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue index 0a2bad5510b..0a2bad5510b 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue index 34d34f3d232..216b429fcbe 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_todo.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_todo.vue @@ -4,7 +4,7 @@ import { s__ } from '~/locale'; import Todo from '~/sidebar/components/todo_toggle/todo.vue'; import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql'; import createAlertTodoMutation from '../../graphql/mutations/alert_todo_create.mutation.graphql'; -import alertQuery from '../../graphql/queries/details.query.graphql'; +import alertQuery from '../../graphql/queries/alert_details.query.graphql'; export default { i18n: { diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue index 3705e36a579..3705e36a579 100644 --- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue diff --git a/app/assets/javascripts/vue_shared/alert_details/constants.js b/app/assets/javascripts/vue_shared/alert_details/constants.js new file mode 100644 index 00000000000..56f79410064 --- /dev/null +++ b/app/assets/javascripts/vue_shared/alert_details/constants.js @@ -0,0 +1,29 @@ +import { s__ } from '~/locale'; + +export const SEVERITY_LEVELS = { + CRITICAL: s__('severity|Critical'), + HIGH: s__('severity|High'), + MEDIUM: s__('severity|Medium'), + LOW: s__('severity|Low'), + INFO: s__('severity|Info'), + UNKNOWN: s__('severity|Unknown'), +}; + +export const DEFAULT_PAGE = 'OPERATIONS'; + +/* eslint-disable @gitlab/require-i18n-strings */ +export const PAGE_CONFIG = { + OPERATIONS: { + // Tracks snowplow event when user views alert details + TRACK_ALERTS_DETAILS_VIEWS_OPTIONS: { + category: 'Alert Management', + action: 'view_alert_details', + }, + // Tracks snowplow event when alert status is updated + TRACK_ALERT_STATUS_UPDATE_OPTIONS: { + category: 'Alert Management', + action: 'update_alert_status', + label: 'Status', + }, + }, +}; diff --git a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/fragments/alert_detail_item.fragment.graphql index 9a9ae369519..9a9ae369519 100644 --- a/app/assets/javascripts/alert_management/graphql/fragments/detail_item.fragment.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/fragments/alert_detail_item.fragment.graphql diff --git a/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql index bc4d91a51d1..bc4d91a51d1 100644 --- a/app/assets/javascripts/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql index 63d952a4857..63d952a4857 100644 --- a/app/assets/javascripts/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql diff --git a/app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_sidebar_status.mutation.graphql index f666fcd6782..f666fcd6782 100644 --- a/app/assets/javascripts/alert_management/graphql/mutations/toggle_sidebar_status.mutation.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_sidebar_status.mutation.graphql diff --git a/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.mutation.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql index ac9858c104f..dc961b5eb90 100644 --- a/app/assets/javascripts/alert_management/graphql/mutations/alert_todo_create.mutation.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql @@ -1,4 +1,4 @@ -#import "../fragments/detail_item.fragment.graphql" +#import "../fragments/alert_detail_item.fragment.graphql" mutation alertTodoCreate($projectPath: ID!, $iid: String!) { alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) { diff --git a/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/queries/alert_details.query.graphql index 8881f49b689..5ee2cf7ca44 100644 --- a/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/queries/alert_details.query.graphql @@ -1,4 +1,4 @@ -#import "../fragments/detail_item.fragment.graphql" +#import "../fragments/alert_detail_item.fragment.graphql" query alertDetails($fullPath: ID!, $alertId: String) { project(fullPath: $fullPath) { diff --git a/app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql b/app/assets/javascripts/vue_shared/alert_details/graphql/queries/alert_sidebar_status.query.graphql index 61c570c5cd0..61c570c5cd0 100644 --- a/app/assets/javascripts/alert_management/graphql/queries/sidebar_status.query.graphql +++ b/app/assets/javascripts/vue_shared/alert_details/graphql/queries/alert_sidebar_status.query.graphql diff --git a/app/assets/javascripts/alert_management/details.js b/app/assets/javascripts/vue_shared/alert_details/index.js index 4217b702d0a..643d6b3a3fe 100644 --- a/app/assets/javascripts/alert_management/details.js +++ b/app/assets/javascripts/vue_shared/alert_details/index.js @@ -4,14 +4,15 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import AlertDetails from './components/alert_details.vue'; -import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql'; +import sidebarStatusQuery from './graphql/queries/alert_sidebar_status.query.graphql'; import createRouter from './router'; +import { DEFAULT_PAGE, PAGE_CONFIG } from './constants'; Vue.use(VueApollo); export default (selector) => { const domEl = document.querySelector(selector); - const { alertId, projectPath, projectIssuesPath, projectId } = domEl.dataset; + const { alertId, projectPath, projectIssuesPath, projectId, page = DEFAULT_PAGE } = domEl.dataset; const router = createRouter(); const resolvers = { @@ -48,18 +49,28 @@ export default (selector) => { }, }); + const provide = { + projectPath, + alertId, + projectIssuesPath, + projectId, + }; + + if (page === DEFAULT_PAGE) { + const { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS, TRACK_ALERT_STATUS_UPDATE_OPTIONS } = PAGE_CONFIG[ + page + ]; + provide.trackAlertsDetailsViewsOptions = TRACK_ALERTS_DETAILS_VIEWS_OPTIONS; + provide.trackAlertStatusUpdateOptions = TRACK_ALERT_STATUS_UPDATE_OPTIONS; + } + // eslint-disable-next-line no-new new Vue({ el: selector, components: { AlertDetails, }, - provide: { - projectPath, - alertId, - projectIssuesPath, - projectId, - }, + provide, apolloProvider, router, render(createElement) { diff --git a/app/assets/javascripts/alert_management/router.js b/app/assets/javascripts/vue_shared/alert_details/router.js index 5687fe4e0f5..5687fe4e0f5 100644 --- a/app/assets/javascripts/alert_management/router.js +++ b/app/assets/javascripts/vue_shared/alert_details/router.js diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb index 0f640397320..ff3c24a91a1 100644 --- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb +++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb @@ -16,7 +16,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro result = DependencyProxy::FindOrCreateManifestService.new(group, image, tag, token).execute if result[:status] == :success - send_upload(result[:manifest].file) + response.headers['Docker-Content-Digest'] = result[:manifest].digest + response.headers['Content-Length'] = result[:manifest].size + response.headers['Docker-Distribution-Api-Version'] = DependencyProxy::DISTRIBUTION_API_VERSION + response.headers['Etag'] = "\"#{result[:manifest].digest}\"" + + send_upload(result[:manifest].file, send_params: { type: result[:manifest].content_type }) else render status: result[:http_status], json: result[:message] end diff --git a/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb b/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb new file mode 100644 index 00000000000..003441d4b91 --- /dev/null +++ b/app/controllers/projects/ci/prometheus_metrics/histograms_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Projects + module Ci + module PrometheusMetrics + class HistogramsController < Projects::ApplicationController + feature_category :pipeline_authoring + + respond_to :json, only: [:create] + + def create + result = ::Ci::PrometheusMetrics::ObserveHistogramsService.new(project, permitted_params).execute + + render json: result.payload, status: result.http_status + end + + private + + def permitted_params + params.permit(histograms: [:name, :value]) + end + end + end + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 88f59484cdd..14e4f3e7dd8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -41,7 +41,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:core_security_mr_widget_counts, @project) push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true) push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true) - push_frontend_feature_flag(:codequality_mr_diff, @project) + push_frontend_feature_flag(:codequality_backend_comparison, @project, default_enabled: :yaml) push_frontend_feature_flag(:suggestions_custom_commit, @project) push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml) push_frontend_feature_flag(:paginated_notes, @project, default_enabled: :yaml) diff --git a/app/graphql/types/milestone_state_enum.rb b/app/graphql/types/milestone_state_enum.rb index 588fcb8883a..e3b60395c9b 100644 --- a/app/graphql/types/milestone_state_enum.rb +++ b/app/graphql/types/milestone_state_enum.rb @@ -5,7 +5,7 @@ module Types graphql_name 'MilestoneStateEnum' description 'Current state of milestone' - value 'active', 'Milestone is currently active' - value 'closed', 'Milestone is closed' + value 'active', description: 'Milestone is currently active' + value 'closed', description: 'Milestone is closed' end end diff --git a/app/models/dependency_proxy.rb b/app/models/dependency_proxy.rb index 9cbaf7e9884..0ed17921aaa 100644 --- a/app/models/dependency_proxy.rb +++ b/app/models/dependency_proxy.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module DependencyProxy URL_SUFFIX = '/dependency_proxy/containers' + DISTRIBUTION_API_VERSION = 'registry/2.0' def self.table_name_prefix 'dependency_proxy_' diff --git a/app/models/dependency_proxy/manifest.rb b/app/models/dependency_proxy/manifest.rb index f3c7f34e0d7..d613d5708f0 100644 --- a/app/models/dependency_proxy/manifest.rb +++ b/app/models/dependency_proxy/manifest.rb @@ -12,5 +12,10 @@ class DependencyProxy::Manifest < ApplicationRecord mount_file_store_uploader DependencyProxy::FileUploader - scope :find_or_initialize_by_file_name, ->(file_name) { find_or_initialize_by(file_name: file_name) } + def self.find_or_initialize_by_file_name_or_digest(file_name:, digest:) + result = find_by(file_name: file_name) || find_by(digest: digest) + return result if result + + new(file_name: file_name, digest: digest) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 17645a35af4..73418270a34 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1503,7 +1503,7 @@ class MergeRequest < ApplicationRecord end def has_codequality_reports? - return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project) + return false unless ::Gitlab::Ci::Features.display_codequality_backend_comparison?(project) actual_head_pipeline&.has_reports?(Ci::JobArtifact.codequality_reports) end diff --git a/app/models/user.rb b/app/models/user.rb index b4ec6064ff8..4a2ca64fbe9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -960,8 +960,8 @@ class User < ApplicationRecord end # rubocop: disable CodeReuse/ServiceClass - def refresh_authorized_projects - Users::RefreshAuthorizedProjectsService.new(self).execute + def refresh_authorized_projects(source: nil) + Users::RefreshAuthorizedProjectsService.new(self, source: source).execute end # rubocop: enable CodeReuse/ServiceClass diff --git a/app/services/authorized_project_update/recalculate_for_user_range_service.rb b/app/services/authorized_project_update/recalculate_for_user_range_service.rb index 14b0f5d6117..f300c45f019 100644 --- a/app/services/authorized_project_update/recalculate_for_user_range_service.rb +++ b/app/services/authorized_project_update/recalculate_for_user_range_service.rb @@ -9,7 +9,7 @@ module AuthorizedProjectUpdate def execute User.where(id: start_user_id..end_user_id).select(:id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord - Users::RefreshAuthorizedProjectsService.new(user).execute + Users::RefreshAuthorizedProjectsService.new(user, source: self.class.name).execute end end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index e511e26adfe..678b386fbbf 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -38,10 +38,15 @@ module Ci # mark builds that are retried if latest_statuses.any? - pipeline.latest_statuses - .where(name: latest_statuses.map(&:second)) - .where.not(id: latest_statuses.map(&:first)) - .update_all(retried: true) + updated_count = pipeline.latest_statuses + .where(name: latest_statuses.map(&:second)) + .where.not(id: latest_statuses.map(&:first)) + .update_all(retried: true) + + # This counter is temporary. It will be used to check whether if we still use this method or not + # after setting correct value of `GenericCommitStatus#retried`. + # More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50465#note_491657115 + metrics.legacy_update_jobs_counter.increment if updated_count > 0 end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/ci/prometheus_metrics/observe_histograms_service.rb b/app/services/ci/prometheus_metrics/observe_histograms_service.rb new file mode 100644 index 00000000000..ee22ea75df9 --- /dev/null +++ b/app/services/ci/prometheus_metrics/observe_histograms_service.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Ci + module PrometheusMetrics + class ObserveHistogramsService + class << self + def available_histograms + @available_histograms ||= [ + histogram(:pipeline_graph_link_calculation_duration_seconds, 'Total time spent calculating links, in seconds', {}, [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2]), + histogram(:pipeline_graph_links_total, 'Number of links per graph', {}, [1, 5, 10, 25, 50, 100, 200]), + histogram(:pipeline_graph_links_per_job_ratio, 'Ratio of links to job per graph', {}, [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]) + ].to_h + end + + private + + def histogram(name, *attrs) + [name.to_s, proc { Gitlab::Metrics.histogram(name, *attrs) }] + end + end + + def initialize(project, params) + @project = project + @params = params + end + + def execute + return ServiceResponse.success(http_status: :accepted) unless enabled? + + params + .fetch(:histograms, []) + .each(&method(:observe)) + + ServiceResponse.success(http_status: :created) + end + + private + + attr_reader :project, :params + + def observe(data) + histogram = find_histogram(data[:name]) + histogram.observe({ project: project.full_path }, data[:value].to_f) + end + + def find_histogram(name) + self.class.available_histograms + .fetch(name) { raise ActiveRecord::RecordNotFound } + .call + end + + def enabled? + ::Feature.enabled?(:ci_accept_frontend_prometheus_metrics, project, default_enabled: :yaml) + end + end + end +end diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb index 6b46f5e4c59..ee608d715aa 100644 --- a/app/services/dependency_proxy/find_or_create_manifest_service.rb +++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb @@ -13,7 +13,7 @@ module DependencyProxy def execute @manifest = @group.dependency_proxy_manifests - .find_or_initialize_by_file_name(@file_name) + .find_or_initialize_by_file_name_or_digest(file_name: @file_name, digest: @tag) head_result = DependencyProxy::HeadManifestService.new(@image, @tag, @token).execute @@ -30,6 +30,7 @@ module DependencyProxy def pull_new_manifest DependencyProxy::PullManifestService.new(@image, @tag, @token).execute_with_manifest do |new_manifest| @manifest.update!( + content_type: new_manifest[:content_type], digest: new_manifest[:digest], file: new_manifest[:file], size: new_manifest[:file].size @@ -38,7 +39,9 @@ module DependencyProxy end def cached_manifest_matches?(head_result) - @manifest && @manifest.digest == head_result[:digest] + return false if head_result[:status] == :error + + @manifest && @manifest.digest == head_result[:digest] && @manifest.content_type == head_result[:content_type] end def respond diff --git a/app/services/dependency_proxy/head_manifest_service.rb b/app/services/dependency_proxy/head_manifest_service.rb index 87d9c417c98..ecc3eb77399 100644 --- a/app/services/dependency_proxy/head_manifest_service.rb +++ b/app/services/dependency_proxy/head_manifest_service.rb @@ -2,6 +2,8 @@ module DependencyProxy class HeadManifestService < DependencyProxy::BaseService + ACCEPT_HEADERS = ::ContainerRegistry::Client::ACCEPTED_TYPES.join(',') + def initialize(image, tag, token) @image = image @tag = tag @@ -9,10 +11,10 @@ module DependencyProxy end def execute - response = Gitlab::HTTP.head(manifest_url, headers: auth_headers) + response = Gitlab::HTTP.head(manifest_url, headers: auth_headers.merge(Accept: ACCEPT_HEADERS)) if response.success? - success(digest: response.headers['docker-content-digest']) + success(digest: response.headers['docker-content-digest'], content_type: response.headers['content-type']) else error(response.body, response.code) end diff --git a/app/services/dependency_proxy/pull_manifest_service.rb b/app/services/dependency_proxy/pull_manifest_service.rb index 5c804489fd1..737414c396e 100644 --- a/app/services/dependency_proxy/pull_manifest_service.rb +++ b/app/services/dependency_proxy/pull_manifest_service.rb @@ -11,7 +11,7 @@ module DependencyProxy def execute_with_manifest raise ArgumentError, 'Block must be provided' unless block_given? - response = Gitlab::HTTP.get(manifest_url, headers: auth_headers) + response = Gitlab::HTTP.get(manifest_url, headers: auth_headers.merge(Accept: ::ContainerRegistry::Client::ACCEPTED_TYPES.join(','))) if response.success? file = Tempfile.new @@ -20,7 +20,7 @@ module DependencyProxy file.write(response) file.flush - yield(success(file: file, digest: response.headers['docker-content-digest'])) + yield(success(file: file, digest: response.headers['docker-content-digest'], content_type: response.headers['content-type'])) ensure file.close file.unlink diff --git a/app/services/issue_rebalancing_service.rb b/app/services/issue_rebalancing_service.rb index 4138c6441c8..849afc4edb8 100644 --- a/app/services/issue_rebalancing_service.rb +++ b/app/services/issue_rebalancing_service.rb @@ -17,8 +17,21 @@ class IssueRebalancingService start = RelativePositioning::START_POSITION - (gaps / 2) * gap_size - Issue.transaction do - indexed_ids.each_slice(100) { |pairs| assign_positions(start, pairs) } + if Feature.enabled?(:issue_rebalancing_optimization) + Issue.transaction do + assign_positions(start, indexed_ids) + .sort_by(&:first) + .each_slice(100) do |pairs_with_position| + update_positions(pairs_with_position, 'rebalance issue positions in batches ordered by id') + end + end + else + Issue.transaction do + indexed_ids.each_slice(100) do |pairs| + pairs_with_position = assign_positions(start, pairs) + update_positions(pairs_with_position, 'rebalance issue positions') + end + end end end @@ -32,13 +45,22 @@ class IssueRebalancingService end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord - def assign_positions(start, positions) - values = positions.map do |id, index| - "(#{id}, #{start + (index * gap_size)})" + def assign_positions(start, pairs) + pairs.map do |id, index| + [id, start + (index * gap_size)] + end + end + + def update_positions(pairs_with_position, query_name) + values = pairs_with_position.map do |id, index| + "(#{id}, #{index})" end.join(', ') - Issue.connection.exec_query(<<~SQL, "rebalance issue positions") + run_update_query(values, query_name) + end + + def run_update_query(values, query_name) + Issue.connection.exec_query(<<~SQL, query_name) WITH cte(cte_id, new_pos) AS ( SELECT * FROM (VALUES #{values}) as t (id, pos) @@ -49,7 +71,6 @@ class IssueRebalancingService WHERE cte_id = id SQL end - # rubocop: enable CodeReuse/ActiveRecord def issue_count @issue_count ||= base.count diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index d0939d5a542..24e3fb73370 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -14,13 +14,14 @@ module Users # service = Users::RefreshAuthorizedProjectsService.new(some_user) # service.execute class RefreshAuthorizedProjectsService - attr_reader :user + attr_reader :user, :source LEASE_TIMEOUT = 1.minute.to_i # user - The User for which to refresh the authorized projects. - def initialize(user, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil) + def initialize(user, source: nil, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil) @user = user + @source = source @incorrect_auth_found_callback = incorrect_auth_found_callback @missing_auth_found_callback = missing_auth_found_callback @@ -91,6 +92,8 @@ module Users # remove - The IDs of the authorization rows to remove. # add - Rows to insert in the form `[user id, project id, access level]` def update_authorizations(remove = [], add = []) + log_refresh_details(remove.length, add.length) + User.transaction do user.remove_project_authorizations(remove) unless remove.empty? ProjectAuthorization.insert_authorizations(add) unless add.empty? @@ -101,6 +104,13 @@ module Users user.reset end + def log_refresh_details(rows_deleted, rows_added) + Gitlab::AppJsonLogger.info(event: 'authorized_projects_refresh', + 'authorized_projects_refresh.source': source, + 'authorized_projects_refresh.rows_deleted': rows_deleted, + 'authorized_projects_refresh.rows_added': rows_added) + end + def fresh_access_levels_per_project fresh_authorizations.each_with_object({}) do |row, hash| hash[row.project_id] = row.access_level diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index a14f75259ec..802df664241 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -24,9 +24,9 @@ .control = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do - = search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control gl-form-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full gl-inset-border-1-gray-200!', spellcheck: false } + = search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control gl-form-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full', spellcheck: false } .control.d-none.d-md-block - = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn gl-button btn-svg' do + = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn gl-button btn-default btn-icon' do = sprite_icon('rss', css_class: 'qa-rss-icon') = render_if_exists 'projects/commits/mirror_status' diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index f5132459131..6e07d6d0f71 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -25,7 +25,7 @@ class AuthorizedProjectsWorker def perform(user_id) user = User.find_by(id: user_id) - user&.refresh_authorized_projects + user&.refresh_authorized_projects(source: self.class.name) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/changelogs/unreleased/290944-pull-by-digest.yml b/changelogs/unreleased/290944-pull-by-digest.yml new file mode 100644 index 00000000000..eea16bb359b --- /dev/null +++ b/changelogs/unreleased/290944-pull-by-digest.yml @@ -0,0 +1,5 @@ +--- +title: Pull-by-digest and Docker 20.x support for the Dependency Proxy +merge_request: 52805 +author: +type: changed diff --git a/changelogs/unreleased/btn-default-rss.yml b/changelogs/unreleased/btn-default-rss.yml new file mode 100644 index 00000000000..8b9e86677be --- /dev/null +++ b/changelogs/unreleased/btn-default-rss.yml @@ -0,0 +1,5 @@ +--- +title: Apply new GitLab UI for RSS button in commits page +merge_request: 52900 +author: Yogi (@yo) +type: other diff --git a/config/feature_flags/development/ci_accept_frontend_prometheus_metrics.yml b/config/feature_flags/development/ci_accept_frontend_prometheus_metrics.yml new file mode 100644 index 00000000000..b795b2505d8 --- /dev/null +++ b/config/feature_flags/development/ci_accept_frontend_prometheus_metrics.yml @@ -0,0 +1,8 @@ +--- +name: ci_accept_frontend_prometheus_metrics +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52820 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300770 +milestone: '13.9' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/config/feature_flags/development/codequality_backend_comparison.yml b/config/feature_flags/development/codequality_backend_comparison.yml new file mode 100644 index 00000000000..f7c3c14f674 --- /dev/null +++ b/config/feature_flags/development/codequality_backend_comparison.yml @@ -0,0 +1,8 @@ +--- +name: codequality_backend_comparison +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53068 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300796 +milestone: '13.9' +type: development +group: group::testing +default_enabled: false diff --git a/config/feature_flags/development/issue_rebalancing_optimization.yml b/config/feature_flags/development/issue_rebalancing_optimization.yml new file mode 100644 index 00000000000..abaeb53f63d --- /dev/null +++ b/config/feature_flags/development/issue_rebalancing_optimization.yml @@ -0,0 +1,8 @@ +--- +name: issue_rebalancing_optimization +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53384 +rollout_issue_url: +milestone: '13.9' +type: development +group: group::project management +default_enabled: false diff --git a/config/routes/project.rb b/config/routes/project.rb index c74d0c19d35..97834894ad1 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -87,6 +87,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do resource :lint, only: [:show, :create] resource :pipeline_editor, only: [:show], controller: :pipeline_editor, path: 'editor' resources :daily_build_group_report_results, only: [:index], constraints: { format: /(csv|json)/ } + namespace :prometheus_metrics do + resources :histograms, only: [:create], constraints: { format: 'json' } + end end namespace :settings do diff --git a/db/migrate/20210128140157_add_content_type_to_dependency_proxy_manifests.rb b/db/migrate/20210128140157_add_content_type_to_dependency_proxy_manifests.rb new file mode 100644 index 00000000000..d016e3c20e2 --- /dev/null +++ b/db/migrate/20210128140157_add_content_type_to_dependency_proxy_manifests.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddContentTypeToDependencyProxyManifests < ActiveRecord::Migration[6.0] + DOWNTIME = false + + # rubocop:disable Migration/AddLimitToTextColumns + # limit is added in 20210128140232_add_text_limit_to_dependency_proxy_manifests_content_type.rb + def change + add_column :dependency_proxy_manifests, :content_type, :text + end + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20210128140232_add_text_limit_to_dependency_proxy_manifests_content_type.rb b/db/migrate/20210128140232_add_text_limit_to_dependency_proxy_manifests_content_type.rb new file mode 100644 index 00000000000..035e4795ce0 --- /dev/null +++ b/db/migrate/20210128140232_add_text_limit_to_dependency_proxy_manifests_content_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddTextLimitToDependencyProxyManifestsContentType < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_text_limit :dependency_proxy_manifests, :content_type, 255 + end + + def down + remove_text_limit :dependency_proxy_manifests, :content_type + end +end diff --git a/db/schema_migrations/20210128140157 b/db/schema_migrations/20210128140157 new file mode 100644 index 00000000000..2b71db39920 --- /dev/null +++ b/db/schema_migrations/20210128140157 @@ -0,0 +1 @@ +0fa84b2038f33e27e549bdb3eb137e1813f604b6e81abc67a49a54d3e1e4bcf5
\ No newline at end of file diff --git a/db/schema_migrations/20210128140232 b/db/schema_migrations/20210128140232 new file mode 100644 index 00000000000..ee335323fce --- /dev/null +++ b/db/schema_migrations/20210128140232 @@ -0,0 +1 @@ +93f337364eb5ca5c67f4d4767d1aee9972bfe0596c89f006317dd6103558e35c
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 00ec06d7d6f..017ef14931f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11775,7 +11775,9 @@ CREATE TABLE dependency_proxy_manifests ( file_name text NOT NULL, file text NOT NULL, digest text NOT NULL, + content_type text, CONSTRAINT check_079b293a7b CHECK ((char_length(file) <= 255)), + CONSTRAINT check_167a9a8a91 CHECK ((char_length(content_type) <= 255)), CONSTRAINT check_c579e3f586 CHECK ((char_length(file_name) <= 255)), CONSTRAINT check_f5d9996bf1 CHECK ((char_length(digest) <= 255)) ); diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 56afac192a6..6512879e8b8 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -4,7 +4,7 @@ group: Compliance info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Audit Events **(STARTER)** +# Audit Events **(PREMIUM)** GitLab offers a way to view the changes made within the GitLab server for owners and administrators on a [paid plan](https://about.gitlab.com/pricing/). @@ -42,7 +42,7 @@ There are two kinds of events logged: Impersonation is where an administrator uses credentials to perform an action as a different user. -### Group events **(STARTER)** +### Group events **(PREMIUM)** A user with a Owner role (or above) can retrieve group audit events of all users. A user with a Developer or Maintainer role is limited to group audit events based on their individual actions. @@ -72,7 +72,7 @@ From there, you can see the following actions: Group events can also be accessed via the [Group Audit Events API](../api/audit_events.md#group-audit-events) -### Project events **(STARTER)** +### Project events **(PREMIUM)** A user with a Maintainer role (or above) can retrieve project audit events of all users. A user with a Developer role is limited to project audit events based on their individual actions. diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 556b3133a02..cd26ef1aef2 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -119,6 +119,9 @@ The following metrics are available: | `gitlab_external_http_duration_seconds` | Counter | 13.8 | Duration in seconds spent on each HTTP call to external systems | | | `gitlab_external_http_exception_total` | Counter | 13.8 | Total number of exceptions raised when making external HTTP calls | | | `ci_report_parser_duration_seconds` | Histogram | 13.9 | Time to parse CI/CD report artifacts | `parser` | +| `pipeline_graph_link_calculation_duration_seconds` | Histogram | 13.9 | Total time spent calculating links, in seconds | `project` | +| `pipeline_graph_links_total` | Histogram | 13.9 | Number of links per graph | `project` | +| `pipeline_graph_links_per_job_ratio` | Histogram | 13.9 | Ratio of links to job per graph | `project` | ## Metrics controlled by a feature flag diff --git a/doc/administration/wikis/index.md b/doc/administration/wikis/index.md index 36ce5fe1111..57bbe913216 100644 --- a/doc/administration/wikis/index.md +++ b/doc/administration/wikis/index.md @@ -1,7 +1,7 @@ --- type: reference, howto stage: Create -group: Knowledge +group: Editor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/api/audit_events.md b/doc/api/audit_events.md index 8867b0373e5..78bf292a3d7 100644 --- a/doc/api/audit_events.md +++ b/doc/api/audit_events.md @@ -126,7 +126,7 @@ Example response: } ``` -## Group Audit Events **(STARTER)** +## Group Audit Events **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34078) in GitLab 12.5. @@ -233,7 +233,7 @@ Example response: } ``` -## Project Audit Events **(STARTER)** +## Project Audit Events **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219238) in GitLab 13.1. diff --git a/doc/api/dependency_proxy.md b/doc/api/dependency_proxy.md index c1745e4e0bb..8448edef9c5 100644 --- a/doc/api/dependency_proxy.md +++ b/doc/api/dependency_proxy.md @@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Free](https://about.gitlab.com/pricing/) in GitLab 13.6. -Deletes the cached manifests and blobs for a group. This endpoint requires group admin access. +Deletes the cached manifests and blobs for a group. This endpoint requires group owner access. WARNING: [A bug exists](https://gitlab.com/gitlab-org/gitlab/-/issues/277161) for this API. diff --git a/doc/api/group_wikis.md b/doc/api/group_wikis.md index 50c1ce5bd84..6c5e2b77f93 100644 --- a/doc/api/group_wikis.md +++ b/doc/api/group_wikis.md @@ -1,6 +1,6 @@ --- stage: Create -group: Knowledge +group: Editor info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments" type: reference, api --- diff --git a/doc/api/tags.md b/doc/api/tags.md index 126bc010022..262f5eb0010 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -123,7 +123,7 @@ Parameters: | `tag_name` | string | yes | The name of a tag | | `ref` | string | yes | Create tag using commit SHA, another tag name, or branch name | | `message` | string | no | Creates annotated tag | -| `release_description` | string | no | Add release notes to the Git tag and store it in the GitLab database | +| `release_description` | string | no | This parameter is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/tags?tag_name=test&ref=master" @@ -186,6 +186,11 @@ Parameters: ## Create a new release +WARNING: +This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) +for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) +in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. + Add release notes to the existing Git tag. If there already exists a release for the given tag, status code `409` is returned. @@ -221,6 +226,11 @@ Response: ## Update a release +WARNING: +This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) +for use in GitLab 11.7, and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/290311) +in GitLab 14.0. Use the [Releases API](../api/releases/index.md) instead. + Updates the release notes of a given release. ```plaintext diff --git a/doc/api/wikis.md b/doc/api/wikis.md index 0e423564da2..43587da9473 100644 --- a/doc/api/wikis.md +++ b/doc/api/wikis.md @@ -1,6 +1,6 @@ --- stage: Create -group: Knowledge +group: Editor info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments" type: reference, api --- diff --git a/doc/ci/migration/jenkins.md b/doc/ci/migration/jenkins.md index 71d5e6eb859..8392bfa50fa 100644 --- a/doc/ci/migration/jenkins.md +++ b/doc/ci/migration/jenkins.md @@ -209,7 +209,7 @@ as well as encourage inner sourcing. In self-managed GitLab instances, you can build an [Instance Template Repository](../../user/admin_area/settings/instance_template_repository.md). Development teams across the whole organization can select templates from a dropdown menu. -A group administrator is able to set a group to use as the source for the +A group maintainer or a group owner is able to set a group to use as the source for the [custom project templates](../../user/admin_area/custom_project_templates.md), which can be used by all projects in the group. An instance administrator can set a group as the source for [instance project templates](../../user/group/custom_project_templates.md), diff --git a/doc/ci/troubleshooting.md b/doc/ci/troubleshooting.md index 2ff685ba6e9..cddcf76236a 100644 --- a/doc/ci/troubleshooting.md +++ b/doc/ci/troubleshooting.md @@ -262,6 +262,23 @@ To [prevent duplicate pipelines](yaml/README.md#prevent-duplicate-pipelines), us [`workflow: rules`](yaml/README.md#workflowrules) or rewrite your rules to control which pipelines can run. +### Console workaround if job using resource_group gets stuck + +```ruby +# find resource group by name +resource_group = Project.find_by_full_path('...').resource_groups.find_by(key: 'the-group-name') +busy_resources = resource_group.resources.where('build_id IS NOT NULL') + +# identify which builds are occupying the resource +# (I think it should be 1 as of today) +busy_resources.pluck(:build_id) + +# it's good to check why this build is holding the resource. +# Is it stuck? Has it been forcefully dropped by the system? +# free up busy resources +busy_resources.update_all(build_id: nil) +``` + ## How to get help If you are unable to resolve pipeline issues, you can get help from: diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index c044decfe36..baee01b8245 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -997,7 +997,8 @@ To link to internal documentation: - Use relative links to Markdown files in the same repository. - Do not use absolute URLs or URLs from `docs.gitlab.com`. - Use `../` to navigate to higher-level directories. -- Don't prepend `./` to links to files or directories. +- Don't prepend `./` to links to files or directories. To link to a file in the + same directory or one of its sub-directories, use the syntax `path/to/file.md`. - Don't link relative to root. For example, `/ee/user/gitlab_com/index.md`. Don't: @@ -1022,6 +1023,7 @@ To link to internal documentation: - `../../merge_requests/index.md` - `../../issues/tags.md` - `../../issues/tags.md#stages` + - `issues/tags.md` NOTE: Using the Markdown extension is necessary for the [`/help`](../index.md#gitlab-help) diff --git a/doc/development/wikis.md b/doc/development/wikis.md index 1984e77866d..7995afb1e17 100644 --- a/doc/development/wikis.md +++ b/doc/development/wikis.md @@ -1,7 +1,7 @@ --- type: reference, dev stage: Create -group: Knowledge +group: Editor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments description: "GitLab's development guidelines for Wikis" --- diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md index e5116a8a0bd..463f8a6efaa 100644 --- a/doc/integration/jira_development_panel.md +++ b/doc/integration/jira_development_panel.md @@ -56,7 +56,7 @@ If you're using: - Self-managed GitLab, self-managed Jira, or both, configure the integration using [Jira's DVCS Connector](#jira-dvcs-configuration), which syncs data hourly. -We recommend that a GitLab group administrator or instance administrator (in the case of +We recommend that a GitLab group maintainer or group owner, or instance administrator (in the case of self-managed GitLab) set up the integration to simplify administration. ### Jira DVCS configuration diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index a50ccc71b8f..7d77e7a6925 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -242,7 +242,7 @@ For each runner, the following attributes are listed: | Projects | Projects to which the runner is assigned | | Jobs | Total of jobs run by the runner | | Tags | Tags associated with the runner | -| Last contact | Timestamp indicating when the GitLab instance last contacted the runner | +| Last contact | Timestamp indicating when the runner last contacted the GitLab instance | You can also edit, pause, or remove each runner. diff --git a/doc/user/analytics/code_review_analytics.md b/doc/user/analytics/code_review_analytics.md index 745774480b0..7f052aec9cd 100644 --- a/doc/user/analytics/code_review_analytics.md +++ b/doc/user/analytics/code_review_analytics.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w --- -# Code Review Analytics **(STARTER)** +# Code Review Analytics **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38062) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.7. diff --git a/doc/user/analytics/merge_request_analytics.md b/doc/user/analytics/merge_request_analytics.md index c0fe19f1f16..6dd50a3be80 100644 --- a/doc/user/analytics/merge_request_analytics.md +++ b/doc/user/analytics/merge_request_analytics.md @@ -5,7 +5,7 @@ group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Merge Request Analytics **(STARTER)** +# Merge Request Analytics **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229045) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3. diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md index 0dbd7af1214..09e899a61ba 100644 --- a/doc/user/group/contribution_analytics/index.md +++ b/doc/user/group/contribution_analytics/index.md @@ -4,7 +4,7 @@ stage: Manage group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Contribution Analytics **(STARTER)** +# Contribution Analytics **(PREMIUM)** > - Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3090) for subgroups in GitLab 12.2. diff --git a/doc/user/group/index.md b/doc/user/group/index.md index bcfc31b3caa..208c64d0406 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -560,7 +560,7 @@ You can change settings that are specific to repositories in your group. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43290) in GitLab 13.6. By default, when you create a new project in GitLab, the initial branch is called `master`. -For groups, a group administrator can customize the initial branch name to something +For groups, a group owner can customize the initial branch name to something else. This way, every new project created under that group from then on will start from the custom branch name rather than `master`. To do so: 1. Go to the **Group page > Settings > Repository** and expand **Default initial diff --git a/doc/user/group/saml_sso/group_managed_accounts.md b/doc/user/group/saml_sso/group_managed_accounts.md index 9f7ba638b92..aee491fa47b 100644 --- a/doc/user/group/saml_sso/group_managed_accounts.md +++ b/doc/user/group/saml_sso/group_managed_accounts.md @@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w WARNING: This [Closed Beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#sts=Closed%20Beta) feature is being re-evaluated in favor of a different [identity model](https://gitlab.com/groups/gitlab-org/-/epics/4345) that does not require separate accounts. -We recommend that group administrators who haven't yet implemented this feature wait for +We recommend that group owners who haven't yet implemented this feature wait for the new solution. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/709) in GitLab 12.1. diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index 0f77d9d7dd3..fd75df513c7 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -7,9 +7,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Dependency Proxy > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6. -> - [Support for private groups](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. -> - Anonymous access to images in public groups is no longer available starting in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Free](https://about.gitlab.com/pricing/) in GitLab 13.6. +> - [Support for private groups](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. +> - Anonymous access to images in public groups is no longer available starting in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. +> - [Support for pull-by-digest and Docker version 20.x](https://gitlab.com/gitlab-org/gitlab/-/issues/290944) in [GitLab Free](https://about.gitlab.com/pricing/) 13.9. The GitLab Dependency Proxy is a local proxy you can use for your frequently-accessed upstream images. @@ -17,11 +18,6 @@ upstream images. In the case of CI/CD, the Dependency Proxy receives a request and returns the upstream image from a registry, acting as a pull-through cache. -NOTE: -The Dependency Proxy is not compatible with Docker version 20.x and later. -If you are using the Dependency Proxy, Docker version 19.x.x is recommended until -[issue #290944](https://gitlab.com/gitlab-org/gitlab/-/issues/290944) is resolved. - ## Prerequisites The Dependency Proxy must be [enabled by an administrator](../../../administration/packages/dependency_proxy.md). @@ -60,7 +56,7 @@ Prerequisites: ### Authenticate with the Dependency Proxy -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. > - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default. > - It's enabled on GitLab.com. > - It's recommended for production use. @@ -162,7 +158,7 @@ the [Dependency Proxy API](../../../api/dependency_proxy.md). ## Docker Hub rate limits and the Dependency Proxy -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241639) in [GitLab Core](https://about.gitlab.com/pricing/) 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241639) in [GitLab Free](https://about.gitlab.com/pricing/) 13.7. <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch how to [use the Dependency Proxy to help avoid Docker Hub rate limits](https://youtu.be/Nc4nUo7Pq08). diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index 45478e6b7a3..5805fb29931 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -68,7 +68,7 @@ To use the GitLab endpoint for NuGet Packages, choose an option: - **Project-level**: Use when you have few NuGet packages and they are not in the same GitLab group. -- **Group-level**: Use when you have many NuGet packages in different within the +- **Group-level**: Use when you have many NuGet packages in different projects within the same GitLab group. Some features such as [publishing](#publish-a-nuget-package) a package are only available on the project-level endpoint. diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index a7148596488..8dc5c3396ae 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -1,6 +1,6 @@ --- -stage: Create -group: Knowledge +stage: Plan +group: Product Planning info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments" --- diff --git a/doc/user/project/pages/pages_access_control.md b/doc/user/project/pages/pages_access_control.md index a2a17a4f2ca..2e0fc87b3df 100644 --- a/doc/user/project/pages/pages_access_control.md +++ b/doc/user/project/pages/pages_access_control.md @@ -38,7 +38,9 @@ For a demonstration, see [Pages access controls](https://www.youtube.com/watch?v - **Only project members**: Only project members are able to browse the website. - **Everyone with access**: Everyone, both logged into and logged out of GitLab, is able to browse the website, no matter their project membership. -1. Click **Save changes**. +1. Click **Save changes**. Note that your changes may not take effect immediately. GitLab Pages uses + a caching mechanism for efficiency. Your changes may not take effect until that cache is + invalidated, which usually takes less than a minute. The next time someone tries to access your website and the access control is enabled, they're presented with a page to sign into GitLab and verify they diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index c7e77c96c7d..aa41f1c0233 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -266,7 +266,7 @@ This action: - Deletes a project including all associated resources (issues, merge requests etc). - From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium](https://about.gitlab.com/pricing/) or higher tiers, -group administrators can [configure](../../group/index.md#enabling-delayed-project-removal) projects within a group +group owners can [configure](../../group/index.md#enabling-delayed-project-removal) projects within a group to be deleted after a delayed period. When enabled, actual deletion happens after number of days specified in [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-delay). diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index 3059c53cf44..087a9069c7c 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -1,6 +1,6 @@ --- stage: Create -group: Knowledge +group: Editor info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments" type: reference, how-to --- diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 54c393d8167..7155b60416b 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -71,6 +71,10 @@ module Gitlab def self.display_quality_on_mr_diff?(project) ::Feature.enabled?(:codequality_mr_diff, project, default_enabled: false) end + + def self.display_codequality_backend_comparison?(project) + ::Feature.enabled?(:codequality_backend_comparison, project, default_enabled: :yaml) + end end end end diff --git a/lib/gitlab/ci/pipeline/metrics.rb b/lib/gitlab/ci/pipeline/metrics.rb index db6cca27f1c..c77f4dcca5a 100644 --- a/lib/gitlab/ci/pipeline/metrics.rb +++ b/lib/gitlab/ci/pipeline/metrics.rb @@ -45,6 +45,15 @@ module Gitlab Gitlab::Metrics.counter(name, comment) end end + + def legacy_update_jobs_counter + strong_memoize(:legacy_update_jobs_counter) do + name = :ci_legacy_update_jobs_as_retried_total + comment = 'Counter of occurrences when jobs were not being set as retried before update_retried' + + Gitlab::Metrics.counter(name, comment) + end + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index aa9c306649a..1bd254b4794 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2483,9 +2483,6 @@ msgstr "" msgid "AlertManagement|Create incident" msgstr "" -msgid "AlertManagement|Critical" -msgstr "" - msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents." msgstr "" @@ -2498,24 +2495,12 @@ msgstr "" msgid "AlertManagement|Events" msgstr "" -msgid "AlertManagement|High" -msgstr "" - msgid "AlertManagement|Incident" msgstr "" -msgid "AlertManagement|Info" -msgstr "" - msgid "AlertManagement|Key" msgstr "" -msgid "AlertManagement|Low" -msgstr "" - -msgid "AlertManagement|Medium" -msgstr "" - msgid "AlertManagement|Metrics" msgstr "" @@ -2597,9 +2582,6 @@ msgstr "" msgid "AlertManagement|Triggered" msgstr "" -msgid "AlertManagement|Unknown" -msgstr "" - msgid "AlertManagement|Value" msgstr "" @@ -24544,6 +24526,9 @@ msgstr "" msgid "Reports|Actions" msgstr "" +msgid "Reports|Activity" +msgstr "" + msgid "Reports|An error occured while loading report" msgstr "" @@ -25805,6 +25790,9 @@ msgstr "" msgid "SecurityReports|Add projects to your group" msgstr "" +msgid "SecurityReports|All" +msgstr "" + msgid "SecurityReports|Comment added to '%{vulnerabilityName}'" msgstr "" @@ -25883,6 +25871,12 @@ msgstr "" msgid "SecurityReports|More information" msgstr "" +msgid "SecurityReports|No activity" +msgstr "" + +msgid "SecurityReports|No longer detected" +msgstr "" + msgid "SecurityReports|No vulnerabilities found" msgstr "" @@ -26008,6 +26002,9 @@ msgstr "" msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly." msgstr "" +msgid "SecurityReports|With issues" +msgstr "" + msgid "SecurityReports|Won't fix / Accept risk" msgstr "" diff --git a/package.json b/package.json index 510a08c30b1..88554004e8a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "1.182.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "27.1.5", + "@gitlab/ui": "27.4.3", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-4", "@rails/ujs": "^6.0.3-4", diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb index 39cbdfb9123..2056feb6434 100644 --- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb +++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb @@ -130,7 +130,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote token request' do + it 'proxies status from the remote token request', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:service_unavailable) @@ -147,7 +147,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote manifest request' do + it 'proxies status from the remote manifest request', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:bad_request) @@ -156,15 +156,19 @@ RSpec.describe Groups::DependencyProxyForContainersController do end it 'sends a file' do - expect(controller).to receive(:send_file).with(manifest.file.path, {}) + expect(controller).to receive(:send_file).with(manifest.file.path, type: manifest.content_type) subject end - it 'returns Content-Disposition: attachment' do + it 'returns Content-Disposition: attachment', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) + expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest) + expect(response.headers['Content-Length']).to eq(manifest.size) + expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION) + expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"") expect(response.headers['Content-Disposition']).to match(/^attachment/) end end @@ -207,7 +211,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do } end - it 'proxies status from the remote blob request' do + it 'proxies status from the remote blob request', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:bad_request) @@ -221,7 +225,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do subject end - it 'returns Content-Disposition: attachment' do + it 'returns Content-Disposition: attachment', :aggregate_failures do subject expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/factories/dependency_proxy.rb b/spec/factories/dependency_proxy.rb index de95df19876..94a7986a8fa 100644 --- a/spec/factories/dependency_proxy.rb +++ b/spec/factories/dependency_proxy.rb @@ -10,7 +10,8 @@ FactoryBot.define do factory :dependency_proxy_manifest, class: 'DependencyProxy::Manifest' do group file { fixture_file_upload('spec/fixtures/dependency_proxy/manifest') } - digest { 'sha256:5ab5a6872b264fe4fd35d63991b9b7d8425f4bc79e7cf4d563c10956581170c9' } + digest { 'sha256:d0710affa17fad5f466a70159cc458227bd25d4afb39514ef662ead3e6c99515' } file_name { 'alpine:latest.json' } + content_type { 'application/vnd.docker.distribution.manifest.v2+json' } end end diff --git a/spec/fixtures/dependency_proxy/manifest b/spec/fixtures/dependency_proxy/manifest index a899d05d697..ed543883d60 100644 --- a/spec/fixtures/dependency_proxy/manifest +++ b/spec/fixtures/dependency_proxy/manifest @@ -1,38 +1,16 @@ { - "schemaVersion": 1, - "name": "library/alpine", - "tag": "latest", - "architecture": "amd64", - "fsLayers": [ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 1472, + "digest": "sha256:7731472c3f2a25edbb9c085c78f42ec71259f2b83485aa60648276d408865839" + }, + "layers": [ { - "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" - }, - { - "blobSum": "sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964" - } - ], - "history": [ - { - "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"container\":\"8c59eb170e19b8c3768b8d06c91053b0debf4a6fa6a452df394145fe9b885ea5\",\"container_config\":{\"Hostname\":\"8c59eb170e19\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"/bin/sh\\\"]\"],\"ArgsEscaped\":true,\"Image\":\"sha256:3543079adc6fb5170279692361be8b24e89ef1809a374c1b4429e1d560d1459c\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2020-10-22T02:19:24.499382102Z\",\"docker_version\":\"18.09.7\",\"id\":\"c5f1aab5bb88eaf1aa62bea08ea6654547d43fd4d15b1a476c77e705dd5385ba\",\"os\":\"linux\",\"parent\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"throwaway\":true}" - }, - { - "v1Compatibility": "{\"id\":\"dc0b50cc52bc340d7848a62cfe8a756f4420592f4984f7a680ef8f9d258176ed\",\"created\":\"2020-10-22T02:19:24.33416307Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:f17f65714f703db9012f00e5ec98d0b2541ff6147c2633f7ab9ba659d0c507f4 in / \"]}}" - } - ], - "signatures": [ - { - "header": { - "jwk": { - "crv": "P-256", - "kid": "XOTE:DZ4C:YBPJ:3O3L:YI4B:NYXU:T4VR:USH6:CXXN:SELU:CSCC:FVPE", - "kty": "EC", - "x": "cR1zye_3354mdbD7Dn-mtXNXvtPtmLlUVDa5vH6Lp74", - "y": "rldUXSllLit6_2BW6AV8aqkwWJXHoYPG9OwkIBouwxQ" - }, - "alg": "ES256" - }, - "signature": "DYB2iB-XKIisqp5Q0OXFOBIOlBOuRV7pnZuKy0cxVB2Qj1VFRhWX4Tq336y0VMWbF6ma1he5A1E_Vk4jazrJ9g", - "protected": "eyJmb3JtYXRMZW5ndGgiOjIxMzcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMC0xMS0yNFQyMjowMTo1MVoifQ" + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2810825, + "digest": "sha256:596ba82af5aaa3e2fd9d6f955b8b94f0744a2b60710e3c243ba3e4a467f051d1" } ] }
\ No newline at end of file diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index 0cc3d565e10..21217e6c608 100644 --- a/spec/frontend/alert_management/components/alert_management_table_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -2,11 +2,11 @@ import { mount } from '@vue/test-utils'; import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar } from '@gitlab/ui'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import mockAlerts from 'jest/vue_shared/alert_details/mocks/alerts.json'; import { visitUrl } from '~/lib/utils/url_utility'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import AlertManagementTable from '~/alert_management/components/alert_management_table.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import mockAlerts from '../mocks/alerts.json'; import defaultProvideValues from '../mocks/alerts_provide_config.json'; jest.mock('~/lib/utils/url_utility', () => ({ diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap index ef68a6a2c32..66e3f180d92 100644 --- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap +++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap @@ -1,72 +1,307 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AlertsSettingsFormNew with default values renders the initial template 1`] = ` -"<form class=\\"gl-mt-6\\"> - <h5 class=\\"gl-font-lg gl-my-5\\">Add new integrations</h5> - <div id=\\"integration-type\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-type__BV_label_\\" for=\\"integration-type\\" class=\\"d-block col-form-label\\">1. Select integration type</label> - <div class=\\"bv-no-focus-ring\\"><select class=\\"gl-form-select mw-100 custom-select\\" id=\\"__BVID__8\\"> - <option value=\\"\\">Select integration type</option> - <option value=\\"HTTP\\">HTTP Endpoint</option> - <option value=\\"PROMETHEUS\\">External Prometheus</option> +<form + class="gl-mt-6" +> + <h5 + class="gl-font-lg gl-my-5" + > + Add new integrations + </h5> + + <div + class="form-group gl-form-group" + id="integration-type" + role="group" + > + <label + class="d-block col-form-label" + for="integration-type" + id="integration-type__BV_label_" + > + 1. Select integration type + </label> + <div + class="bv-no-focus-ring" + > + <select + class="gl-form-select mw-100 custom-select" + id="__BVID__8" + > + <option + value="" + > + Select integration type + </option> + <option + value="HTTP" + > + HTTP Endpoint + </option> + <option + value="PROMETHEUS" + > + External Prometheus + </option> </select> + <!----> <!----> <!----> <!----> </div> </div> - <transition-stub css=\\"true\\" enterclass=\\"\\" leaveclass=\\"collapse show\\" entertoclass=\\"collapse show\\" leavetoclass=\\"collapse\\" enteractiveclass=\\"collapsing\\" leaveactiveclass=\\"collapsing\\" class=\\"gl-mt-3\\"> - <div class=\\"collapse\\" style=\\"display: none;\\" id=\\"__BVID__10\\"> + + <transition-stub + class="gl-mt-3" + css="true" + enteractiveclass="collapsing" + enterclass="" + entertoclass="collapse show" + leaveactiveclass="collapsing" + leaveclass="collapse show" + leavetoclass="collapse" + > + <div + class="collapse" + id="__BVID__10" + style="display: none;" + > <div> - <div id=\\"name-integration\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"name-integration__BV_label_\\" for=\\"name-integration\\" class=\\"d-block col-form-label\\">2. Name integration</label> - <div class=\\"bv-no-focus-ring\\"><input type=\\"text\\" placeholder=\\"Enter integration name\\" class=\\"gl-form-input form-control\\" id=\\"__BVID__15\\"> + <div + class="form-group gl-form-group" + id="name-integration" + role="group" + > + <label + class="d-block col-form-label" + for="name-integration" + id="name-integration__BV_label_" + > + 2. Name integration + </label> + <div + class="bv-no-focus-ring" + > + <input + class="gl-form-input form-control" + id="__BVID__15" + placeholder="Enter integration name" + type="text" + /> <!----> <!----> <!----> </div> </div> - <div id=\\"integration-webhook\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-webhook__BV_label_\\" for=\\"integration-webhook\\" class=\\"d-block col-form-label\\">3. Set up webhook</label> - <div class=\\"bv-no-focus-ring\\"><span>Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html\\" class=\\"gl-link gl-display-inline-block\\">GitLab documentation</a> to learn more about configuring your endpoint.</span> <label class=\\"gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal\\"> - <div class=\\"gl-toggle-wrapper\\"><span class=\\"gl-toggle-label\\">Active</span> - <!----> <button aria-label=\\"Active\\" type=\\"button\\" class=\\"gl-toggle\\"><span class=\\"toggle-icon\\"><svg data-testid=\\"close-icon\\" aria-hidden=\\"true\\" class=\\"gl-icon s16\\"><use href=\\"#close\\"></use></svg></span></button></div> + + <div + class="form-group gl-form-group" + id="integration-webhook" + role="group" + > + <label + class="d-block col-form-label" + for="integration-webhook" + id="integration-webhook__BV_label_" + > + 3. Set up webhook + </label> + <div + class="bv-no-focus-ring" + > + <span> + Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the + <a + class="gl-link gl-display-inline-block" + href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html" + rel="noopener noreferrer" + target="_blank" + > + GitLab documentation + </a> + to learn more about configuring your endpoint. + </span> + + <label + class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal" + > + <span + class="gl-toggle-wrapper" + > + <span + class="gl-toggle-label" + data-testid="toggle-label" + > + Active + </span> + + <!----> + + <button + aria-label="Active" + class="gl-toggle" + role="switch" + type="button" + > + <span + class="toggle-icon" + > + <svg + aria-hidden="true" + class="gl-icon s16" + data-testid="close-icon" + > + <use + href="#close" + /> + </svg> + </span> + </button> + </span> + <!----> </label> + <!----> - <div class=\\"gl-my-4\\"><span class=\\"gl-font-weight-bold\\"> + + <div + class="gl-my-4" + > + <span + class="gl-font-weight-bold" + > + Webhook URL - </span> - <div id=\\"url\\" readonly=\\"readonly\\"> - <div role=\\"group\\" class=\\"input-group\\"> + + </span> + + <div + id="url" + readonly="readonly" + > + <div + class="input-group" + role="group" + > <!----> - <!----> <input id=\\"url\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\"> - <div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\"> - <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\"> - <use href=\\"#copy-to-clipboard\\"></use> + <!----> + + <input + class="gl-form-input form-control" + id="url" + readonly="readonly" + type="text" + /> + + <div + class="input-group-append" + > + <button + aria-label="Copy this value" + class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" + data-clipboard-text="" + title="Copy" + type="button" + > + <!----> + + <svg + aria-hidden="true" + class="gl-button-icon gl-icon s16" + data-testid="copy-to-clipboard-icon" + > + <use + href="#copy-to-clipboard" + /> </svg> - <!----></button></div> + + <!----> + </button> + </div> <!----> </div> </div> </div> - <div class=\\"gl-my-4\\"><span class=\\"gl-font-weight-bold\\"> + + <div + class="gl-my-4" + > + <span + class="gl-font-weight-bold" + > + Authorization key - </span> - <div id=\\"authorization-key\\" readonly=\\"readonly\\" class=\\"gl-mb-3\\"> - <div role=\\"group\\" class=\\"input-group\\"> + + </span> + + <div + class="gl-mb-3" + id="authorization-key" + readonly="readonly" + > + <div + class="input-group" + role="group" + > <!----> - <!----> <input id=\\"authorization-key\\" type=\\"text\\" readonly=\\"readonly\\" class=\\"gl-form-input form-control\\"> - <div class=\\"input-group-append\\"><button title=\\"Copy\\" data-clipboard-text=\\"\\" aria-label=\\"Copy this value\\" type=\\"button\\" class=\\"btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon\\"> - <!----> <svg data-testid=\\"copy-to-clipboard-icon\\" aria-hidden=\\"true\\" class=\\"gl-button-icon gl-icon s16\\"> - <use href=\\"#copy-to-clipboard\\"></use> + <!----> + + <input + class="gl-form-input form-control" + id="authorization-key" + readonly="readonly" + type="text" + /> + + <div + class="input-group-append" + > + <button + aria-label="Copy this value" + class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" + data-clipboard-text="" + title="Copy" + type="button" + > + <!----> + + <svg + aria-hidden="true" + class="gl-button-icon gl-icon s16" + data-testid="copy-to-clipboard-icon" + > + <use + href="#copy-to-clipboard" + /> </svg> - <!----></button></div> + + <!----> + </button> + </div> <!----> </div> - </div> <button type=\\"button\\" disabled=\\"disabled\\" class=\\"btn btn-default btn-md disabled gl-button\\"> + </div> + + <button + class="btn btn-default btn-md disabled gl-button" + disabled="disabled" + type="button" + > + <!----> + <!----> - <!----> <span class=\\"gl-button-text\\"> + + <span + class="gl-button-text" + > + Reset Key - </span></button> + + </span> + </button> + <!----> </div> <!----> @@ -74,25 +309,98 @@ exports[`AlertsSettingsFormNew with default values renders the initial template <!----> </div> </div> - <div id=\\"test-integration\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"test-integration__BV_label_\\" for=\\"test-integration\\" class=\\"d-block col-form-label\\">4. Sample alert payload (optional)</label> - <div class=\\"bv-no-focus-ring\\"><span>Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).</span> <textarea id=\\"test-payload\\" disabled=\\"disabled\\" placeholder=\\"{ "events": [{ "application": "Name of application" }] }\\" wrap=\\"soft\\" class=\\"gl-form-input gl-form-textarea gl-my-3 form-control is-valid\\" style=\\"resize: none; overflow-y: scroll;\\"></textarea> + + <div + class="form-group gl-form-group" + id="test-integration" + role="group" + > + <label + class="d-block col-form-label" + for="test-integration" + id="test-integration__BV_label_" + > + 4. Sample alert payload (optional) + </label> + <div + class="bv-no-focus-ring" + > + <span> + Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional). + </span> + + <textarea + class="gl-form-input gl-form-textarea gl-my-3 form-control is-valid" + disabled="disabled" + id="test-payload" + placeholder="{ \\"events\\": [{ \\"application\\": \\"Name of application\\" }] }" + style="resize: none; overflow-y: scroll;" + wrap="soft" + /> <!----> <!----> <!----> </div> </div> + <!----> + <!----> </div> - <div class=\\"gl-display-flex gl-justify-content-start gl-py-3\\"><button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\"> + + <div + class="gl-display-flex gl-justify-content-start gl-py-3" + > + <button + class="btn js-no-auto-disable btn-success btn-md gl-button" + data-testid="integration-form-submit" + type="submit" + > <!----> - <!----> <span class=\\"gl-button-text\\">Save integration - </span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary\\"> + <!----> - <!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button type=\\"reset\\" class=\\"btn js-no-auto-disable btn-default btn-md gl-button\\"> + + <span + class="gl-button-text" + > + Save integration + + </span> + </button> + + <button + class="btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary" + data-testid="integration-test-and-submit" + disabled="disabled" + type="button" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + Save and test payload + </span> + </button> + + <button + class="btn js-no-auto-disable btn-default btn-md gl-button" + type="reset" + > <!----> - <!----> <span class=\\"gl-button-text\\">Cancel</span></button></div> + + <!----> + + <span + class="gl-button-text" + > + Cancel + </span> + </button> + </div> </div> </transition-stub> -</form>" +</form> `; diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js index 3247f913816..2afa714fcf8 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js @@ -81,7 +81,7 @@ describe('AlertsSettingsFormNew', () => { }); it('renders the initial template', () => { - expect(wrapper.html()).toMatchSnapshot(); + expect(wrapper.element).toMatchSnapshot(); }); it('render the initial form with only an integration type dropdown', () => { diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap index ee4ec4636ea..6047b404197 100644 --- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap +++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap @@ -60,8 +60,8 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ > <svg aria-hidden="true" - class="gl-icon s16 gl-new-dropdown-item-check-icon" - data-testid="mobile-issue-close-icon" + class="gl-icon s16 gl-new-dropdown-item-check-icon gl-mt-3 gl-align-self-start" + data-testid="dropdown-item-checkbox" > <use href="#mobile-issue-close" @@ -115,8 +115,8 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ > <svg aria-hidden="true" - class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden" - data-testid="mobile-issue-close-icon" + class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden gl-mt-3 gl-align-self-start" + data-testid="dropdown-item-checkbox" > <use href="#mobile-issue-close" diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index cfc3b7af282..e629396e699 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -22,6 +22,13 @@ describe('graph component', () => { pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'), }; + const defaultData = { + measurements: { + width: 800, + height: 800, + }, + }; + const createComponent = ({ data = {}, mountFn = shallowMount, @@ -34,7 +41,10 @@ describe('graph component', () => { ...props, }, data() { - return { ...data }; + return { + ...defaultData, + ...data, + }; }, provide: { dataMethod: GRAPHQL, diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js index b70ff92b079..6ff6bae1284 100644 --- a/spec/frontend/reports/codequality_report/store/actions_spec.js +++ b/spec/frontend/reports/codequality_report/store/actions_spec.js @@ -65,7 +65,7 @@ describe('Codequality Reports actions', () => { let mock; let diffFeatureFlagEnabled; - describe('with codequalityMrDiff feature flag enabled', () => { + describe('with codequalityBackendComparison feature flag enabled', () => { beforeEach(() => { diffFeatureFlagEnabled = true; localState.reportsPath = `${TEST_HOST}/codequality_reports.json`; @@ -112,7 +112,7 @@ describe('Codequality Reports actions', () => { }); }); - describe('with codequalityMrDiff feature flag disabled', () => { + describe('with codequalityBackendComparison feature flag disabled', () => { beforeEach(() => { diffFeatureFlagEnabled = false; localState.headPath = `${TEST_HOST}/head.json`; diff --git a/spec/frontend/alert_management/components/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js index 976e50625a6..40af8e60008 100644 --- a/spec/frontend/alert_management/components/alert_details_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js @@ -4,17 +4,14 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import AlertDetails from '~/alert_management/components/alert_details.vue'; -import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue'; -import { - ALERTS_SEVERITY_LABELS, - trackAlertsDetailsViewsOptions, -} from '~/alert_management/constants'; -import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql'; +import AlertDetails from '~/vue_shared/alert_details/components/alert_details.vue'; +import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue'; +import { SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants'; +import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql'; import { joinPaths } from '~/lib/utils/url_utility'; import Tracking from '~/tracking'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; -import mockAlerts from '../mocks/alerts.json'; +import mockAlerts from './mocks/alerts.json'; const mockAlert = mockAlerts[0]; const environmentName = 'Production'; @@ -29,7 +26,13 @@ describe('AlertDetails', () => { const projectId = '1'; const $router = { replace: jest.fn() }; - function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) { + function mountComponent({ + data, + loading = false, + mountMethod = shallowMount, + provide = {}, + stubs = {}, + } = {}) { wrapper = extendedWrapper( mountMethod(AlertDetails, { provide: { @@ -37,6 +40,7 @@ describe('AlertDetails', () => { projectPath, projectIssuesPath, projectId, + ...provide, }, data() { return { @@ -112,9 +116,7 @@ describe('AlertDetails', () => { }); it('renders severity', () => { - expect(wrapper.findByTestId('severity').text()).toBe( - ALERTS_SEVERITY_LABELS[mockAlert.severity], - ); + expect(wrapper.findByTestId('severity').text()).toBe(SEVERITY_LEVELS[mockAlert.severity]); }); it('renders a title', () => { @@ -321,16 +323,27 @@ describe('AlertDetails', () => { }); describe('Snowplow tracking', () => { + const mountOptions = { + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alert: mockAlert }, + loading: false, + }; + beforeEach(() => { jest.spyOn(Tracking, 'event'); - mountComponent({ - props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alert: mockAlert }, - loading: false, - }); }); - it('should track alert details page views', () => { + it('should not track alert details page views when the tracking options do not exist', () => { + mountComponent(mountOptions); + expect(Tracking.event).not.toHaveBeenCalled(); + }); + + it('should track alert details page views when the tracking options exist', () => { + const trackAlertsDetailsViewsOptions = { + category: 'Alert Management', + action: 'view_alert_details', + }; + mountComponent({ ...mountOptions, provide: { trackAlertsDetailsViewsOptions } }); const { category, action } = trackAlertsDetailsViewsOptions; expect(Tracking.event).toHaveBeenCalledWith(category, action); }); diff --git a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js index 0b766c01ec6..e08955d5f8e 100644 --- a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js @@ -1,8 +1,8 @@ import { mount } from '@vue/test-utils'; -import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue'; -import createAlertTodoMutation from '~/alert_management/graphql/mutations/alert_todo_create.mutation.graphql'; +import SidebarTodo from '~/vue_shared/alert_details/components/sidebar/sidebar_todo.vue'; +import createAlertTodoMutation from '~/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql'; import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql'; -import mockAlerts from '../mocks/alerts.json'; +import mockAlerts from './mocks/alerts.json'; const mockAlert = mockAlerts[0]; diff --git a/spec/frontend/alert_management/components/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js index bae82c38a0d..c75909651d7 100644 --- a/spec/frontend/alert_management/components/alert_metrics_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import axios from 'axios'; import waitForPromises from 'helpers/wait_for_promises'; -import AlertMetrics from '~/alert_management/components/alert_metrics.vue'; +import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue'; import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue'; jest.mock('~/monitoring/stores', () => ({ diff --git a/spec/frontend/alert_management/components/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js index 6f2ddb86020..b37518cc424 100644 --- a/spec/frontend/alert_management/components/alert_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js @@ -1,11 +1,10 @@ import { shallowMount } from '@vue/test-utils'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import waitForPromises from 'helpers/wait_for_promises'; -import { trackAlertStatusUpdateOptions } from '~/alert_management/constants'; -import AlertManagementStatus from '~/alert_management/components/alert_status.vue'; -import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql'; +import AlertManagementStatus from '~/vue_shared/alert_details/components/alert_status.vue'; +import updateAlertStatusMutation from '~/graphql_shared//mutations/alert_status_update.mutation.graphql'; import Tracking from '~/tracking'; -import mockAlerts from '../mocks/alerts.json'; +import mockAlerts from './mocks/alerts.json'; const mockAlert = mockAlerts[0]; @@ -20,7 +19,7 @@ describe('AlertManagementStatus', () => { return waitForPromises(); }; - function mountComponent({ props = {}, loading = false, stubs = {} } = {}) { + function mountComponent({ props = {}, provide = {}, loading = false, stubs = {} } = {}) { wrapper = shallowMount(AlertManagementStatus, { propsData: { alert: { ...mockAlert }, @@ -28,6 +27,7 @@ describe('AlertManagementStatus', () => { isSidebar: false, ...props, }, + provide, mocks: { $apollo: { mutate: jest.fn(), @@ -134,10 +134,25 @@ describe('AlertManagementStatus', () => { describe('Snowplow tracking', () => { beforeEach(() => { jest.spyOn(Tracking, 'event'); + }); + + it('should not track alert status updates when the tracking options do not exist', () => { mountComponent({}); + Tracking.event.mockClear(); + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); + findFirstStatusOption().vm.$emit('click'); + setImmediate(() => { + expect(Tracking.event).not.toHaveBeenCalled(); + }); }); - it('should track alert status updates', () => { + it('should track alert status updates when the tracking options exist', () => { + const trackAlertStatusUpdateOptions = { + category: 'Alert Management', + action: 'update_alert_status', + label: 'Status', + }; + mountComponent({ provide: { trackAlertStatusUpdateOptions } }); Tracking.event.mockClear(); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); findFirstStatusOption().vm.$emit('click'); diff --git a/spec/frontend/alert_management/components/alert_summary_row_spec.js b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js index 47c715c089a..a2981478954 100644 --- a/spec/frontend/alert_management/components/alert_summary_row_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_summary_row_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue'; +import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue'; const label = 'a label'; const value = 'a value'; diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/vue_shared/alert_details/mocks/alerts.json index 5267a4fe50d..5267a4fe50d 100644 --- a/spec/frontend/alert_management/mocks/alerts.json +++ b/spec/frontend/vue_shared/alert_details/mocks/alerts.json diff --git a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js index 00c479071fe..4c7f86a40ee 100644 --- a/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_managment_sidebar_assignees_spec.js @@ -2,10 +2,10 @@ import { shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { GlDropdownItem } from '@gitlab/ui'; -import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue'; -import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue'; -import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql'; -import mockAlerts from '../../mocks/alerts.json'; +import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue'; +import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; +import AlertSetAssignees from '~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql'; +import mockAlerts from '../mocks/alerts.json'; const mockAlert = mockAlerts[0]; diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js index 5235ae63fee..c2df37821d3 100644 --- a/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js @@ -1,9 +1,9 @@ import { shallowMount, mount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import AlertSidebar from '~/alert_management/components/alert_sidebar.vue'; -import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue'; -import mockAlerts from '../../mocks/alerts.json'; +import AlertSidebar from '~/vue_shared/alert_details/components/alert_sidebar.vue'; +import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue'; +import mockAlerts from '../mocks/alerts.json'; const mockAlert = mockAlerts[0]; diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js index 0b60a36cf54..95c9dac5034 100644 --- a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js @@ -1,10 +1,8 @@ import { mount } from '@vue/test-utils'; import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; -import { trackAlertStatusUpdateOptions } from '~/alert_management/constants'; -import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue'; -import updateAlertStatusMutation from '~/graphql_shared/mutations/update_alert_status.mutation.graphql'; -import Tracking from '~/tracking'; -import mockAlerts from '../../mocks/alerts.json'; +import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue'; +import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql'; +import mockAlerts from '../mocks/alerts.json'; const mockAlert = mockAlerts[0]; @@ -101,30 +99,5 @@ describe('Alert Details Sidebar Status', () => { expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered'); }); }); - - describe('Snowplow tracking', () => { - beforeEach(() => { - jest.spyOn(Tracking, 'event'); - mountComponent({ - props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alert: mockAlert }, - loading: false, - }); - }); - - it('should track alert status updates', () => { - Tracking.event.mockClear(); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); - findStatusDropdownItem().vm.$emit('click'); - const status = findStatusDropdownItem().text(); - setImmediate(() => { - const { category, action, label } = trackAlertStatusUpdateOptions; - expect(Tracking.event).toHaveBeenCalledWith(category, action, { - label, - property: status, - }); - }); - }); - }); }); }); diff --git a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js index 65cfc600d76..90966482bb4 100644 --- a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js +++ b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; -import SystemNote from '~/alert_management/components/system_notes/system_note.vue'; -import mockAlerts from '../../mocks/alerts.json'; +import SystemNote from '~/vue_shared/alert_details/components/system_notes/system_note.vue'; +import mockAlerts from '../mocks/alerts.json'; const mockAlert = mockAlerts[1]; diff --git a/spec/models/dependency_proxy/manifest_spec.rb b/spec/models/dependency_proxy/manifest_spec.rb index aa2e73356dd..4203644c003 100644 --- a/spec/models/dependency_proxy/manifest_spec.rb +++ b/spec/models/dependency_proxy/manifest_spec.rb @@ -29,24 +29,32 @@ RSpec.describe DependencyProxy::Manifest, type: :model do end end - describe '.find_or_initialize_by_file_name' do - subject { DependencyProxy::Manifest.find_or_initialize_by_file_name(file_name) } + describe '.find_or_initialize_by_file_name_or_digest' do + let_it_be(:file_name) { 'foo' } + let_it_be(:digest) { 'bar' } - context 'no manifest exists' do - let_it_be(:file_name) { 'foo' } + subject { DependencyProxy::Manifest.find_or_initialize_by_file_name_or_digest(file_name: file_name, digest: digest) } + context 'no manifest exists' do it 'initializes a manifest' do - expect(DependencyProxy::Manifest).to receive(:new).with(file_name: file_name) + expect(DependencyProxy::Manifest).to receive(:new).with(file_name: file_name, digest: digest) subject end end - context 'manifest exists' do + context 'manifest exists and matches file_name' do let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest) } let_it_be(:file_name) { dependency_proxy_manifest.file_name } it { is_expected.to eq(dependency_proxy_manifest) } end + + context 'manifest exists and matches digest' do + let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest) } + let_it_be(:digest) { dependency_proxy_manifest.digest } + + it { is_expected.to eq(dependency_proxy_manifest) } + end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 26bd3df52f4..aef3aa34425 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2022,7 +2022,7 @@ RSpec.describe MergeRequest, factory_default: :keep do context 'when feature flag is disabled' do before do - stub_feature_flags(codequality_mr_diff: false) + stub_feature_flags(codequality_backend_comparison: false) end it { is_expected.to be_falsey } diff --git a/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb b/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb new file mode 100644 index 00000000000..5d2f3e98bb4 --- /dev/null +++ b/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Projects::Ci::PrometheusMetrics::HistogramsController' do + let_it_be(:project) { create(:project, :public) } + + describe 'POST /*namespace_id/:project_id/-/ci/prometheus_metrics/histograms' do + context 'with known histograms' do + it 'returns 201 Created' do + post histograms_route(histograms: [ + { name: :pipeline_graph_link_calculation_duration_seconds, value: 1 }, + { name: :pipeline_graph_links_total, value: 10 } + ]) + + expect(response).to have_gitlab_http_status(:created) + end + end + + context 'with unknown histograms' do + it 'returns 404 Not Found' do + post histograms_route(histograms: [{ name: :chunky_bacon, value: 5 }]) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'with the feature flag disabled' do + before do + stub_feature_flags(ci_accept_frontend_prometheus_metrics: false) + end + + it 'returns 202 Accepted' do + post histograms_route(histograms: [ + { name: :pipeline_graph_link_calculation_duration_seconds, value: 1 } + ]) + + expect(response).to have_gitlab_http_status(:accepted) + end + end + end + + def histograms_route(params = {}) + namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, **params) + end +end diff --git a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb index a4637b6ba1c..0c944cad40c 100644 --- a/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb +++ b/spec/services/authorized_project_update/recalculate_for_user_range_service_spec.rb @@ -9,7 +9,7 @@ RSpec.describe AuthorizedProjectUpdate::RecalculateForUserRangeService do it 'calls Users::RefreshAuthorizedProjectsService' do users.each do |user| expect(Users::RefreshAuthorizedProjectsService).to( - receive(:new).with(user).and_call_original) + receive(:new).with(user, source: described_class.name).and_call_original) end range = users.map(&:id).minmax diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index a7889f0644d..d316c9a262b 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -50,6 +50,35 @@ RSpec.describe Ci::ProcessPipelineService do expect(all_builds.retried).to contain_exactly(build_retried) end + context 'counter ci_legacy_update_jobs_as_retried_total' do + let(:counter) { double(increment: true) } + + before do + allow(Gitlab::Metrics).to receive(:counter).and_call_original + allow(Gitlab::Metrics).to receive(:counter) + .with(:ci_legacy_update_jobs_as_retried_total, anything) + .and_return(counter) + end + + it 'increments the counter' do + expect(counter).to receive(:increment) + + subject.execute + end + + context 'when the previous build has already retried column true' do + before do + build_retried.update_columns(retried: true) + end + + it 'does not increment the counter' do + expect(counter).not_to receive(:increment) + + subject.execute + end + end + end + def create_build(name, **opts) create(:ci_build, :created, pipeline: pipeline, name: name, **opts) end diff --git a/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb new file mode 100644 index 00000000000..17330f5b77d --- /dev/null +++ b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PrometheusMetrics::ObserveHistogramsService do + let_it_be(:project) { create(:project) } + let(:params) { {} } + + subject(:execute) { described_class.new(project, params).execute } + + before do + Gitlab::Metrics.reset_registry! + end + + context 'with empty data' do + it 'does not raise errors' do + is_expected.to be_success + end + end + + context 'observes metrics successfully' do + let(:params) do + { + histograms: [ + { name: 'pipeline_graph_link_calculation_duration_seconds', value: '1' }, + { name: 'pipeline_graph_links_per_job_ratio', value: '0.9' } + ] + } + end + + it 'increments the metrics' do + execute + + expect(histogram_data).to match(a_hash_including({ 0.8 => 0.0, 1 => 1.0, 2 => 1.0 })) + + expect(histogram_data(:pipeline_graph_links_per_job_ratio)) + .to match(a_hash_including({ 0.8 => 0.0, 0.9 => 1.0, 1 => 1.0 })) + end + + it 'returns an empty body and status code' do + is_expected.to be_success + expect(subject.http_status).to eq(:created) + expect(subject.payload).to eq({}) + end + end + + context 'with unknown histograms' do + let(:params) do + { histograms: [{ name: 'chunky_bacon', value: '4' }] } + end + + it 'raises ActiveRecord::RecordNotFound error' do + expect { subject }.to raise_error ActiveRecord::RecordNotFound + end + end + + context 'with feature flag disabled' do + before do + stub_feature_flags(ci_accept_frontend_prometheus_metrics: false) + end + + let(:params) do + { + histograms: [ + { name: 'pipeline_graph_link_calculation_duration_seconds', value: '4' } + ] + } + end + + it 'does not register the metrics' do + execute + + expect(histogram_data).to be_nil + end + + it 'returns an empty body and status code' do + is_expected.to be_success + expect(subject.http_status).to eq(:accepted) + expect(subject.payload).to eq({}) + end + end + + def histogram_data(name = :pipeline_graph_link_calculation_duration_seconds) + Gitlab::Metrics.registry.get(name)&.get({ project: project.full_path }) + end +end diff --git a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb index c375e5a2fa3..40a2f954786 100644 --- a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb @@ -10,7 +10,12 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do let(:manifest) { dependency_proxy_manifest.file.read } let(:group) { dependency_proxy_manifest.group } let(:token) { Digest::SHA256.hexdigest('123') } - let(:headers) { { 'docker-content-digest' => dependency_proxy_manifest.digest } } + let(:headers) do + { + 'docker-content-digest' => dependency_proxy_manifest.digest, + 'content-type' => dependency_proxy_manifest.content_type + } + end describe '#execute' do subject { described_class.new(group, image, tag, token).execute } @@ -18,22 +23,37 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do context 'when no manifest exists' do let_it_be(:image) { 'new-image' } - before do - stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest) - stub_manifest_download(image, tag, headers: headers) + shared_examples 'downloading the manifest' do + it 'downloads manifest from remote registry if there is no cached one', :aggregate_failures do + expect { subject }.to change { group.dependency_proxy_manifests.count }.by(1) + expect(subject[:status]).to eq(:success) + expect(subject[:manifest]).to be_a(DependencyProxy::Manifest) + expect(subject[:manifest]).to be_persisted + end end - it 'downloads manifest from remote registry if there is no cached one', :aggregate_failures do - expect { subject }.to change { group.dependency_proxy_manifests.count }.by(1) - expect(subject[:status]).to eq(:success) - expect(subject[:manifest]).to be_a(DependencyProxy::Manifest) - expect(subject[:manifest]).to be_persisted + context 'successful head request' do + before do + stub_manifest_head(image, tag, headers: headers) + stub_manifest_download(image, tag, headers: headers) + end + + it_behaves_like 'downloading the manifest' + end + + context 'failed head request' do + before do + stub_manifest_head(image, tag, status: :error) + stub_manifest_download(image, tag, headers: headers) + end + + it_behaves_like 'downloading the manifest' end end context 'when manifest exists' do before do - stub_manifest_head(image, tag, digest: dependency_proxy_manifest.digest) + stub_manifest_head(image, tag, headers: headers) end shared_examples 'using the cached manifest' do @@ -48,15 +68,17 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do context 'when digest is stale' do let(:digest) { 'new-digest' } + let(:content_type) { 'new-content-type' } before do - stub_manifest_head(image, tag, digest: digest) - stub_manifest_download(image, tag, headers: { 'docker-content-digest' => digest }) + stub_manifest_head(image, tag, headers: { 'docker-content-digest' => digest, 'content-type' => content_type }) + stub_manifest_download(image, tag, headers: { 'docker-content-digest' => digest, 'content-type' => content_type }) end it 'downloads the new manifest and updates the existing record', :aggregate_failures do expect(subject[:status]).to eq(:success) expect(subject[:manifest]).to eq(dependency_proxy_manifest) + expect(subject[:manifest].content_type).to eq(content_type) expect(subject[:manifest].digest).to eq(digest) end end diff --git a/spec/services/dependency_proxy/head_manifest_service_spec.rb b/spec/services/dependency_proxy/head_manifest_service_spec.rb index 7c7ebe4d181..9c1e4d650f8 100644 --- a/spec/services/dependency_proxy/head_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/head_manifest_service_spec.rb @@ -8,12 +8,19 @@ RSpec.describe DependencyProxy::HeadManifestService do let(:tag) { 'latest' } let(:token) { Digest::SHA256.hexdigest('123') } let(:digest) { '12345' } + let(:content_type) { 'foo' } + let(:headers) do + { + 'docker-content-digest' => digest, + 'content-type' => content_type + } + end subject { described_class.new(image, tag, token).execute } context 'remote request is successful' do before do - stub_manifest_head(image, tag, digest: digest) + stub_manifest_head(image, tag, headers: headers) end it { expect(subject[:status]).to eq(:success) } diff --git a/spec/services/dependency_proxy/pull_manifest_service_spec.rb b/spec/services/dependency_proxy/pull_manifest_service_spec.rb index b760839d1fb..b3053174cc0 100644 --- a/spec/services/dependency_proxy/pull_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/pull_manifest_service_spec.rb @@ -9,7 +9,10 @@ RSpec.describe DependencyProxy::PullManifestService do let(:token) { Digest::SHA256.hexdigest('123') } let(:manifest) { { foo: 'bar' }.to_json } let(:digest) { '12345' } - let(:headers) { { 'docker-content-digest' => digest } } + let(:content_type) { 'foo' } + let(:headers) do + { 'docker-content-digest' => digest, 'content-type' => content_type } + end subject { described_class.new(image, tag, token).execute_with_manifest(&method(:check_response)) } @@ -25,6 +28,7 @@ RSpec.describe DependencyProxy::PullManifestService do expect(response[:status]).to eq(:success) expect(response[:file].read).to eq(manifest) expect(response[:digest]).to eq(digest) + expect(response[:content_type]).to eq(content_type) end subject diff --git a/spec/services/issue_rebalancing_service_spec.rb b/spec/services/issue_rebalancing_service_spec.rb index 94f594c8083..7b3d4213b24 100644 --- a/spec/services/issue_rebalancing_service_spec.rb +++ b/spec/services/issue_rebalancing_service_spec.rb @@ -32,70 +32,88 @@ RSpec.describe IssueRebalancingService do project.reload.issues.reorder(relative_position: :asc).to_a end - it 'rebalances a set of issues with clumps at the end and start' do - all_issues = start_clump + unclumped + end_clump.reverse - service = described_class.new(project.issues.first) + shared_examples 'IssueRebalancingService shared examples' do + it 'rebalances a set of issues with clumps at the end and start' do + all_issues = start_clump + unclumped + end_clump.reverse + service = described_class.new(project.issues.first) - expect { service.execute }.not_to change { issues_in_position_order.map(&:id) } + expect { service.execute }.not_to change { issues_in_position_order.map(&:id) } - all_issues.each(&:reset) + all_issues.each(&:reset) - gaps = all_issues.take(all_issues.count - 1).zip(all_issues.drop(1)).map do |a, b| - b.relative_position - a.relative_position + gaps = all_issues.take(all_issues.count - 1).zip(all_issues.drop(1)).map do |a, b| + b.relative_position - a.relative_position + end + + expect(gaps).to all(be > RelativePositioning::MIN_GAP) + expect(all_issues.first.relative_position).to be > (RelativePositioning::MIN_POSITION * 0.9999) + expect(all_issues.last.relative_position).to be < (RelativePositioning::MAX_POSITION * 0.9999) end - expect(gaps).to all(be > RelativePositioning::MIN_GAP) - expect(all_issues.first.relative_position).to be > (RelativePositioning::MIN_POSITION * 0.9999) - expect(all_issues.last.relative_position).to be < (RelativePositioning::MAX_POSITION * 0.9999) - end + it 'is idempotent' do + service = described_class.new(project.issues.first) - it 'is idempotent' do - service = described_class.new(project.issues.first) + expect do + service.execute + service.execute + end.not_to change { issues_in_position_order.map(&:id) } + end - expect do - service.execute - service.execute - end.not_to change { issues_in_position_order.map(&:id) } - end + it 'does nothing if the feature flag is disabled' do + stub_feature_flags(rebalance_issues: false) + issue = project.issues.first + issue.project + issue.project.group + old_pos = issue.relative_position - it 'does nothing if the feature flag is disabled' do - stub_feature_flags(rebalance_issues: false) - issue = project.issues.first - issue.project - issue.project.group - old_pos = issue.relative_position + service = described_class.new(issue) - service = described_class.new(issue) + expect { service.execute }.not_to exceed_query_limit(0) + expect(old_pos).to eq(issue.reload.relative_position) + end - expect { service.execute }.not_to exceed_query_limit(0) - expect(old_pos).to eq(issue.reload.relative_position) - end + it 'acts if the flag is enabled for the project' do + issue = create(:issue, project: project, author: user, relative_position: max_pos) + stub_feature_flags(rebalance_issues: issue.project) - it 'acts if the flag is enabled for the project' do - issue = create(:issue, project: project, author: user, relative_position: max_pos) - stub_feature_flags(rebalance_issues: issue.project) + service = described_class.new(issue) - service = described_class.new(issue) + expect { service.execute }.to change { issue.reload.relative_position } + end - expect { service.execute }.to change { issue.reload.relative_position } - end + it 'acts if the flag is enabled for the group' do + issue = create(:issue, project: project, author: user, relative_position: max_pos) + project.update!(group: create(:group)) + stub_feature_flags(rebalance_issues: issue.project.group) - it 'acts if the flag is enabled for the group' do - issue = create(:issue, project: project, author: user, relative_position: max_pos) - project.update!(group: create(:group)) - stub_feature_flags(rebalance_issues: issue.project.group) + service = described_class.new(issue) - service = described_class.new(issue) + expect { service.execute }.to change { issue.reload.relative_position } + end + + it 'aborts if there are too many issues' do + issue = project.issues.first + base = double(count: 10_001) - expect { service.execute }.to change { issue.reload.relative_position } + allow(Issue).to receive(:relative_positioning_query_base).with(issue).and_return(base) + + expect { described_class.new(issue).execute }.to raise_error(described_class::TooManyIssues) + end end - it 'aborts if there are too many issues' do - issue = project.issues.first - base = double(count: 10_001) + context 'when issue_rebalancing_optimization feature flag is on' do + before do + stub_feature_flags(issue_rebalancing_optimization: true) + end + + it_behaves_like 'IssueRebalancingService shared examples' + end - allow(Issue).to receive(:relative_positioning_query_base).with(issue).and_return(base) + context 'when issue_rebalancing_optimization feature flag is on' do + before do + stub_feature_flags(issue_rebalancing_optimization: false) + end - expect { described_class.new(issue).execute }.to raise_error(described_class::TooManyIssues) + it_behaves_like 'IssueRebalancingService shared examples' end end diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 9404668e3c5..cc01b22f9d2 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -143,6 +143,21 @@ RSpec.describe Users::RefreshAuthorizedProjectsService do expect(authorizations[0].project_id).to eq(project.id) expect(authorizations[0].access_level).to eq(Gitlab::Access::MAINTAINER) end + + it 'logs the details of the refresh' do + source = :foo + service = described_class.new(user, source: source) + user.project_authorizations.delete_all + + expect(Gitlab::AppJsonLogger).to( + receive(:info) + .with(event: 'authorized_projects_refresh', + 'authorized_projects_refresh.source': source, + 'authorized_projects_refresh.rows_deleted': 0, + 'authorized_projects_refresh.rows_added': 1)) + + service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MAINTAINER]]) + end end describe '#fresh_access_levels_per_project' do diff --git a/spec/support/helpers/dependency_proxy_helpers.rb b/spec/support/helpers/dependency_proxy_helpers.rb index ebb849628bf..0d8f56906e3 100644 --- a/spec/support/helpers/dependency_proxy_helpers.rb +++ b/spec/support/helpers/dependency_proxy_helpers.rb @@ -18,11 +18,11 @@ module DependencyProxyHelpers .to_return(status: status, body: body || manifest, headers: headers) end - def stub_manifest_head(image, tag, status: 200, body: nil, digest: '123456') + def stub_manifest_head(image, tag, status: 200, body: nil, headers: {}) manifest_url = registry.manifest_url(image, tag) stub_full_request(manifest_url, method: :head) - .to_return(status: status, body: body, headers: { 'docker-content-digest' => digest } ) + .to_return(status: status, body: body, headers: headers ) end def stub_blob_download(image, blob_sha, status = 200, body = '123456') diff --git a/yarn.lock b/yarn.lock index 05d09f74cc3..c9ff1adab18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -876,10 +876,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@27.1.5": - version "27.1.5" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-27.1.5.tgz#f18327f48b9ed38e674573a761ce89d33b359add" - integrity sha512-2L8Wa05IFx7Bj7smpwXFdnUoGi6CBzBIUVYIfPO/LkBtBHTYf9QoUH1EF7qduHY2EBWcWNu5X1EKWFrs9ErmsQ== +"@gitlab/ui@27.4.3": + version "27.4.3" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-27.4.3.tgz#cc65721de9de689b870673bf8efbfd39afe6399c" + integrity sha512-to6NJEYYqNG199VnwqQwV3vOJwK+0jlxW1tiqny/u7RRi+HPmjJD0hDxlx1dnyaaYXoRjgoXOPyjHCGVGyFZwA== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |