Skip to content
Snippets Groups Projects
Commit 25989ab7 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 9bbb32b2
No related branches found
No related tags found
No related merge requests found
Showing
with 503 additions and 90 deletions
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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.
Loading
Loading
Loading
Loading
@@ -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;
 
Loading
Loading
@@ -46,6 +42,7 @@ export default {
GlDropdownItem,
GlFormGroup,
GlModal,
DateTimePicker,
},
directives: {
GlModal: GlModalDirective,
Loading
Loading
@@ -171,10 +168,8 @@ export default {
return {
state: 'gettingStarted',
elWidth: 0,
selectedTimeWindow: '',
selectedTimeWindowKey: '',
formIsValid: null,
timeWindows: {},
selectedTimeWindow: {},
isRearrangingPanels: false,
};
},
Loading
Loading
@@ -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'), {
Loading
Loading
@@ -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);
Loading
Loading
@@ -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,
},
Loading
Loading
@@ -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"
Loading
Loading
@@ -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"
Loading
Loading
@@ -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
Loading
Loading
<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>
Loading
Loading
@@ -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',
Loading
Loading
@@ -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 = {
Loading
Loading
import { secondsIn, timeWindowsKeyNames } from './constants';
import dateformat from 'dateformat';
import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
 
const secondsToMilliseconds = seconds => seconds * 1000;
 
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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;
}
Loading
Loading
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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`
Loading
Loading
@@ -444,7 +445,6 @@ class IssuableFinder
items
end
 
# rubocop: disable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
when 'closed'
Loading
Loading
@@ -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`
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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.
Loading
Loading
@@ -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.
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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)
Loading
Loading
@@ -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
 
Loading
Loading
# 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')
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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))
 
Loading
Loading
@@ -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)
Loading
Loading
Loading
Loading
@@ -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]
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment