Commit 25989ab7 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 9bbb32b2
......@@ -6,8 +6,8 @@
/doc/ @axil @marcia @eread @mikelewis
# Frontend maintainers should see everything in `app/assets/`
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
# Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database
......
......@@ -273,11 +273,6 @@ RSpec/ContextWording:
RSpec/EmptyLineAfterFinalLet:
Enabled: false
# Offense count: 232
# Cop supports --auto-correct.
RSpec/EmptyLineAfterSubject:
Enabled: false
# Offense count: 719
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
......
......@@ -12,23 +12,19 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import DateTimePicker from './date_time_picker/date_time_picker.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
import { sidebarAnimationDuration, timeWindows } from '../constants';
import { sidebarAnimationDuration } from '../constants';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import {
getTimeDiff,
getTimeWindow,
downloadCSVOptions,
generateLinkToChartOptions,
} from '../utils';
import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
let sidebarMutationObserver;
......@@ -46,6 +42,7 @@ export default {
GlDropdownItem,
GlFormGroup,
GlModal,
DateTimePicker,
},
directives: {
GlModal: GlModalDirective,
......@@ -171,10 +168,8 @@ export default {
return {
state: 'gettingStarted',
elWidth: 0,
selectedTimeWindow: '',
selectedTimeWindowKey: '',
formIsValid: null,
timeWindows: {},
selectedTimeWindow: {},
isRearrangingPanels: false,
};
},
......@@ -237,11 +232,13 @@ export default {
end,
};
this.timeWindows = timeWindows;
this.selectedTimeWindowKey = getTimeWindow(range);
this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
this.selectedTimeWindow = range;
if (!isValidDate(start) || !isValidDate(end)) {
this.showInvalidDateError();
} else {
this.fetchData(range);
}
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
......@@ -298,6 +295,9 @@ export default {
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1);
},
showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.'));
},
generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path;
const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
......@@ -320,16 +320,12 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
activeTimeWindow(key) {
return this.timeWindows[key] === this.selectedTimeWindow;
},
setTimeWindowParameter(key) {
const { start, end } = getTimeDiff(key);
return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
},
groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0;
},
onDateTimePickerApply(timeWindowUrlParams) {
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
},
downloadCSVOptions,
generateLinkToChartOptions,
},
......@@ -342,14 +338,14 @@ export default {
<template>
<div class="prometheus-graphs">
<div class="gl-p-3 pb-0 border-bottom bg-gray-light">
<div class="prometheus-graphs-header gl-p-3 pb-0 border-bottom bg-gray-light">
<div class="row">
<template v-if="environmentsEndpoint">
<gl-form-group
:label="__('Dashboard')"
label-size="sm"
label-for="monitor-dashboards-dropdown"
class="col-sm-12 col-md-4 col-lg-2"
class="col-sm-12 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-dashboards-dropdown"
......@@ -372,7 +368,7 @@ export default {
:label="s__('Metrics|Environment')"
label-size="sm"
label-for="monitor-environments-dropdown"
class="col-sm-6 col-md-4 col-lg-2"
class="col-sm-6 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-environments-dropdown"
......@@ -397,30 +393,19 @@ export default {
:label="s__('Metrics|Show last')"
label-size="sm"
label-for="monitor-time-window-dropdown"
class="col-sm-6 col-md-4 col-lg-2"
>
<gl-dropdown
id="monitor-time-window-dropdown"
class="mb-0 d-flex js-time-window-dropdown"
toggle-class="dropdown-menu-toggle"
:text="selectedTimeWindow"
class="col-sm-6 col-md-6 col-lg-4"
>
<gl-dropdown-item
v-for="(value, key) in timeWindows"
:key="key"
:active="activeTimeWindow(key)"
:href="setTimeWindowParameter(key)"
active-class="active"
>{{ value }}</gl-dropdown-item
>
</gl-dropdown>
<date-time-picker
:selected-time-window="selectedTimeWindow"
@onApply="onDateTimePickerApply"
/>
</gl-form-group>
</template>
<gl-form-group
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length"
label-for="prometheus-graphs-dropdown-buttons"
class="dropdown-buttons col-lg d-lg-flex align-items-end"
class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end"
>
<div id="prometheus-graphs-dropdown-buttons">
<gl-button
......
<script>
import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import DateTimePickerInput from './date_time_picker_input.vue';
import {
getTimeDiff,
getTimeWindow,
stringToISODate,
ISODateToString,
truncateZerosInDateTime,
isDateTimePickerInputValid,
} from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
export default {
components: {
Icon,
DateTimePickerInput,
GlFormGroup,
GlButton,
GlDropdown,
GlDropdownItem,
},
props: {
timeWindows: {
type: Object,
required: false,
default: () => timeWindows,
},
selectedTimeWindow: {
type: Object,
required: false,
default: () => {},
},
},
data() {
return {
selectedTimeWindowText: '',
customTime: {
from: null,
to: null,
},
};
},
computed: {
applyEnabled() {
return Boolean(this.inputState.from && this.inputState.to);
},
inputState() {
const { from, to } = this.customTime;
return {
from: from && isDateTimePickerInputValid(from),
to: to && isDateTimePickerInputValid(to),
};
},
},
mounted() {
const range = getTimeWindow(this.selectedTimeWindow);
if (range) {
this.selectedTimeWindowText = this.timeWindows[range];
} else {
this.customTime = {
from: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.start)),
to: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.end)),
};
this.selectedTimeWindowText = sprintf(s__('%{from} to %{to}'), this.customTime);
}
},
methods: {
activeTimeWindow(key) {
return this.timeWindows[key] === this.selectedTimeWindowText;
},
setCustomTimeWindowParameter() {
this.$emit('onApply', {
start: stringToISODate(this.customTime.from),
end: stringToISODate(this.customTime.to),
});
},
setTimeWindowParameter(key) {
const { start, end } = getTimeDiff(key);
this.$emit('onApply', {
start,
end,
});
},
closeDropdown() {
this.$refs.dropdown.hide();
},
},
};
</script>
<template>
<gl-dropdown
ref="dropdown"
:text="selectedTimeWindowText"
menu-class="time-window-dropdown-menu"
class="js-time-window-dropdown"
>
<div class="d-flex justify-content-between time-window-dropdown-menu-container">
<gl-form-group
:label="__('Custom range')"
label-for="custom-from-time"
class="custom-time-range-form-group col-md-7 p-0 m-0"
>
<date-time-picker-input
id="custom-time-from"
v-model="customTime.from"
:label="__('From')"
:state="inputState.from"
/>
<date-time-picker-input
id="custom-time-to"
v-model="customTime.to"
:label="__('To')"
:state="inputState.to"
/>
<gl-form-group>
<gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
<gl-button
variant="success"
:disabled="!applyEnabled"
@click="setCustomTimeWindowParameter"
>{{ __('Apply') }}</gl-button
>
</gl-form-group>
</gl-form-group>
<gl-form-group
:label="__('Quick range')"
label-for="group-id-dropdown"
label-align="center"
class="col-md-4 p-0 m-0"
>
<gl-dropdown-item
v-for="(value, key) in timeWindows"
:key="key"
:active="activeTimeWindow(key)"
active-class="active"
@click="setTimeWindowParameter(key)"
>
<icon
name="mobile-issue-close"
class="align-bottom"
:class="{ invisible: !activeTimeWindow(key) }"
/>
{{ value }}
</gl-dropdown-item>
</gl-form-group>
</div>
</gl-dropdown>
</template>
<script>
import _ from 'underscore';
import { s__, sprintf } from '~/locale';
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { dateFormats } from '~/monitoring/constants';
const inputGroupText = {
invalidFeedback: sprintf(s__('Format: %{dateFormat}'), {
dateFormat: dateFormats.dateTimePicker.format,
}),
placeholder: dateFormats.dateTimePicker.format,
};
export default {
components: {
GlFormGroup,
GlFormInput,
},
props: {
state: {
default: null,
required: true,
validator: prop => typeof prop === 'boolean' || prop === null,
},
value: {
default: null,
required: false,
validator: prop => typeof prop === 'string' || prop === null,
},
label: {
type: String,
default: '',
required: true,
},
id: {
type: String,
required: false,
default: () => _.uniqueId('dateTimePicker_'),
},
},
data() {
return {
inputGroupText,
};
},
computed: {
invalidFeedback() {
return this.state ? '' : this.inputGroupText.invalidFeedback;
},
inputState() {
// When the state is valid we want to show no
// green outline. Hence passing null and not true.
if (this.state === true) {
return null;
}
return this.state;
},
},
methods: {
onInputBlur(e) {
this.$emit('input', e.target.value.trim() || null);
},
},
};
</script>
<template>
<gl-form-group :label="label" label-size="sm" :label-for="id" :invalid-feedback="invalidFeedback">
<gl-form-input
:id="id"
:value="value"
:state="inputState"
:placeholder="inputGroupText.placeholder"
@blur="onInputBlur"
/>
</gl-form-group>
</template>
......@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const sidebarAnimationDuration = 300; // milliseconds.
export const chartHeight = 300;
/**
* Valid strings for this regex are
* 2019-10-01 and 2019-10-01 01:02:03
*/
export const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
export const graphTypes = {
deploymentData: 'scatter',
......@@ -28,6 +33,11 @@ export const timeWindows = {
export const dateFormats = {
timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT',
dateTimePicker: {
format: 'yyyy-mm-dd hh:mm:ss',
ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'",
stringDate: 'yyyy-mm-dd HH:MM:ss',
},
};
export const secondsIn = {
......
import { secondsIn, timeWindowsKeyNames } from './constants';
import dateformat from 'dateformat';
import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
const secondsToMilliseconds = seconds => seconds * 1000;
......@@ -19,7 +20,49 @@ export const getTimeWindow = ({ start, end }) =>
return timeRange;
}
return acc;
}, timeWindowsKeyNames.eightHours);
}, null);
export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
/**
* The URL params start and end need to be validated
* before passing them down to other components.
*
* @param {string} dateString
*/
export const isValidDate = dateString => {
try {
// dateformat throws error that can be caught.
// This is better than using `new Date()`
if (dateString && dateString.trim()) {
dateformat(dateString, 'isoDateTime');
return true;
}
return false;
} catch {
return false;
}
};
/**
* Convert the input in Time picker component to ISO date.
*
* @param {string} val
* @returns {string}
*/
export const stringToISODate = val =>
dateformat(new Date(val.replace(/-/g, '/')), dateFormats.dateTimePicker.ISODate, true);
/**
* Convert the ISO date received from the URL to string
* for the Time picker component.
*
* @param {Date} date
* @returns {string}
*/
export const ISODateToString = date => dateformat(date, dateFormats.dateTimePicker.stringDate);
/**
* This method is used to validate if the graph data format for a chart component
......
......@@ -46,6 +46,20 @@
}
}
.prometheus-graphs-header {
.time-window-dropdown-menu {
padding: $gl-padding $gl-padding 0 $gl-padding-12;
}
.time-window-dropdown-menu-container {
width: 360px;
}
.custom-time-range-form-group > label {
padding-bottom: $gl-padding;
}
}
.prometheus-panel {
margin-top: 20px;
}
......
......@@ -161,7 +161,7 @@ class IssuableFinder
labels_count = label_names.any? ? label_names.count : 1
labels_count = 1 if use_cte_for_search?
finder.execute.reorder(nil).group(:state).count.each do |key, value|
finder.execute.reorder(nil).group(:state_id).count.each do |key, value|
counts[count_key(key)] += value / labels_count
end
......@@ -385,7 +385,8 @@ class IssuableFinder
end
def count_key(value)
Array(value).last.to_sym
value = Array(value).last
klass.available_states.key(value)
end
# Negates all params found in `negatable_params`
......@@ -444,7 +445,6 @@ class IssuableFinder
items
end
# rubocop: disable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
when 'closed'
......@@ -454,12 +454,11 @@ class IssuableFinder
when 'opened'
items.opened
when 'locked'
items.where(state: 'locked')
items.with_state(:locked)
else
items
end
end
# rubocop: enable CodeReuse/ActiveRecord
def by_group(items)
# Selection by group is already covered by `by_project` and `projects`
......
......@@ -217,6 +217,8 @@ module Ci
scope :for_sha, -> (sha) { where(sha: sha) }
scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
scope :for_ref, -> (ref) { where(ref: ref) }
scope :for_id, -> (id) { where(id: id) }
scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
scope :triggered_by_merge_request, -> (merge_request) do
......
......@@ -25,12 +25,20 @@ module Issuable
include UpdatedAtFilterable
include IssuableStates
include ClosedAtFilterable
include VersionedDescription
TITLE_LENGTH_MAX = 255
TITLE_HTML_LENGTH_MAX = 800
DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
STATE_ID_MAP = {
opened: 1,
closed: 2,
merged: 3,
locked: 4
}.with_indifferent_access.freeze
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance.
......@@ -172,13 +180,17 @@ module Issuable
fuzzy_search(query, [:title])
end
# Available state values persisted in state_id column using state machine
def available_states
@available_states ||= STATE_ID_MAP.slice(*available_state_names)
end
# Available state names used to persist state_id column using state machine
#
# Override this on subclasses if different states are needed
#
# Check MergeRequest.available_states for example
def available_states
@available_states ||= { opened: 1, closed: 2 }.with_indifferent_access
# Check MergeRequest.available_states_names for example
def available_state_names
[:opened, :closed]
end
# Searches for records with a matching title or description.
......@@ -297,6 +309,14 @@ module Issuable
end
end
def state
self.class.available_states.key(state_id)
end
def state=(value)
self.state_id = self.class.available_states[value]
end
def resource_parent
project
end
......
......@@ -4,22 +4,20 @@ module IssuableStates
extend ActiveSupport::Concern
# The state:string column is being migrated to state_id:integer column
# This is a temporary hook to populate state_id column with new values
# and should be removed after the state column is removed.
# Check https://gitlab.com/gitlab-org/gitlab-foss/issues/51789 for more information
# This is a temporary hook to keep state column in sync until it is removed.
# Check https: https://gitlab.com/gitlab-org/gitlab/issues/33814 for more information
# The state column can be safely removed after 2019-10-27
included do
before_save :set_state_id
before_save :sync_issuable_deprecated_state
end
def set_state_id
return if state.nil? || state.empty?
def sync_issuable_deprecated_state
return if self.is_a?(Epic)
return unless respond_to?(:state)
return if state_id.nil?
# Needed to prevent breaking some migration specs that
# rollback database to a point where state_id does not exist.
# We can use this guard clause for now since this file will
# be removed in the next release.
return unless self.has_attribute?(:state_id)
deprecated_state = self.class.available_states.key(state_id)
self.state_id = self.class.available_states[state]
self.write_attribute(:state, deprecated_state)
end
end
......@@ -6,7 +6,9 @@ module Milestoneish
end
def closed_issues_count(user)
count_issues_by_state(user)['closed'].to_i
closed_state_id = Issue.available_states[:closed]
count_issues_by_state(user)[closed_state_id].to_i
end
def complete?(user)
......@@ -117,7 +119,7 @@ module Milestoneish
def count_issues_by_state(user)
memoize_per_user(user, :count_issues_by_state) do
issues_visible_to_user(user).reorder(nil).group(:state).count
issues_visible_to_user(user).reorder(nil).group(:state_id).count
end
end
......
# frozen_string_literal: true
module VersionedDescription
extend ActiveSupport::Concern
included do
attr_accessor :saved_description_version
has_many :description_versions
after_update :save_description_version
end
private
def save_description_version
self.saved_description_version = nil
return unless Feature.enabled?(:save_description_versions, issuing_parent)
return unless saved_change_to_description?
unless description_versions.exists?
description_versions.create!(
description: description_before_last_save,
created_at: created_at
)
end
self.saved_description_version = description_versions.create!(description: description)
end
end
# frozen_string_literal: true
module WorkerAttributes
extend ActiveSupport::Concern
class_methods do
def feature_category(value)
raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
worker_attributes[:feature_category] = value
end
# Special case: mark this work as not associated with a feature category
# this should be used for cross-cutting concerns, such as mailer workers.
def feature_category_not_owned!
worker_attributes[:feature_category] = :not_owned
end
def get_feature_category
get_worker_attribute(:feature_category)
end
def feature_category_not_owned?
get_worker_attribute(:feature_category) == :not_owned
end
protected
# Returns a worker attribute declared on this class or its parent class.
# This approach allows declared attributes to be inherited by
# child classes.
def get_worker_attribute(name)
worker_attributes[name] || superclass_worker_attributes(name)
end
private
def worker_attributes
@attributes ||= {}
end
def superclass_worker_attributes(name)
return unless superclass.include? WorkerAttributes
superclass.get_worker_attribute(name)
end
end
end
# frozen_string_literal: true
class DescriptionVersion < ApplicationRecord
belongs_to :issue
belongs_to :merge_request
validate :exactly_one_issuable
def self.issuable_attrs
%i(issue merge_request).freeze
end
private
def exactly_one_issuable
issuable_count = self.class.issuable_attrs.count { |attr| self["#{attr}_id"] }
errors.add(:base, "Exactly one of #{self.class.issuable_attrs.join(', ')} is required") if issuable_count != 1
end
end
DescriptionVersion.prepend_if_ee('EE::DescriptionVersion')
......@@ -71,7 +71,7 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
state_machine :state, initial: :opened do
state_machine :state_id, initial: :opened do
event :close do
transition [:opened] => :closed
end
......@@ -80,8 +80,8 @@ class Issue < ApplicationRecord
transition closed: :opened
end
state :opened
state :closed
state :opened, value: Issue.available_states[:opened]
state :closed, value: Issue.available_states[:closed]
before_transition any => :closed do |issue|
issue.closed_at = issue.system_note_timestamp
......@@ -93,6 +93,13 @@ class Issue < ApplicationRecord
end
end
# Alias to state machine .with_state_id method
# This needs to be defined after the state machine block to avoid errors
class << self
alias_method :with_state, :with_state_id
alias_method :with_states, :with_state_ids
end
def self.relative_positioning_query_base(issue)
in_projects(issue.parent_ids)
end
......
......@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord
# when creating new merge request
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
state_machine :state, initial: :opened do
# Keep states definition to be evaluated before the state_machine block to avoid spec failures.
# If this gets evaluated after, the `merged` and `locked` states which are overrided can be nil.
def self.available_state_names
super + [:merged, :locked]
end
state_machine :state_id, initial: :opened do
event :close do
transition [:opened] => :closed
end
......@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord
end
end
state :opened
state :closed
state :merged
state :locked
state :opened, value: MergeRequest.available_states[:opened]
state :closed, value: MergeRequest.available_states[:closed]
state :merged, value: MergeRequest.available_states[:merged]
state :locked, value: MergeRequest.available_states[:locked]
end
# Alias to state machine .with_state_id method
# This needs to be defined after the state machine block to avoid errors
class << self
alias_method :with_state, :with_state_id
alias_method :with_states, :with_state_ids
end
state_machine :merge_status, initial: :unchecked do
......@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord
'!'
end
def self.available_states
@available_states ||= super.merge(merged: 3, locked: 4)
end
# Returns the top 100 target branches
#
# The returned value is a Array containing branch names
......
......@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord
metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition)
condition = MergeRequest.arel_table[:state].eq(:merged)
condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:merged])
.and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before))
.and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
......@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord
end
scope :old_closed_diffs, -> (before) do
condition = MergeRequest.arel_table[:state].eq(:closed)
condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:closed])
.and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
joins(merge_request: :metrics).where(condition)
......
......@@ -161,7 +161,7 @@ class HipchatService < Service
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
title = render_line(obj_attr[:title])
state = obj_attr[:state]
state = Issue.available_states.key(obj_attr[:state_id])
issue_iid = obj_attr[:iid]
issue_url = obj_attr[:url]
description = obj_attr[:description]
......
......@@ -54,7 +54,7 @@ class PushEvent < Event
.select(1)
.where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref')
.where(state: :opened)
.with_state(:opened)
# For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're
......
......@@ -23,6 +23,7 @@ class SystemNoteMetadata < ApplicationRecord
validates :action, inclusion: { in: :icon_types }, allow_nil: true
belongs_to :note
belongs_to :description_version
def icon_types
ICON_TYPES
......
......@@ -44,7 +44,7 @@ module Projects
end
expose :url do |service|
service.dig('status', 'url')
service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}"
end
expose :description do |service|
......
......@@ -39,6 +39,10 @@ module Issuable
if note.system_note_metadata
new_params[:system_note_metadata] = note.system_note_metadata.dup
# TODO: Implement copying of description versions when an issue is moved
# https://gitlab.com/gitlab-org/gitlab/issues/32300
new_params[:system_note_metadata].description_version = nil
end
new_note.update(new_params)
......
......@@ -10,6 +10,10 @@ class NoteSummary
project: project, author: author, note: body }
@metadata = { action: action, commit_count: commit_count }.compact
if action == 'description' && noteable.saved_description_version
@metadata[:description_version] = noteable.saved_description_version
end
set_commit_params if note[:noteable].is_a?(Commit)
end
......
......@@ -180,10 +180,11 @@ module ObjectStorage
end
def workhorse_authorize(has_length:, maximum_size: nil)
{
RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size),
TempPath: workhorse_local_upload_path
}.compact
if self.object_store_enabled? && self.direct_upload_enabled?
{ RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size) }
else
{ TempPath: workhorse_local_upload_path }
end
end
def workhorse_local_upload_path
......
......@@ -20,4 +20,5 @@
- if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
= render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
= render 'shared/empty_states/issues', new_project_issue_button_path: new_project_issue_button_path, show_import_button: true
- button_path = local_assigns.fetch(:button_path, false)
- button_path = local_assigns.fetch(:new_project_issue_button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
- show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
......@@ -56,4 +56,3 @@
- if show_import_button
= render 'projects/issues/import_csv/modal'
......@@ -4,6 +4,8 @@ class AdminEmailWorker
include ApplicationWorker
include CronjobQueue
feature_category_not_owned!
def perform
send_repository_check_mail if Gitlab::CurrentSettings.repository_checks_enabled
end
......
......@@ -4,6 +4,8 @@ class AuthorizedProjectsWorker
include ApplicationWorker
prepend WaitableWorker
feature_category :authentication_and_authorization
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
# visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
# for more details.
......
......@@ -4,6 +4,7 @@ class AutoMergeProcessWorker
include ApplicationWorker
queue_namespace :auto_merge
feature_category :continuous_delivery
def perform(merge_request_id)
MergeRequest.find_by_id(merge_request_id).try do |merge_request|
......
......@@ -3,6 +3,8 @@
class BackgroundMigrationWorker
include ApplicationWorker
feature_category_not_owned!
# The minimum amount of time between processing two jobs of the same migration
# class.
#
......
......@@ -5,6 +5,7 @@ class BuildHooksWorker
include PipelineQueue
queue_namespace :pipeline_hooks
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
......
......@@ -5,6 +5,7 @@ class BuildQueueWorker
include PipelineQueue
queue_namespace :pipeline_processing
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
......
......@@ -3,6 +3,8 @@
class ChatNotificationWorker
include ApplicationWorker
feature_category :chatops
RESCHEDULE_INTERVAL = 2.seconds
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -5,6 +5,8 @@ module Ci
include ApplicationWorker
include CronjobQueue
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform
# Archive stale live traces which still resides in redis or database
......
......@@ -6,6 +6,7 @@ module Ci
include PipelineQueue
queue_namespace :pipeline_processing
feature_category :continuous_integration
def perform(build_id)
Ci::Build.find_by_id(build_id).try do |build|
......
......@@ -6,6 +6,7 @@ module Ci
include PipelineQueue
queue_namespace :pipeline_processing
feature_category :continuous_integration
def perform(build_id)
::Ci::Build.find_by_id(build_id).try do |build|
......
......@@ -4,6 +4,7 @@ class CleanupContainerRepositoryWorker
include ApplicationWorker
queue_namespace :container_repository
feature_category :container_registry
attr_reader :container_repository, :current_user
......
......@@ -8,6 +8,7 @@ module ApplicationWorker
extend ActiveSupport::Concern
include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
include WorkerAttributes
included do
set_queue
......
......@@ -5,5 +5,6 @@ module AutoDevopsQueue
included do
queue_namespace :auto_devops
feature_category :auto_devops
end
end
......@@ -5,5 +5,6 @@ module ChaosQueue
included do
queue_namespace :chaos
feature_category :chaos_engineering
end
end
......@@ -8,5 +8,6 @@ module ClusterQueue
included do
queue_namespace :gcp_cluster
feature_category :kubernetes_configuration
end
end
......@@ -12,6 +12,8 @@ module Gitlab
include GithubImport::Queue
include ReschedulingMethods
include NotifyUponDeath
feature_category :importers
end
# project - An instance of `Project` to import the data into.
......
......@@ -7,6 +7,7 @@ module Gitlab
included do
queue_namespace :github_importer
feature_category :importers
# If a job produces an error it may block a stage from advancing
# forever. To prevent this from happening we prevent jobs from going to
......
......@@ -8,5 +8,6 @@ module ObjectPoolQueue
included do
queue_namespace :object_pool
feature_category :gitaly
end
end
......@@ -8,5 +8,6 @@ module PipelineBackgroundQueue
included do
queue_namespace :pipeline_background
feature_category :continuous_integration
end
end
......@@ -8,5 +8,6 @@ module PipelineQueue
included do
queue_namespace :pipeline_default
feature_category :continuous_integration
end
end
......@@ -6,7 +6,7 @@ module RepositoryCheckQueue
included do
queue_namespace :repository_check
sidekiq_options retry: false
feature_category :source_code_management
end
end
......@@ -8,5 +8,6 @@ module TodosDestroyerQueue
included do
queue_namespace :todos_destroyer
feature_category :issue_tracking
end
end
......@@ -3,6 +3,8 @@
class CreateEvidenceWorker
include ApplicationWorker
feature_category :release_governance
def perform(release_id)
release = Release.find_by_id(release_id)
return unless release
......
......@@ -3,6 +3,8 @@
class CreateGpgSignatureWorker
include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id)
# Older versions of Git::BranchPushService may push a single commit ID on
......
......@@ -3,6 +3,8 @@
class CreateNoteDiffFileWorker
include ApplicationWorker
feature_category :source_code_management
def perform(diff_note_id)
diff_note = DiffNote.find(diff_note_id)
......
......@@ -5,6 +5,7 @@ class CreatePipelineWorker
include PipelineQueue
queue_namespace :pipeline_creation
feature_category :continuous_integration
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
......
......@@ -5,6 +5,7 @@ class DeleteContainerRepositoryWorker
include ExclusiveLeaseGuard
queue_namespace :container_repository
feature_category :container_registry
LEASE_TIMEOUT = 1.hour
......
......@@ -3,6 +3,8 @@
class DeleteDiffFilesWorker
include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord
def perform(merge_request_diff_id)
merge_request_diff = MergeRequestDiff.find(merge_request_diff_id)
......
......@@ -3,6 +3,8 @@
class DeleteMergedBranchesWorker
include ApplicationWorker
feature_category :source_code_management
def perform(project_id, user_id)
begin
project = Project.find(project_id)
......
......@@ -3,6 +3,8 @@
class DeleteStoredFilesWorker
include ApplicationWorker
feature_category_not_owned!
def perform(class_name, keys)
klass = begin
class_name.constantize
......
......@@ -3,6 +3,8 @@
class DeleteUserWorker
include ApplicationWorker
feature_category :authentication_and_authorization
def perform(current_user_id, delete_user_id, options = {})
delete_user = User.find(delete_user_id)
current_user = User.find(current_user_id)
......
......@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker
queue_namespace :deployment
feature_category :continuous_delivery
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try(:execute_hooks)
......
......@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker
queue_namespace :deployment
feature_category :continuous_delivery
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try do |deployment|
......
......@@ -6,6 +6,7 @@ class DetectRepositoryLanguagesWorker
include ExclusiveLeaseGuard
sidekiq_options retry: 1
feature_category :source_code_management
LEASE_TIMEOUT = 300
......
......@@ -3,6 +3,8 @@
class EmailReceiverWorker
include ApplicationWorker
feature_category :issue_tracking
def perform(raw)
return unless Gitlab::IncomingEmail.enabled?
......
......@@ -5,6 +5,8 @@ class EmailsOnPushWorker
attr_reader :email, :skip_premailer
feature_category :source_code_management
def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys!
options.reverse_merge!(
......
......@@ -4,6 +4,8 @@ class ExpireBuildArtifactsWorker
include ApplicationWorker
include CronjobQueue
feature_category :continuous_integration
def perform
if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
perform_efficient_artifacts_removal
......
......@@ -3,6 +3,8 @@
class ExpireBuildInstanceArtifactsWorker
include ApplicationWorker
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
build = Ci::Build
......
......@@ -4,6 +4,7 @@ class GitGarbageCollectWorker
include ApplicationWorker
sidekiq_options retry: false
feature_category :gitaly
# Timeout set to 24h
LEASE_TIMEOUT = 86400
......
......@@ -10,6 +10,7 @@ module Gitlab
include ApplicationWorker
sidekiq_options dead: false
feature_category :importers
INTERVAL = 30.seconds.to_i
......
......@@ -4,6 +4,8 @@ class GitlabShellWorker
include ApplicationWorker
include Gitlab::ShellAdapter
feature_category :source_code_management
def perform(action, *arg)
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
......
......@@ -6,6 +6,8 @@ class GitlabUsagePingWorker
include ApplicationWorker
include CronjobQueue
feature_category_not_owned!
# Retry for up to approximately three hours then give up.
sidekiq_options retry: 10, dead: false
......
......@@ -4,6 +4,8 @@ class GroupDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
feature_category :groups
def perform(group_id, user_id)
begin
group = Group.find(group_id)
......
......@@ -3,6 +3,9 @@
module HashedStorage
class BaseWorker
include ExclusiveLeaseGuard
include WorkerAttributes
feature_category :source_code_management
LEASE_TIMEOUT = 30.seconds.to_i
LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'
......
......@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker
queue_namespace :hashed_storage
feature_category :source_code_management
# @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch
......
......@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker
queue_namespace :hashed_storage
feature_category :source_code_management
# @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch
......
......@@ -4,6 +4,8 @@ class ImportExportProjectCleanupWorker
include ApplicationWorker
include CronjobQueue
feature_category :importers
def perform
ImportExportCleanUpService.new.execute
end
......
......@@ -3,6 +3,8 @@
class ImportIssuesCsvWorker
include ApplicationWorker
feature_category :issue_tracking
sidekiq_retries_exhausted do |job|
Upload.find(job['args'][2]).destroy
end
......
......@@ -3,6 +3,8 @@
class InvalidGpgSignatureUpdateWorker
include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord
def perform(gpg_key_id)
gpg_key = GpgKey.find_by(id: gpg_key_id)
......
......@@ -6,6 +6,8 @@ require 'socket'
class IrkerWorker
include ApplicationWorker
feature_category :integrations
def perform(project_id, chans, colors, push_data, settings)
project = Project.find(project_id)
......
......@@ -4,6 +4,8 @@ class IssueDueSchedulerWorker
include ApplicationWorker
include CronjobQueue
feature_category :issue_tracking
# rubocop: disable CodeReuse/ActiveRecord
def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
......
......@@ -5,6 +5,8 @@ module MailScheduler
include ApplicationWorker
include MailSchedulerQueue
feature_category :issue_tracking
# rubocop: disable CodeReuse/ActiveRecord
def perform(project_id)
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
......
......@@ -7,6 +7,8 @@ module MailScheduler
include ApplicationWorker
include MailSchedulerQueue
feature_category :issue_tracking
def perform(meth, *args)
check_arguments!(args)
......
......@@ -3,6 +3,8 @@
class MergeWorker
include ApplicationWorker
feature_category :source_code_management
def perform(merge_request_id, current_user_id, params)
params = params.with_indifferent_access
current_user = User.find(current_user_id)
......
......@@ -3,6 +3,8 @@
class MigrateExternalDiffsWorker
include ApplicationWorker
feature_category :source_code_management
def perform(merge_request_diff_id)
diff = MergeRequestDiff.find_by_id(merge_request_diff_id)
return unless diff
......
......@@ -10,6 +10,8 @@ class NamespacelessProjectDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
feature_category :authentication_and_authorization
def perform(project_id)
begin
project = Project.unscoped.find(project_id)
......@@ -31,6 +33,6 @@ class NamespacelessProjectDestroyWorker
def unlink_fork(project)
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
merge_requests.update_all(state: 'closed')
merge_requests.update_all(state_id: MergeRequest.available_states[:closed])
end
end
......@@ -5,6 +5,8 @@ module Namespaces
include ApplicationWorker
include CronjobQueue
feature_category :source_code_management
# Worker to prune pending rows on Namespace::AggregationSchedule
# It's scheduled to run once a day at 1:05am.
def perform
......
......@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker
queue_namespace :update_namespace_statistics
feature_category :source_code_management
def perform(namespace_id)
namespace = Namespace.find(namespace_id)
......
......@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker
queue_namespace :update_namespace_statistics
feature_category :source_code_management
def perform(namespace_id)
return unless aggregation_schedules_table_exists?
......
......@@ -4,6 +4,8 @@ class NewIssueWorker
include ApplicationWorker
include NewIssuable
feature_category :issue_tracking
def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id)
......
......@@ -4,6 +4,8 @@ class NewMergeRequestWorker
include ApplicationWorker
include NewIssuable
feature_category :source_code_management
def perform(merge_request_id, user_id)
return unless objects_found?(merge_request_id, user_id)
......
......@@ -3,6 +3,8 @@
class NewNoteWorker
include ApplicationWorker
feature_category :issue_tracking
# Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later)
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -4,6 +4,7 @@ class NewReleaseWorker
include ApplicationWorker
queue_namespace :notifications
feature_category :release_orchestration
def perform(release_id)
release = Release.with_project_and_namespace.find_by_id(release_id)
......
......@@ -6,6 +6,7 @@ module ObjectStorage
include ObjectStorageQueue
sidekiq_options retry: 5
feature_category_not_owned!
def perform(uploader_class_name, subject_class_name, file_field, subject_id)
uploader_class = uploader_class_name.constantize
......
......@@ -5,6 +5,8 @@ module ObjectStorage
include ApplicationWorker
include ObjectStorageQueue
feature_category_not_owned!
SanityCheckError = Class.new(StandardError)
class MigrationResult
......
......@@ -4,6 +4,8 @@ class PagesDomainRemovalCronWorker
include ApplicationWorker
include CronjobQueue
feature_category :pages
def perform
PagesDomain.for_removal.find_each do |domain|
domain.destroy!
......
......@@ -4,6 +4,8 @@ class PagesDomainSslRenewalCronWorker
include ApplicationWorker
include CronjobQueue
feature_category :pages
def perform
return unless ::Gitlab::LetsEncrypt.enabled?
......
......@@ -3,6 +3,8 @@
class PagesDomainSslRenewalWorker
include ApplicationWorker
feature_category :pages
def perform(domain_id)
domain = PagesDomain.find_by_id(domain_id)
return unless domain&.enabled?
......
......@@ -4,6 +4,8 @@ class PagesDomainVerificationCronWorker
include ApplicationWorker
include CronjobQueue
feature_category :pages
def perform
return if Gitlab::Database.read_only?
......
......@@ -3,6 +3,8 @@
class PagesDomainVerificationWorker
include ApplicationWorker
feature_category :pages
# rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
return if Gitlab::Database.read_only?
......
......@@ -4,6 +4,7 @@ class PagesWorker
include ApplicationWorker
sidekiq_options retry: 3
feature_category :pages
def perform(action, *arg)
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
......
......@@ -5,6 +5,7 @@ class PipelineProcessWorker
include PipelineQueue
queue_namespace :pipeline_processing
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, build_ids = nil)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
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