This instance will be upgraded to Heptapod 0.26.0rc1 on 2021-10-26 at 14:30 UTC+2 (a few minutes of down time)

Commit cb029fb7 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 005cb1ccc1bf
<script>
import { GlButton, GlIcon, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
const IGNORED = 'ignored';
const RESOLVED = 'resolved';
const UNRESOLVED = 'unresolved';
const statusValidation = [IGNORED, RESOLVED, UNRESOLVED];
export default {
components: {
GlButton,
GlIcon,
GlButtonGroup,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
error: {
type: Object,
required: true,
validator: ({ status }) => statusValidation.includes(status),
},
},
computed: {
ignoreBtn() {
return this.error.status !== IGNORED
? { status: IGNORED, icon: 'eye-slash', title: __('Ignore') }
: { status: UNRESOLVED, icon: 'eye', title: __('Undo Ignore') };
},
resolveBtn() {
return this.error.status !== RESOLVED
? { status: RESOLVED, icon: 'check-circle', title: __('Resolve') }
: { status: UNRESOLVED, icon: 'canceled-circle', title: __('Unresolve') };
},
detailsLink() {
return `error_tracking/${this.error.id}/details`;
},
},
};
</script>
<template>
<div>
<gl-button-group class="flex-column flex-md-row ml-0 ml-md-n4">
<gl-button
:key="ignoreBtn.status"
:ref="`${ignoreBtn.title.toLowerCase()}Error`"
v-gl-tooltip.hover
class="d-block mb-2 mb-md-0 w-100"
:title="ignoreBtn.title"
@click="$emit('update-issue-status', { errorId: error.id, status: ignoreBtn.status })"
>
<gl-icon class="d-none d-md-inline m-0" :name="ignoreBtn.icon" :size="12" />
<span class="d-md-none">{{ ignoreBtn.title }}</span>
</gl-button>
<gl-button
:key="resolveBtn.status"
:ref="`${resolveBtn.title.toLowerCase()}Error`"
v-gl-tooltip.hover
class="d-block mb-2 mb-md-0 w-100"
:title="resolveBtn.title"
@click="$emit('update-issue-status', { errorId: error.id, status: resolveBtn.status })"
>
<gl-icon class="d-none d-md-inline m-0" :name="resolveBtn.icon" :size="12" />
<span class="d-md-none">{{ resolveBtn.title }}</span>
</gl-button>
</gl-button-group>
<gl-button
:href="detailsLink"
category="secondary"
variant="info"
class="d-block d-md-none mb-2 mb-md-0"
>
{{ __('More details') }}
</gl-button>
</div>
</template>
......@@ -13,12 +13,12 @@ import {
GlDropdownDivider,
GlTooltipDirective,
GlPagination,
GlButtonGroup,
} from '@gitlab/ui';
import AccessorUtils from '~/lib/utils/accessor';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale';
import { isEmpty } from 'lodash';
import ErrorTrackingActions from './error_tracking_actions.vue';
export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
......@@ -26,10 +26,6 @@ export default {
FIRST_PAGE: 1,
PREV_PAGE: 1,
NEXT_PAGE: 2,
statusButtons: [
{ status: 'ignored', icon: 'eye-slash', title: __('Ignore') },
{ status: 'resolved', icon: 'check-circle', title: __('Resolve') },
],
fields: [
{
key: 'error',
......@@ -58,12 +54,7 @@ export default {
{
key: 'status',
label: '',
tdClass: `table-col d-none d-md-table-cell align-items-center pl-md-0`,
},
{
key: 'details',
tdClass: 'table-col d-md-none d-flex align-items-center rounded-bottom bg-secondary',
thClass: 'invisible w-0',
tdClass: `${tableDataClass}`,
},
],
statusFilters: {
......@@ -89,7 +80,7 @@ export default {
GlFormInput,
GlPagination,
TimeAgo,
GlButtonGroup,
ErrorTrackingActions,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -206,7 +197,7 @@ export default {
this.filterValue = label;
return this.filterByStatus(status);
},
updateIssueStatus(errorId, status) {
updateIssueStatus({ errorId, status }) {
this.updateStatus({
endpoint: this.getIssueUpdatePath(errorId),
status,
......@@ -220,8 +211,10 @@ export default {
<template>
<div class="error-list">
<div v-if="errorTrackingEnabled">
<div class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 p-0 p-sm-3">
<div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0 bg-secondary">
<div
class="row flex-column flex-md-row align-items-md-center m-0 mt-sm-2 p-3 p-sm-3 bg-secondary border"
>
<div class="search-box flex-fill mb-1 mb-md-0">
<div class="filtered-search-box mb-0">
<gl-dropdown
:text="__('Recent searches')"
......@@ -273,7 +266,7 @@ export default {
<gl-dropdown
:text="$options.statusFilters[statusFilter]"
class="status-dropdown mr-2"
class="status-dropdown mx-md-1 mb-1 mb-md-0"
menu-class="dropdown"
:disabled="loading"
>
......@@ -366,46 +359,7 @@ export default {
</div>
</template>
<template #cell(status)="errors">
<gl-button-group>
<gl-button
v-for="button in $options.statusButtons"
:key="button.status"
:ref="button.title.toLowerCase() + 'Error'"
v-gl-tooltip.hover
:title="button.title"
@click="updateIssueStatus(errors.item.id, button.status)"
>
<gl-icon :name="button.icon" :size="12" />
</gl-button>
</gl-button-group>
</template>
<template #cell(details)="errors">
<gl-button
category="primary"
variant="info"
block
class="mb-1 mt-2"
@click="updateIssueStatus(errors.item.id, 'resolved')"
>
{{ __('Resolve') }}
</gl-button>
<gl-button
category="secondary"
variant="default"
block
class="mb-2"
@click="updateIssueStatus(errors.item.id, 'ignored')"
>
{{ __('Ignore') }}
</gl-button>
<gl-button
:href="getDetailsLink(errors.item.id)"
category="secondary"
variant="info"
class="d-block mb-2"
>
{{ __('More details') }}
</gl-button>
<error-tracking-actions :error="errors.item" @update-issue-status="updateIssueStatus" />
</template>
<template #empty>
{{ __('No errors to display.') }}
......
$gray-border: 1px solid $border-color;
.error-list {
.sort-control {
.btn {
......@@ -13,19 +11,14 @@ $gray-border: 1px solid $border-color;
}
}
@include media-breakpoint-up(sm) {
.row-top {
border: $gray-border;
background-color: $gray-50;
}
}
@include media-breakpoint-down(md) {
@include media-breakpoint-down(sm) {
.error-list-table {
.table-col {
min-height: 68px;
&:last-child {
background-color: $gray-normal;
&::before {
content: none !important;
}
......
......@@ -174,6 +174,8 @@ def persisted_environment
pipeline: Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE)
end
scope :with_coverage, -> { where.not(coverage: nil) }
acts_as_taggable
add_authentication_token_field :token, encrypted: :optional
......
# frozen_string_literal: true
module Ci
class DailyReportResult < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :last_pipeline, class_name: 'Ci::Pipeline', foreign_key: :last_pipeline_id
belongs_to :project
# TODO: Refactor this out when BuildReportResult is implemented.
# They both need to share the same enum values for param.
REPORT_PARAMS = {
coverage: 0
}.freeze
enum param_type: REPORT_PARAMS
def self.upsert_reports(data)
upsert_all(data, unique_by: :index_daily_report_results_unique_columns) if data.any?
end
end
end
......@@ -82,6 +82,8 @@ class Pipeline < ApplicationRecord
has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline
has_many :daily_report_results, class_name: 'Ci::DailyReportResult', foreign_key: :last_pipeline_id
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :id, to: :project, prefix: true
......@@ -189,7 +191,10 @@ class Pipeline < ApplicationRecord
end
after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
# We wait a little bit to ensure that all BuildFinishedWorkers finish first
# because this is where some metrics like code coverage is parsed and stored
# in CI build records which the daily build metrics worker relies on.
pipeline.run_after_commit { Ci::DailyReportResultsWorker.perform_in(10.minutes, pipeline.id) }
end
after_transition do |pipeline, transition|
......@@ -941,6 +946,14 @@ def cacheable?
Ci::PipelineEnums.ci_config_sources.key?(config_source.to_sym)
end
def source_ref_path
if branch? || merge_request?
Gitlab::Git::BRANCH_REF_PREFIX + source_ref.to_s
elsif tag?
Gitlab::Git::TAG_REF_PREFIX + source_ref.to_s
end
end
private
def pipeline_data
......
......@@ -314,6 +314,8 @@ class Project < ApplicationRecord
has_many :import_failures, inverse_of: :project
has_many :daily_report_results, class_name: 'Ci::DailyReportResult'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
......
......@@ -21,6 +21,7 @@ class User < ApplicationRecord
include OptionallySearch
include FromUnion
include BatchDestroyDependentAssociations
include IgnorableColumns
DEFAULT_NOTIFICATION_LEVEL = :participating
......@@ -59,9 +60,10 @@ class User < ApplicationRecord
MINIMUM_INACTIVE_DAYS = 180
enum bot_type: ::UserBotTypeEnums.bots
enum user_type: ::UserTypeEnums.types
ignore_column :bot_type, remove_with: '12.11', remove_after: '2020-04-22'
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
# rubocop: disable CodeReuse/ServiceClass
......@@ -337,8 +339,9 @@ def inactive_message
scope :with_emails, -> { preload(:emails) }
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
scope :with_public_profile, -> { where(private_profile: false) }
scope :bots, -> { where.not(bot_type: nil) }
scope :humans, -> { where(user_type: nil, bot_type: nil) }
scope :bots, -> { where(user_type: UserTypeEnums.bots.values) }
scope :not_bots, -> { humans.or(where.not(user_type: UserTypeEnums.bots.values)) }
scope :humans, -> { where(user_type: nil) }
scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do
where('EXISTS (?)',
......@@ -618,7 +621,7 @@ def ghost
def alert_bot
email_pattern = "alert%s@#{Settings.gitlab.host}"
unique_internal(where(bot_type: :alert_bot), 'alert-bot', email_pattern) do |u|
unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u|
u.bio = 'The GitLab alert bot'
u.name = 'GitLab Alert Bot'
end
......@@ -640,7 +643,7 @@ def full_path
end
def bot?
bot_type.present?
UserTypeEnums.bots.has_key?(user_type)
end
def internal?
......@@ -652,7 +655,7 @@ def self.internal
end
def self.non_internal
without_ghosts.humans
without_ghosts.not_bots
end
#
......
# frozen_string_literal: true
module UserBotTypeEnums
def self.bots
{
alert_bot: 2
}
end
end
UserBotTypeEnums.prepend_if_ee('EE::UserBotTypeEnums')
......@@ -2,13 +2,13 @@
module UserTypeEnums
def self.types
bots
bots.merge(human: nil)
end
def self.bots
{
AlertBot: 2
}
alert_bot: 2
}.with_indifferent_access
end
end
......
# frozen_string_literal: true
module Ci
class DailyReportResultService
def execute(pipeline)
return unless Feature.enabled?(:ci_daily_code_coverage, pipeline.project, default_enabled: true)
DailyReportResult.upsert_reports(coverage_reports(pipeline))
end
private
def coverage_reports(pipeline)
base_attrs = {
project_id: pipeline.project_id,
ref_path: pipeline.source_ref_path,
param_type: DailyReportResult.param_types[:coverage],
date: pipeline.created_at.to_date,
last_pipeline_id: pipeline.id
}
pipeline.builds.with_coverage.map do |build|
base_attrs.merge(
title: build.group_name,
value: build.coverage
)
end
end
end
end
......@@ -605,6 +605,13 @@
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: pipeline_background:ci_daily_report_results
:feature_category: :continuous_integration
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
- :name: pipeline_cache:expire_job_cache
:feature_category: :continuous_integration
:has_external_dependencies:
......@@ -745,13 +752,6 @@
:resource_boundary: :unknown
:weight: 5
:idempotent:
- :name: pipeline_processing:pipeline_success
:feature_category: :continuous_integration
:has_external_dependencies:
:urgency: :high
:resource_boundary: :unknown
:weight: 5
:idempotent:
- :name: pipeline_processing:pipeline_update
:feature_category: :continuous_integration
:has_external_dependencies:
......
# frozen_string_literal: true
module Ci
class DailyReportResultsWorker
include ApplicationWorker
include PipelineBackgroundQueue
idempotent!
def perform(pipeline_id)
Ci::Pipeline.find_by_id(pipeline_id).try do |pipeline|
Ci::DailyReportResultService.new.execute(pipeline)
end
end
end
end
# frozen_string_literal: true
class PipelineSuccessWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include PipelineQueue
queue_namespace :pipeline_processing
urgency :high
def perform(pipeline_id)
# no-op
end
end
---
title: Improve Advanced global search performance by using routing
merge_request: 27398
author:
type: performance
---
title: Update icons in Sentry Error Tracking list for ignored/resolved errors
merge_request: 27125
author:
type: other
---
title: Move bots functionality to user_type column
merge_request: 26981
author:
type: performance
---
title: Store daily code coverages into ci_daily_report_results table
merge_request: 24695
author:
type: added
......@@ -469,6 +469,7 @@ tables:
- ghost
- last_activity_on
- notified_of_own_activity
- user_type
- bot_type
- preferred_language
- theme_id
......
# frozen_string_literal: true
class CreateDailyReportResults < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
create_table :ci_daily_report_results do |t|
t.date :date, null: false
t.bigint :project_id, null: false
t.bigint :last_pipeline_id, null: false
t.float :value, null: false
t.integer :param_type, limit: 8, null: false
t.string :ref_path, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.string :title, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.index :last_pipeline_id
t.index [:project_id, :ref_path, :param_type, :date, :title], name: 'index_daily_report_results_unique_columns', unique: true
t.foreign_key :projects, on_delete: :cascade
t.foreign_key :ci_pipelines, column: :last_pipeline_id, on_delete: :cascade
end
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment