Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9bbb32b297
commit
25989ab7ef
|
|
@ -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;
|
||||
|
||||
this.fetchData(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"
|
||||
class="col-sm-6 col-md-6 col-lg-4"
|
||||
>
|
||||
<gl-dropdown
|
||||
id="monitor-time-window-dropdown"
|
||||
class="mb-0 d-flex js-time-window-dropdown"
|
||||
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>
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<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 = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# 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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
- 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)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue