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

Add latest changes from gitlab-org/gitlab@master

parent 9bbb32b2
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
/doc/ @axil @marcia @eread @mikelewis /doc/ @axil @marcia @eread @mikelewis
# Frontend maintainers should see everything in `app/assets/` # Frontend maintainers should see everything in `app/assets/`
app/assets/ @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 *.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
# Database maintainers should review changes in `db/` # Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database db/ @gitlab-org/maintainers/database
......
...@@ -273,11 +273,6 @@ RSpec/ContextWording: ...@@ -273,11 +273,6 @@ RSpec/ContextWording:
RSpec/EmptyLineAfterFinalLet: RSpec/EmptyLineAfterFinalLet:
Enabled: false Enabled: false
# Offense count: 232
# Cop supports --auto-correct.
RSpec/EmptyLineAfterSubject:
Enabled: false
# Offense count: 719 # Offense count: 719
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
......
...@@ -12,23 +12,19 @@ import { ...@@ -12,23 +12,19 @@ import {
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue'; 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 invalidUrl from '~/lib/utils/invalid_url';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; 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 MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue'; import MonitorSingleStatChart from './charts/single_stat.vue';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.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 TrackEventDirective from '~/vue_shared/directives/track_event';
import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
import {
getTimeDiff,
getTimeWindow,
downloadCSVOptions,
generateLinkToChartOptions,
} from '../utils';
let sidebarMutationObserver; let sidebarMutationObserver;
...@@ -46,6 +42,7 @@ export default { ...@@ -46,6 +42,7 @@ export default {
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
GlModal, GlModal,
DateTimePicker,
}, },
directives: { directives: {
GlModal: GlModalDirective, GlModal: GlModalDirective,
...@@ -171,10 +168,8 @@ export default { ...@@ -171,10 +168,8 @@ export default {
return { return {
state: 'gettingStarted', state: 'gettingStarted',
elWidth: 0, elWidth: 0,
selectedTimeWindow: '',
selectedTimeWindowKey: '',
formIsValid: null, formIsValid: null,
timeWindows: {}, selectedTimeWindow: {},
isRearrangingPanels: false, isRearrangingPanels: false,
}; };
}, },
...@@ -237,11 +232,13 @@ export default { ...@@ -237,11 +232,13 @@ export default {
end, end,
}; };
this.timeWindows = timeWindows; this.selectedTimeWindow = range;
this.selectedTimeWindowKey = getTimeWindow(range);
this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
this.fetchData(range); if (!isValidDate(start) || !isValidDate(end)) {
this.showInvalidDateError();
} else {
this.fetchData(range);
}
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), { sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
...@@ -298,6 +295,9 @@ export default { ...@@ -298,6 +295,9 @@ export default {
// See https://gitlab.com/gitlab-org/gitlab/issues/27835 // See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1); metrics.splice(graphIndex, 1);
}, },
showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.'));
},
generateLink(group, title, yLabel) { generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path; const dashboard = this.currentDashboard || this.firstDashboard.path;
const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null); const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
...@@ -320,16 +320,12 @@ export default { ...@@ -320,16 +320,12 @@ export default {
submitCustomMetricsForm() { submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit(); 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) { groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0; return this.chartsWithData(group.metrics).length > 0;
}, },
onDateTimePickerApply(timeWindowUrlParams) {
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
},
downloadCSVOptions, downloadCSVOptions,
generateLinkToChartOptions, generateLinkToChartOptions,
}, },
...@@ -342,14 +338,14 @@ export default { ...@@ -342,14 +338,14 @@ export default {
<template> <template>
<div class="prometheus-graphs"> <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"> <div class="row">
<template v-if="environmentsEndpoint"> <template v-if="environmentsEndpoint">
<gl-form-group <gl-form-group
:label="__('Dashboard')" :label="__('Dashboard')"
label-size="sm" label-size="sm"
label-for="monitor-dashboards-dropdown" 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 <gl-dropdown
id="monitor-dashboards-dropdown" id="monitor-dashboards-dropdown"
...@@ -372,7 +368,7 @@ export default { ...@@ -372,7 +368,7 @@ export default {
:label="s__('Metrics|Environment')" :label="s__('Metrics|Environment')"
label-size="sm" label-size="sm"
label-for="monitor-environments-dropdown" 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 <gl-dropdown
id="monitor-environments-dropdown" id="monitor-environments-dropdown"
...@@ -397,30 +393,19 @@ export default { ...@@ -397,30 +393,19 @@ export default {
:label="s__('Metrics|Show last')" :label="s__('Metrics|Show last')"
label-size="sm" label-size="sm"
label-for="monitor-time-window-dropdown" label-for="monitor-time-window-dropdown"
class="col-sm-6 col-md-4 col-lg-2" class="col-sm-6 col-md-6 col-lg-4"
> >
<gl-dropdown <date-time-picker
id="monitor-time-window-dropdown" :selected-time-window="selectedTimeWindow"
class="mb-0 d-flex js-time-window-dropdown" @onApply="onDateTimePickerApply"
toggle-class="dropdown-menu-toggle" />
:text="selectedTimeWindow"
>
<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>
</gl-form-group> </gl-form-group>
</template> </template>
<gl-form-group <gl-form-group
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length" v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length"
label-for="prometheus-graphs-dropdown-buttons" 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"> <div id="prometheus-graphs-dropdown-buttons">
<gl-button <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'; ...@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const sidebarAnimationDuration = 300; // milliseconds. export const sidebarAnimationDuration = 300; // milliseconds.
export const chartHeight = 300; 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 = { export const graphTypes = {
deploymentData: 'scatter', deploymentData: 'scatter',
...@@ -28,6 +33,11 @@ export const timeWindows = { ...@@ -28,6 +33,11 @@ export const timeWindows = {
export const dateFormats = { export const dateFormats = {
timeOfDay: 'h:MM TT', timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT', 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 = { export const secondsIn = {
......
import { secondsIn, timeWindowsKeyNames } from './constants'; import dateformat from 'dateformat';
import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
const secondsToMilliseconds = seconds => seconds * 1000; const secondsToMilliseconds = seconds => seconds * 1000;
...@@ -19,7 +20,49 @@ export const getTimeWindow = ({ start, end }) => ...@@ -19,7 +20,49 @@ export const getTimeWindow = ({ start, end }) =>
return timeRange; return timeRange;
} }
return acc; 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 * This method is used to validate if the graph data format for a chart component
......
...@@ -46,6 +46,20 @@ ...@@ -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 { .prometheus-panel {
margin-top: 20px; margin-top: 20px;
} }
......
...@@ -161,7 +161,7 @@ class IssuableFinder ...@@ -161,7 +161,7 @@ class IssuableFinder
labels_count = label_names.any? ? label_names.count : 1 labels_count = label_names.any? ? label_names.count : 1
labels_count = 1 if use_cte_for_search? 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 counts[count_key(key)] += value / labels_count
end end
...@@ -385,7 +385,8 @@ class IssuableFinder ...@@ -385,7 +385,8 @@ class IssuableFinder
end end
def count_key(value) def count_key(value)
Array(value).last.to_sym value = Array(value).last
klass.available_states.key(value)
end end
# Negates all params found in `negatable_params` # Negates all params found in `negatable_params`
...@@ -444,7 +445,6 @@ class IssuableFinder ...@@ -444,7 +445,6 @@ class IssuableFinder
items items
end end
# rubocop: disable CodeReuse/ActiveRecord
def by_state(items) def by_state(items)
case params[:state].to_s case params[:state].to_s
when 'closed' when 'closed'
...@@ -454,12 +454,11 @@ class IssuableFinder ...@@ -454,12 +454,11 @@ class IssuableFinder
when 'opened' when 'opened'
items.opened items.opened
when 'locked' when 'locked'
items.where(state: 'locked') items.with_state(:locked)
else else
items items
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
def by_group(items) def by_group(items)
# Selection by group is already covered by `by_project` and `projects` # Selection by group is already covered by `by_project` and `projects`
......
...@@ -217,6 +217,8 @@ module Ci ...@@ -217,6 +217,8 @@ module Ci
scope :for_sha, -> (sha) { where(sha: sha) } scope :for_sha, -> (sha) { where(sha: sha) }
scope :for_source_sha, -> (source_sha) { where(source_sha: source_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_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 :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
scope :triggered_by_merge_request, -> (merge_request) do scope :triggered_by_merge_request, -> (merge_request) do
......
...@@ -25,12 +25,20 @@ module Issuable ...@@ -25,12 +25,20 @@ module Issuable
include UpdatedAtFilterable include UpdatedAtFilterable
include IssuableStates include IssuableStates
include ClosedAtFilterable include ClosedAtFilterable
include VersionedDescription
TITLE_LENGTH_MAX = 255 TITLE_LENGTH_MAX = 255
TITLE_HTML_LENGTH_MAX = 800 TITLE_HTML_LENGTH_MAX = 800
DESCRIPTION_LENGTH_MAX = 1.megabyte DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes 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 # This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance. # lists avoiding n+1 queries and improving performance.
...@@ -172,13 +180,17 @@ module Issuable ...@@ -172,13 +180,17 @@ module Issuable
fuzzy_search(query, [:title]) fuzzy_search(query, [:title])
end 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 # Override this on subclasses if different states are needed
# #
# Check MergeRequest.available_states for example # Check MergeRequest.available_states_names for example
def available_states def available_state_names
@available_states ||= { opened: 1, closed: 2 }.with_indifferent_access [:opened, :closed]
end end
# Searches for records with a matching title or description. # Searches for records with a matching title or description.
...@@ -297,6 +309,14 @@ module Issuable ...@@ -297,6 +309,14 @@ module Issuable
end end
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 def resource_parent
project project
end end
......
...@@ -4,22 +4,20 @@ module IssuableStates ...@@ -4,22 +4,20 @@ module IssuableStates
extend ActiveSupport::Concern extend ActiveSupport::Concern
# The state:string column is being migrated to state_id:integer column # 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 # This is a temporary hook to keep state column in sync until it is removed.
# and should be removed after the state column is removed. # Check https: https://gitlab.com/gitlab-org/gitlab/issues/33814 for more information
# Check https://gitlab.com/gitlab-org/gitlab-foss/issues/51789 for more information # The state column can be safely removed after 2019-10-27
included do included do
before_save :set_state_id before_save :sync_issuable_deprecated_state
end end
def set_state_id def sync_issuable_deprecated_state
return if state.nil? || state.empty? return if self.is_a?(Epic)
return unless respond_to?(:state)
return if state_id.nil?
# Needed to prevent breaking some migration specs that deprecated_state = self.class.available_states.key(state_id)
# 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)
self.state_id = self.class.available_states[state] self.write_attribute(:state, deprecated_state)
end end
end end
...@@ -6,7 +6,9 @@ module Milestoneish ...@@ -6,7 +6,9 @@ module Milestoneish
end end
def closed_issues_count(user) 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 end
def complete?(user) def complete?(user)
...@@ -117,7 +119,7 @@ module Milestoneish ...@@ -117,7 +119,7 @@ module Milestoneish
def count_issues_by_state(user) def count_issues_by_state(user)
memoize_per_user(user, :count_issues_by_state) do 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
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 ...@@ -71,7 +71,7 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true attr_spammable :description, spam_description: true
state_machine :state, initial: :opened do state_machine :state_id, initial: :opened do
event :close do event :close do
transition [:opened] => :closed transition [:opened] => :closed
end end
...@@ -80,8 +80,8 @@ class Issue < ApplicationRecord ...@@ -80,8 +80,8 @@ class Issue < ApplicationRecord
transition closed: :opened transition closed: :opened
end end
state :opened state :opened, value: Issue.available_states[:opened]
state :closed state :closed, value: Issue.available_states[:closed]
before_transition any => :closed do |issue| before_transition any => :closed do |issue|
issue.closed_at = issue.system_note_timestamp issue.closed_at = issue.system_note_timestamp
...@@ -93,6 +93,13 @@ class Issue < ApplicationRecord ...@@ -93,6 +93,13 @@ class Issue < ApplicationRecord
end end
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) def self.relative_positioning_query_base(issue)
in_projects(issue.parent_ids) in_projects(issue.parent_ids)
end end
......
...@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord ...@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare 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 event :close do
transition [:opened] => :closed transition [:opened] => :closed
end end
...@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord ...@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord
end end
end end
state :opened state :opened, value: MergeRequest.available_states[:opened]
state :closed state :closed, value: MergeRequest.available_states[:closed]
state :merged state :merged, value: MergeRequest.available_states[:merged]
state :locked 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 end
state_machine :merge_status, initial: :unchecked do state_machine :merge_status, initial: :unchecked do
...@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord ...@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord
'!' '!'
end end
def self.available_states
@available_states ||= super.merge(merged: 3, locked: 4)
end
# Returns the top 100 target branches # Returns the top 100 target branches
# #
# The returned value is a Array containing branch names # The returned value is a Array containing branch names
......
...@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord
metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition) 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].lteq(before))
.and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil)) .and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
...@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord
end end
scope :old_closed_diffs, -> (before) do 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)) .and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
joins(merge_request: :metrics).where(condition) joins(merge_request: :metrics).where(condition)
......
...@@ -161,7 +161,7 @@ class HipchatService < Service ...@@ -161,7 +161,7 @@ class HipchatService < Service
obj_attr = data[:object_attributes] obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr) obj_attr = HashWithIndifferentAccess.new(obj_attr)
title = render_line(obj_attr[:title]) 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_iid = obj_attr[:iid]
issue_url = obj_attr[:url] issue_url = obj_attr[:url]
description = obj_attr[:description] description = obj_attr[:description]
......
...@@ -54,7 +54,7 @@ class PushEvent < Event ...@@ -54,7 +54,7 @@ class PushEvent < Event
.select(1) .select(1)
.where('merge_requests.source_project_id = events.project_id') .where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref') .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 # For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're # "push_event_payload" association not being set. Because of this we're
......
...@@ -23,6 +23,7 @@ class SystemNoteMetadata < ApplicationRecord ...@@ -23,6 +23,7 @@ class SystemNoteMetadata < ApplicationRecord
validates :action, inclusion: { in: :icon_types }, allow_nil: true validates :action, inclusion: { in: :icon_types }, allow_nil: true
belongs_to :note belongs_to :note
belongs_to :description_version
def icon_types def icon_types
ICON_TYPES ICON_TYPES
......
...@@ -44,7 +44,7 @@ module Projects ...@@ -44,7 +44,7 @@ module Projects
end end
expose :url do |service| expose :url do |service|
service.dig('status', 'url') service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}"
end end
expose :description do |service| expose :description do |service|
......
...@@ -39,6 +39,10 @@ module Issuable ...@@ -39,6 +39,10 @@ module Issuable
if note.system_note_metadata if note.system_note_metadata
new_params[:system_note_metadata] = note.system_note_metadata.dup 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 end
new_note.update(new_params) new_note.update(new_params)
......
...@@ -10,6 +10,10 @@ class NoteSummary ...@@ -10,6 +10,10 @@ class NoteSummary
project: project, author: author, note: body } project: project, author: author, note: body }
@metadata = { action: action, commit_count: commit_count }.compact @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) set_commit_params if note[:noteable].is_a?(Commit)
end end
......
...@@ -180,10 +180,11 @@ module ObjectStorage ...@@ -180,10 +180,11 @@ module ObjectStorage
end end
def workhorse_authorize(has_length:, maximum_size: nil) def workhorse_authorize(has_length:, maximum_size: nil)
{ if self.object_store_enabled? && self.direct_upload_enabled?
RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size), { RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size) }
TempPath: workhorse_local_upload_path else
}.compact { TempPath: workhorse_local_upload_path }
end
end end
def workhorse_local_upload_path def workhorse_local_upload_path
......
...@@ -20,4 +20,5 @@ ...@@ -20,4 +20,5 @@
- if new_issue_email - if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue' = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else - 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) - 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) - show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button - has_button = button_path || project_select_button
...@@ -56,4 +56,3 @@ ...@@ -56,4 +56,3 @@
- if show_import_button - if show_import_button
= render 'projects/issues/import_csv/modal' = render 'projects/issues/import_csv/modal'
...@@ -4,6 +4,8 @@ class AdminEmailWorker ...@@ -4,6 +4,8 @@ class AdminEmailWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category_not_owned!
def perform def perform
send_repository_check_mail if Gitlab::CurrentSettings.repository_checks_enabled send_repository_check_mail if Gitlab::CurrentSettings.repository_checks_enabled
end end
......
...@@ -4,6 +4,8 @@ class AuthorizedProjectsWorker ...@@ -4,6 +4,8 @@ class AuthorizedProjectsWorker
include ApplicationWorker include ApplicationWorker
prepend WaitableWorker prepend WaitableWorker
feature_category :authentication_and_authorization
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the # 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 # visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
# for more details. # for more details.
......
...@@ -4,6 +4,7 @@ class AutoMergeProcessWorker ...@@ -4,6 +4,7 @@ class AutoMergeProcessWorker
include ApplicationWorker include ApplicationWorker
queue_namespace :auto_merge queue_namespace :auto_merge
feature_category :continuous_delivery
def perform(merge_request_id) def perform(merge_request_id)
MergeRequest.find_by_id(merge_request_id).try do |merge_request| MergeRequest.find_by_id(merge_request_id).try do |merge_request|
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class BackgroundMigrationWorker class BackgroundMigrationWorker
include ApplicationWorker include ApplicationWorker
feature_category_not_owned!
# The minimum amount of time between processing two jobs of the same migration # The minimum amount of time between processing two jobs of the same migration
# class. # class.
# #
......
...@@ -5,6 +5,7 @@ class BuildHooksWorker ...@@ -5,6 +5,7 @@ class BuildHooksWorker
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_hooks queue_namespace :pipeline_hooks
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id) def perform(build_id)
......
...@@ -5,6 +5,7 @@ class BuildQueueWorker ...@@ -5,6 +5,7 @@ class BuildQueueWorker
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id) def perform(build_id)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class ChatNotificationWorker class ChatNotificationWorker
include ApplicationWorker include ApplicationWorker
feature_category :chatops
RESCHEDULE_INTERVAL = 2.seconds RESCHEDULE_INTERVAL = 2.seconds
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
......
...@@ -5,6 +5,8 @@ module Ci ...@@ -5,6 +5,8 @@ module Ci
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform def perform
# Archive stale live traces which still resides in redis or database # Archive stale live traces which still resides in redis or database
......
...@@ -6,6 +6,7 @@ module Ci ...@@ -6,6 +6,7 @@ module Ci
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
feature_category :continuous_integration
def perform(build_id) def perform(build_id)
Ci::Build.find_by_id(build_id).try do |build| Ci::Build.find_by_id(build_id).try do |build|
......
...@@ -6,6 +6,7 @@ module Ci ...@@ -6,6 +6,7 @@ module Ci
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
feature_category :continuous_integration
def perform(build_id) def perform(build_id)
::Ci::Build.find_by_id(build_id).try do |build| ::Ci::Build.find_by_id(build_id).try do |build|
......
...@@ -4,6 +4,7 @@ class CleanupContainerRepositoryWorker ...@@ -4,6 +4,7 @@ class CleanupContainerRepositoryWorker
include ApplicationWorker include ApplicationWorker
queue_namespace :container_repository queue_namespace :container_repository
feature_category :container_registry
attr_reader :container_repository, :current_user attr_reader :container_repository, :current_user
......
...@@ -8,6 +8,7 @@ module ApplicationWorker ...@@ -8,6 +8,7 @@ module ApplicationWorker
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
include WorkerAttributes
included do included do
set_queue set_queue
......
...@@ -5,5 +5,6 @@ module AutoDevopsQueue ...@@ -5,5 +5,6 @@ module AutoDevopsQueue
included do included do
queue_namespace :auto_devops queue_namespace :auto_devops
feature_category :auto_devops
end end
end end
...@@ -5,5 +5,6 @@ module ChaosQueue ...@@ -5,5 +5,6 @@ module ChaosQueue
included do included do
queue_namespace :chaos queue_namespace :chaos
feature_category :chaos_engineering
end end
end end
...@@ -8,5 +8,6 @@ module ClusterQueue ...@@ -8,5 +8,6 @@ module ClusterQueue
included do included do
queue_namespace :gcp_cluster queue_namespace :gcp_cluster
feature_category :kubernetes_configuration
end end
end end
...@@ -12,6 +12,8 @@ module Gitlab ...@@ -12,6 +12,8 @@ module Gitlab
include GithubImport::Queue include GithubImport::Queue
include ReschedulingMethods include ReschedulingMethods
include NotifyUponDeath include NotifyUponDeath
feature_category :importers
end end
# project - An instance of `Project` to import the data into. # project - An instance of `Project` to import the data into.
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
included do included do
queue_namespace :github_importer queue_namespace :github_importer
feature_category :importers
# If a job produces an error it may block a stage from advancing # 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 # forever. To prevent this from happening we prevent jobs from going to
......
...@@ -8,5 +8,6 @@ module ObjectPoolQueue ...@@ -8,5 +8,6 @@ module ObjectPoolQueue
included do included do
queue_namespace :object_pool queue_namespace :object_pool
feature_category :gitaly
end end
end end
...@@ -8,5 +8,6 @@ module PipelineBackgroundQueue ...@@ -8,5 +8,6 @@ module PipelineBackgroundQueue
included do included do
queue_namespace :pipeline_background queue_namespace :pipeline_background
feature_category :continuous_integration
end end
end end
...@@ -8,5 +8,6 @@ module PipelineQueue ...@@ -8,5 +8,6 @@ module PipelineQueue
included do included do
queue_namespace :pipeline_default queue_namespace :pipeline_default
feature_category :continuous_integration
end end
end end
...@@ -6,7 +6,7 @@ module RepositoryCheckQueue ...@@ -6,7 +6,7 @@ module RepositoryCheckQueue
included do included do
queue_namespace :repository_check queue_namespace :repository_check
sidekiq_options retry: false sidekiq_options retry: false
feature_category :source_code_management
end end
end end
...@@ -8,5 +8,6 @@ module TodosDestroyerQueue ...@@ -8,5 +8,6 @@ module TodosDestroyerQueue
included do included do
queue_namespace :todos_destroyer queue_namespace :todos_destroyer
feature_category :issue_tracking
end end
end end
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class CreateEvidenceWorker class CreateEvidenceWorker
include ApplicationWorker include ApplicationWorker
feature_category :release_governance
def perform(release_id) def perform(release_id)
release = Release.find_by_id(release_id) release = Release.find_by_id(release_id)
return unless release return unless release
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class CreateGpgSignatureWorker class CreateGpgSignatureWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id) def perform(commit_shas, project_id)
# Older versions of Git::BranchPushService may push a single commit ID on # Older versions of Git::BranchPushService may push a single commit ID on
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class CreateNoteDiffFileWorker class CreateNoteDiffFileWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
def perform(diff_note_id) def perform(diff_note_id)
diff_note = DiffNote.find(diff_note_id) diff_note = DiffNote.find(diff_note_id)
......
...@@ -5,6 +5,7 @@ class CreatePipelineWorker ...@@ -5,6 +5,7 @@ class CreatePipelineWorker
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_creation queue_namespace :pipeline_creation
feature_category :continuous_integration
def perform(project_id, user_id, ref, source, params = {}) def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id) project = Project.find(project_id)
......
...@@ -5,6 +5,7 @@ class DeleteContainerRepositoryWorker ...@@ -5,6 +5,7 @@ class DeleteContainerRepositoryWorker
include ExclusiveLeaseGuard include ExclusiveLeaseGuard
queue_namespace :container_repository queue_namespace :container_repository
feature_category :container_registry
LEASE_TIMEOUT = 1.hour LEASE_TIMEOUT = 1.hour
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class DeleteDiffFilesWorker class DeleteDiffFilesWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(merge_request_diff_id) def perform(merge_request_diff_id)
merge_request_diff = MergeRequestDiff.find(merge_request_diff_id) merge_request_diff = MergeRequestDiff.find(merge_request_diff_id)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class DeleteMergedBranchesWorker class DeleteMergedBranchesWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
def perform(project_id, user_id) def perform(project_id, user_id)
begin begin
project = Project.find(project_id) project = Project.find(project_id)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class DeleteStoredFilesWorker class DeleteStoredFilesWorker
include ApplicationWorker include ApplicationWorker
feature_category_not_owned!
def perform(class_name, keys) def perform(class_name, keys)
klass = begin klass = begin
class_name.constantize class_name.constantize
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class DeleteUserWorker class DeleteUserWorker
include ApplicationWorker include ApplicationWorker
feature_category :authentication_and_authorization
def perform(current_user_id, delete_user_id, options = {}) def perform(current_user_id, delete_user_id, options = {})
delete_user = User.find(delete_user_id) delete_user = User.find(delete_user_id)
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
......
...@@ -5,6 +5,7 @@ module Deployments ...@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker include ApplicationWorker
queue_namespace :deployment queue_namespace :deployment
feature_category :continuous_delivery
def perform(deployment_id) def perform(deployment_id)
Deployment.find_by_id(deployment_id).try(:execute_hooks) Deployment.find_by_id(deployment_id).try(:execute_hooks)
......
...@@ -5,6 +5,7 @@ module Deployments ...@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker include ApplicationWorker
queue_namespace :deployment queue_namespace :deployment
feature_category :continuous_delivery
def perform(deployment_id) def perform(deployment_id)
Deployment.find_by_id(deployment_id).try do |deployment| Deployment.find_by_id(deployment_id).try do |deployment|
......
...@@ -6,6 +6,7 @@ class DetectRepositoryLanguagesWorker ...@@ -6,6 +6,7 @@ class DetectRepositoryLanguagesWorker
include ExclusiveLeaseGuard include ExclusiveLeaseGuard
sidekiq_options retry: 1 sidekiq_options retry: 1
feature_category :source_code_management
LEASE_TIMEOUT = 300 LEASE_TIMEOUT = 300
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class EmailReceiverWorker class EmailReceiverWorker
include ApplicationWorker include ApplicationWorker
feature_category :issue_tracking
def perform(raw) def perform(raw)
return unless Gitlab::IncomingEmail.enabled? return unless Gitlab::IncomingEmail.enabled?
......
...@@ -5,6 +5,8 @@ class EmailsOnPushWorker ...@@ -5,6 +5,8 @@ class EmailsOnPushWorker
attr_reader :email, :skip_premailer attr_reader :email, :skip_premailer
feature_category :source_code_management
def perform(project_id, recipients, push_data, options = {}) def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys! options.symbolize_keys!
options.reverse_merge!( options.reverse_merge!(
......
...@@ -4,6 +4,8 @@ class ExpireBuildArtifactsWorker ...@@ -4,6 +4,8 @@ class ExpireBuildArtifactsWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :continuous_integration
def perform def perform
if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true) if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
perform_efficient_artifacts_removal perform_efficient_artifacts_removal
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class ExpireBuildInstanceArtifactsWorker class ExpireBuildInstanceArtifactsWorker
include ApplicationWorker include ApplicationWorker
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(build_id) def perform(build_id)
build = Ci::Build build = Ci::Build
......
...@@ -4,6 +4,7 @@ class GitGarbageCollectWorker ...@@ -4,6 +4,7 @@ class GitGarbageCollectWorker
include ApplicationWorker include ApplicationWorker
sidekiq_options retry: false sidekiq_options retry: false
feature_category :gitaly
# Timeout set to 24h # Timeout set to 24h
LEASE_TIMEOUT = 86400 LEASE_TIMEOUT = 86400
......
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
include ApplicationWorker include ApplicationWorker
sidekiq_options dead: false sidekiq_options dead: false
feature_category :importers
INTERVAL = 30.seconds.to_i INTERVAL = 30.seconds.to_i
......
...@@ -4,6 +4,8 @@ class GitlabShellWorker ...@@ -4,6 +4,8 @@ class GitlabShellWorker
include ApplicationWorker include ApplicationWorker
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
feature_category :source_code_management
def perform(action, *arg) def perform(action, *arg)
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end end
......
...@@ -6,6 +6,8 @@ class GitlabUsagePingWorker ...@@ -6,6 +6,8 @@ class GitlabUsagePingWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category_not_owned!
# Retry for up to approximately three hours then give up. # Retry for up to approximately three hours then give up.
sidekiq_options retry: 10, dead: false sidekiq_options retry: 10, dead: false
......
...@@ -4,6 +4,8 @@ class GroupDestroyWorker ...@@ -4,6 +4,8 @@ class GroupDestroyWorker
include ApplicationWorker include ApplicationWorker
include ExceptionBacktrace include ExceptionBacktrace
feature_category :groups
def perform(group_id, user_id) def perform(group_id, user_id)
begin begin
group = Group.find(group_id) group = Group.find(group_id)
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
module HashedStorage module HashedStorage
class BaseWorker class BaseWorker
include ExclusiveLeaseGuard include ExclusiveLeaseGuard
include WorkerAttributes
feature_category :source_code_management
LEASE_TIMEOUT = 30.seconds.to_i LEASE_TIMEOUT = 30.seconds.to_i
LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker' LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'
......
...@@ -5,6 +5,7 @@ module HashedStorage ...@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker include ApplicationWorker
queue_namespace :hashed_storage queue_namespace :hashed_storage
feature_category :source_code_management
# @param [Integer] start initial ID of the batch # @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch # @param [Integer] finish last ID of the batch
......
...@@ -5,6 +5,7 @@ module HashedStorage ...@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker include ApplicationWorker
queue_namespace :hashed_storage queue_namespace :hashed_storage
feature_category :source_code_management
# @param [Integer] start initial ID of the batch # @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch # @param [Integer] finish last ID of the batch
......
...@@ -4,6 +4,8 @@ class ImportExportProjectCleanupWorker ...@@ -4,6 +4,8 @@ class ImportExportProjectCleanupWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :importers
def perform def perform
ImportExportCleanUpService.new.execute ImportExportCleanUpService.new.execute
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class ImportIssuesCsvWorker class ImportIssuesCsvWorker
include ApplicationWorker include ApplicationWorker
feature_category :issue_tracking
sidekiq_retries_exhausted do |job| sidekiq_retries_exhausted do |job|
Upload.find(job['args'][2]).destroy Upload.find(job['args'][2]).destroy
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class InvalidGpgSignatureUpdateWorker class InvalidGpgSignatureUpdateWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(gpg_key_id) def perform(gpg_key_id)
gpg_key = GpgKey.find_by(id: gpg_key_id) gpg_key = GpgKey.find_by(id: gpg_key_id)
......
...@@ -6,6 +6,8 @@ require 'socket' ...@@ -6,6 +6,8 @@ require 'socket'
class IrkerWorker class IrkerWorker
include ApplicationWorker include ApplicationWorker
feature_category :integrations
def perform(project_id, chans, colors, push_data, settings) def perform(project_id, chans, colors, push_data, settings)
project = Project.find(project_id) project = Project.find(project_id)
......
...@@ -4,6 +4,8 @@ class IssueDueSchedulerWorker ...@@ -4,6 +4,8 @@ class IssueDueSchedulerWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :issue_tracking
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] } project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
......
...@@ -5,6 +5,8 @@ module MailScheduler ...@@ -5,6 +5,8 @@ module MailScheduler
include ApplicationWorker include ApplicationWorker
include MailSchedulerQueue include MailSchedulerQueue
feature_category :issue_tracking
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(project_id) def perform(project_id)
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue| Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
......
...@@ -7,6 +7,8 @@ module MailScheduler ...@@ -7,6 +7,8 @@ module MailScheduler
include ApplicationWorker include ApplicationWorker
include MailSchedulerQueue include MailSchedulerQueue
feature_category :issue_tracking
def perform(meth, *args) def perform(meth, *args)
check_arguments!(args) check_arguments!(args)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class MergeWorker class MergeWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
def perform(merge_request_id, current_user_id, params) def perform(merge_request_id, current_user_id, params)
params = params.with_indifferent_access params = params.with_indifferent_access
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class MigrateExternalDiffsWorker class MigrateExternalDiffsWorker
include ApplicationWorker include ApplicationWorker
feature_category :source_code_management
def perform(merge_request_diff_id) def perform(merge_request_diff_id)
diff = MergeRequestDiff.find_by_id(merge_request_diff_id) diff = MergeRequestDiff.find_by_id(merge_request_diff_id)
return unless diff return unless diff
......
...@@ -10,6 +10,8 @@ class NamespacelessProjectDestroyWorker ...@@ -10,6 +10,8 @@ class NamespacelessProjectDestroyWorker
include ApplicationWorker include ApplicationWorker
include ExceptionBacktrace include ExceptionBacktrace
feature_category :authentication_and_authorization
def perform(project_id) def perform(project_id)
begin begin
project = Project.unscoped.find(project_id) project = Project.unscoped.find(project_id)
...@@ -31,6 +33,6 @@ class NamespacelessProjectDestroyWorker ...@@ -31,6 +33,6 @@ class NamespacelessProjectDestroyWorker
def unlink_fork(project) def unlink_fork(project)
merge_requests = project.forked_from_project.merge_requests.opened.from_project(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
end end
...@@ -5,6 +5,8 @@ module Namespaces ...@@ -5,6 +5,8 @@ module Namespaces
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :source_code_management
# Worker to prune pending rows on Namespace::AggregationSchedule # Worker to prune pending rows on Namespace::AggregationSchedule
# It's scheduled to run once a day at 1:05am. # It's scheduled to run once a day at 1:05am.
def perform def perform
......
...@@ -5,6 +5,7 @@ module Namespaces ...@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker include ApplicationWorker
queue_namespace :update_namespace_statistics queue_namespace :update_namespace_statistics
feature_category :source_code_management
def perform(namespace_id) def perform(namespace_id)
namespace = Namespace.find(namespace_id) namespace = Namespace.find(namespace_id)
......
...@@ -5,6 +5,7 @@ module Namespaces ...@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker include ApplicationWorker
queue_namespace :update_namespace_statistics queue_namespace :update_namespace_statistics
feature_category :source_code_management
def perform(namespace_id) def perform(namespace_id)
return unless aggregation_schedules_table_exists? return unless aggregation_schedules_table_exists?
......
...@@ -4,6 +4,8 @@ class NewIssueWorker ...@@ -4,6 +4,8 @@ class NewIssueWorker
include ApplicationWorker include ApplicationWorker
include NewIssuable include NewIssuable
feature_category :issue_tracking
def perform(issue_id, user_id) def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id) return unless objects_found?(issue_id, user_id)
......
...@@ -4,6 +4,8 @@ class NewMergeRequestWorker ...@@ -4,6 +4,8 @@ class NewMergeRequestWorker
include ApplicationWorker include ApplicationWorker
include NewIssuable include NewIssuable
feature_category :source_code_management
def perform(merge_request_id, user_id) def perform(merge_request_id, user_id)
return unless objects_found?(merge_request_id, user_id) return unless objects_found?(merge_request_id, user_id)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class NewNoteWorker class NewNoteWorker
include ApplicationWorker include ApplicationWorker
feature_category :issue_tracking
# Keep extra parameter to preserve backwards compatibility with # Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later) # old `NewNoteWorker` jobs (can remove later)
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
......
...@@ -4,6 +4,7 @@ class NewReleaseWorker ...@@ -4,6 +4,7 @@ class NewReleaseWorker
include ApplicationWorker include ApplicationWorker
queue_namespace :notifications queue_namespace :notifications
feature_category :release_orchestration
def perform(release_id) def perform(release_id)
release = Release.with_project_and_namespace.find_by_id(release_id) release = Release.with_project_and_namespace.find_by_id(release_id)
......
...@@ -6,6 +6,7 @@ module ObjectStorage ...@@ -6,6 +6,7 @@ module ObjectStorage
include ObjectStorageQueue include ObjectStorageQueue
sidekiq_options retry: 5 sidekiq_options retry: 5
feature_category_not_owned!
def perform(uploader_class_name, subject_class_name, file_field, subject_id) def perform(uploader_class_name, subject_class_name, file_field, subject_id)
uploader_class = uploader_class_name.constantize uploader_class = uploader_class_name.constantize
......
...@@ -5,6 +5,8 @@ module ObjectStorage ...@@ -5,6 +5,8 @@ module ObjectStorage
include ApplicationWorker include ApplicationWorker
include ObjectStorageQueue include ObjectStorageQueue
feature_category_not_owned!
SanityCheckError = Class.new(StandardError) SanityCheckError = Class.new(StandardError)
class MigrationResult class MigrationResult
......
...@@ -4,6 +4,8 @@ class PagesDomainRemovalCronWorker ...@@ -4,6 +4,8 @@ class PagesDomainRemovalCronWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :pages
def perform def perform
PagesDomain.for_removal.find_each do |domain| PagesDomain.for_removal.find_each do |domain|
domain.destroy! domain.destroy!
......
...@@ -4,6 +4,8 @@ class PagesDomainSslRenewalCronWorker ...@@ -4,6 +4,8 @@ class PagesDomainSslRenewalCronWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :pages
def perform def perform
return unless ::Gitlab::LetsEncrypt.enabled? return unless ::Gitlab::LetsEncrypt.enabled?
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class PagesDomainSslRenewalWorker class PagesDomainSslRenewalWorker
include ApplicationWorker include ApplicationWorker
feature_category :pages
def perform(domain_id) def perform(domain_id)
domain = PagesDomain.find_by_id(domain_id) domain = PagesDomain.find_by_id(domain_id)
return unless domain&.enabled? return unless domain&.enabled?
......
...@@ -4,6 +4,8 @@ class PagesDomainVerificationCronWorker ...@@ -4,6 +4,8 @@ class PagesDomainVerificationCronWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue include CronjobQueue
feature_category :pages
def perform def perform
return if Gitlab::Database.read_only? return if Gitlab::Database.read_only?
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class PagesDomainVerificationWorker class PagesDomainVerificationWorker
include ApplicationWorker include ApplicationWorker
feature_category :pages
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id) def perform(domain_id)
return if Gitlab::Database.read_only? return if Gitlab::Database.read_only?
......
...@@ -4,6 +4,7 @@ class PagesWorker ...@@ -4,6 +4,7 @@ class PagesWorker
include ApplicationWorker include ApplicationWorker
sidekiq_options retry: 3 sidekiq_options retry: 3
feature_category :pages
def perform(action, *arg) def perform(action, *arg)
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
......
...@@ -5,6 +5,7 @@ class PipelineProcessWorker ...@@ -5,6 +5,7 @@ class PipelineProcessWorker
include PipelineQueue include PipelineQueue
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, build_ids = nil) 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