diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-18 11:11:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-18 11:11:44 +0000 |
commit | 25989ab7ef1a444ed2abd5479f176d58e1d9462a (patch) | |
tree | 271bb24f3c7178f320cb9de0be0833a285327d09 | |
parent | 9bbb32b29703f3ce33dd35d5101145774b793a6d (diff) | |
download | gitlab-ce-25989ab7ef1a444ed2abd5479f176d58e1d9462a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
287 files changed, 2376 insertions, 287 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 6be92710ad9..a02740373da 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -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 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 81bb28dacab..3ed7af71b4f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -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. diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 2e35ef8d4b0..b4ea415bb51 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -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 diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue new file mode 100644 index 00000000000..4616a767295 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue @@ -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> diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue new file mode 100644 index 00000000000..0388a6190d9 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue @@ -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> diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 13aba3d9f44..2836fe4fc26 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -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 = { diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 9049695b992..4c72f5226b7 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -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 diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss index ceafff94719..154e505f7a4 100644 --- a/app/assets/stylesheets/pages/prometheus.scss +++ b/app/assets/stylesheets/pages/prometheus.scss @@ -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; } diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2364777cdc5..477093ddadf 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -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` diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5e52062ef40..3bf19399cec 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -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 diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 94a0de556f4..852576dbbc2 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -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 diff --git a/app/models/concerns/issuable_states.rb b/app/models/concerns/issuable_states.rb index 33bc41d7f44..f0b9f0d1f3a 100644 --- a/app/models/concerns/issuable_states.rb +++ b/app/models/concerns/issuable_states.rb @@ -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 diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index 3deb86da6cf..42b370990ac 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -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 diff --git a/app/models/concerns/versioned_description.rb b/app/models/concerns/versioned_description.rb new file mode 100644 index 00000000000..63a24aadc8a --- /dev/null +++ b/app/models/concerns/versioned_description.rb @@ -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 diff --git a/app/models/concerns/worker_attributes.rb b/app/models/concerns/worker_attributes.rb new file mode 100644 index 00000000000..af40e9e3b19 --- /dev/null +++ b/app/models/concerns/worker_attributes.rb @@ -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 diff --git a/app/models/description_version.rb b/app/models/description_version.rb new file mode 100644 index 00000000000..abab7f94212 --- /dev/null +++ b/app/models/description_version.rb @@ -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') diff --git a/app/models/issue.rb b/app/models/issue.rb index d0b2165fcc7..b9b481ac29b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 50efe5d6aa0..7cdaa3e3ca7 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -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 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index fe8ba9765b7..735ad046f22 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -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) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 1d0b37abf72..019bd54f48c 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -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] diff --git a/app/models/push_event.rb b/app/models/push_event.rb index 6f7365a2763..5cab686f20b 100644 --- a/app/models/push_event.rb +++ b/app/models/push_event.rb @@ -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 diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index 8ec90ca25d3..11cbeb60bba 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -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 diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb index 40ac52d96af..a1e0bf02d11 100644 --- a/app/serializers/projects/serverless/service_entity.rb +++ b/app/serializers/projects/serverless/service_entity.rb @@ -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| diff --git a/app/services/issuable/clone/content_rewriter.rb b/app/services/issuable/clone/content_rewriter.rb index f75b51c4be3..67d2f9fd3fe 100644 --- a/app/services/issuable/clone/content_rewriter.rb +++ b/app/services/issuable/clone/content_rewriter.rb @@ -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) diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb index 60a68568833..6fe14939aaa 100644 --- a/app/services/note_summary.rb +++ b/app/services/note_summary.rb @@ -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 diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index f99ad987156..36bde629f9c 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -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 diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 49e482ff1df..2633a3899f7 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -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 diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index 9173b802dd4..325e01bb5c8 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -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' - diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb index f69e74b2674..be05d2a6752 100644 --- a/app/workers/admin_email_worker.rb +++ b/app/workers/admin_email_worker.rb @@ -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 diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index c9ddeb08613..577c439f4a2 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -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. diff --git a/app/workers/auto_merge_process_worker.rb b/app/workers/auto_merge_process_worker.rb index cd81cdbc60c..e4dccb891ce 100644 --- a/app/workers/auto_merge_process_worker.rb +++ b/app/workers/auto_merge_process_worker.rb @@ -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| diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb index b83412b5e6e..20e2cdd7f96 100644 --- a/app/workers/background_migration_worker.rb +++ b/app/workers/background_migration_worker.rb @@ -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. # diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb index b0c3676714c..15b31acf3e5 100644 --- a/app/workers/build_hooks_worker.rb +++ b/app/workers/build_hooks_worker.rb @@ -5,6 +5,7 @@ class BuildHooksWorker include PipelineQueue queue_namespace :pipeline_hooks + feature_category :continuous_integration # rubocop: disable CodeReuse/ActiveRecord def perform(build_id) diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb index 67d5b0f5f5b..6584fba4c65 100644 --- a/app/workers/build_queue_worker.rb +++ b/app/workers/build_queue_worker.rb @@ -5,6 +5,7 @@ class BuildQueueWorker include PipelineQueue queue_namespace :pipeline_processing + feature_category :continuous_integration # rubocop: disable CodeReuse/ActiveRecord def perform(build_id) diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb index 25a306e94d8..3bc2edad62c 100644 --- a/app/workers/chat_notification_worker.rb +++ b/app/workers/chat_notification_worker.rb @@ -3,6 +3,8 @@ class ChatNotificationWorker include ApplicationWorker + feature_category :chatops + RESCHEDULE_INTERVAL = 2.seconds # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb index ad7a29719ac..74f389175b9 100644 --- a/app/workers/ci/archive_traces_cron_worker.rb +++ b/app/workers/ci/archive_traces_cron_worker.rb @@ -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 diff --git a/app/workers/ci/build_prepare_worker.rb b/app/workers/ci/build_prepare_worker.rb index 1a35a74ae53..20208c18d03 100644 --- a/app/workers/ci/build_prepare_worker.rb +++ b/app/workers/ci/build_prepare_worker.rb @@ -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| diff --git a/app/workers/ci/build_schedule_worker.rb b/app/workers/ci/build_schedule_worker.rb index da219adffc6..f22ec4c7810 100644 --- a/app/workers/ci/build_schedule_worker.rb +++ b/app/workers/ci/build_schedule_worker.rb @@ -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| diff --git a/app/workers/cleanup_container_repository_worker.rb b/app/workers/cleanup_container_repository_worker.rb index 0331fc7b01c..83fb3e58d29 100644 --- a/app/workers/cleanup_container_repository_worker.rb +++ b/app/workers/cleanup_container_repository_worker.rb @@ -4,6 +4,7 @@ class CleanupContainerRepositoryWorker include ApplicationWorker queue_namespace :container_repository + feature_category :container_registry attr_reader :container_repository, :current_user diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb index 2b36ccb8304..62748808ff1 100644 --- a/app/workers/concerns/application_worker.rb +++ b/app/workers/concerns/application_worker.rb @@ -8,6 +8,7 @@ module ApplicationWorker extend ActiveSupport::Concern include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker + include WorkerAttributes included do set_queue diff --git a/app/workers/concerns/auto_devops_queue.rb b/app/workers/concerns/auto_devops_queue.rb index aba928ccaab..61e3c1544bd 100644 --- a/app/workers/concerns/auto_devops_queue.rb +++ b/app/workers/concerns/auto_devops_queue.rb @@ -5,5 +5,6 @@ module AutoDevopsQueue included do queue_namespace :auto_devops + feature_category :auto_devops end end diff --git a/app/workers/concerns/chaos_queue.rb b/app/workers/concerns/chaos_queue.rb index e406509d12d..c5db10491f2 100644 --- a/app/workers/concerns/chaos_queue.rb +++ b/app/workers/concerns/chaos_queue.rb @@ -5,5 +5,6 @@ module ChaosQueue included do queue_namespace :chaos + feature_category :chaos_engineering end end diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb index e44b40c36c9..180b86b0124 100644 --- a/app/workers/concerns/cluster_queue.rb +++ b/app/workers/concerns/cluster_queue.rb @@ -8,5 +8,6 @@ module ClusterQueue included do queue_namespace :gcp_cluster + feature_category :kubernetes_configuration end end diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb index eeeff6e93a0..b856a9329dd 100644 --- a/app/workers/concerns/gitlab/github_import/object_importer.rb +++ b/app/workers/concerns/gitlab/github_import/object_importer.rb @@ -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. diff --git a/app/workers/concerns/gitlab/github_import/queue.rb b/app/workers/concerns/gitlab/github_import/queue.rb index 59b621f16ab..7cc23dd7c0b 100644 --- a/app/workers/concerns/gitlab/github_import/queue.rb +++ b/app/workers/concerns/gitlab/github_import/queue.rb @@ -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 diff --git a/app/workers/concerns/object_pool_queue.rb b/app/workers/concerns/object_pool_queue.rb index 5b648df9c72..c2e84470fba 100644 --- a/app/workers/concerns/object_pool_queue.rb +++ b/app/workers/concerns/object_pool_queue.rb @@ -8,5 +8,6 @@ module ObjectPoolQueue included do queue_namespace :object_pool + feature_category :gitaly end end diff --git a/app/workers/concerns/pipeline_background_queue.rb b/app/workers/concerns/pipeline_background_queue.rb index bbb8ad0c982..0a23780b807 100644 --- a/app/workers/concerns/pipeline_background_queue.rb +++ b/app/workers/concerns/pipeline_background_queue.rb @@ -8,5 +8,6 @@ module PipelineBackgroundQueue included do queue_namespace :pipeline_background + feature_category :continuous_integration end end diff --git a/app/workers/concerns/pipeline_queue.rb b/app/workers/concerns/pipeline_queue.rb index 3aaed4669e5..27cbf6eb61c 100644 --- a/app/workers/concerns/pipeline_queue.rb +++ b/app/workers/concerns/pipeline_queue.rb @@ -8,5 +8,6 @@ module PipelineQueue included do queue_namespace :pipeline_default + feature_category :continuous_integration end end diff --git a/app/workers/concerns/repository_check_queue.rb b/app/workers/concerns/repository_check_queue.rb index 216d67e5dbc..76f6e1c2e91 100644 --- a/app/workers/concerns/repository_check_queue.rb +++ b/app/workers/concerns/repository_check_queue.rb @@ -6,7 +6,7 @@ module RepositoryCheckQueue included do queue_namespace :repository_check - sidekiq_options retry: false + feature_category :source_code_management end end diff --git a/app/workers/concerns/todos_destroyer_queue.rb b/app/workers/concerns/todos_destroyer_queue.rb index 8e2b1d30579..1bbccbfb1f9 100644 --- a/app/workers/concerns/todos_destroyer_queue.rb +++ b/app/workers/concerns/todos_destroyer_queue.rb @@ -8,5 +8,6 @@ module TodosDestroyerQueue included do queue_namespace :todos_destroyer + feature_category :issue_tracking end end diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb index 5fc901ae514..027dbd2f101 100644 --- a/app/workers/create_evidence_worker.rb +++ b/app/workers/create_evidence_worker.rb @@ -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 diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb index e3fb5d479ae..fc36a2adccd 100644 --- a/app/workers/create_gpg_signature_worker.rb +++ b/app/workers/create_gpg_signature_worker.rb @@ -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 diff --git a/app/workers/create_note_diff_file_worker.rb b/app/workers/create_note_diff_file_worker.rb index 0850250f7e3..ca200bd17b4 100644 --- a/app/workers/create_note_diff_file_worker.rb +++ b/app/workers/create_note_diff_file_worker.rb @@ -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) diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb index 037b4a57d4b..70412ffd095 100644 --- a/app/workers/create_pipeline_worker.rb +++ b/app/workers/create_pipeline_worker.rb @@ -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) diff --git a/app/workers/delete_container_repository_worker.rb b/app/workers/delete_container_repository_worker.rb index 42e66513ff1..e70b4fb0a58 100644 --- a/app/workers/delete_container_repository_worker.rb +++ b/app/workers/delete_container_repository_worker.rb @@ -5,6 +5,7 @@ class DeleteContainerRepositoryWorker include ExclusiveLeaseGuard queue_namespace :container_repository + feature_category :container_registry LEASE_TIMEOUT = 1.hour diff --git a/app/workers/delete_diff_files_worker.rb b/app/workers/delete_diff_files_worker.rb index f518dfe871c..e0c1724f1f7 100644 --- a/app/workers/delete_diff_files_worker.rb +++ b/app/workers/delete_diff_files_worker.rb @@ -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) diff --git a/app/workers/delete_merged_branches_worker.rb b/app/workers/delete_merged_branches_worker.rb index 017d7fd1cb0..44b3db30d0d 100644 --- a/app/workers/delete_merged_branches_worker.rb +++ b/app/workers/delete_merged_branches_worker.rb @@ -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) diff --git a/app/workers/delete_stored_files_worker.rb b/app/workers/delete_stored_files_worker.rb index ff7931849d8..8a693a64055 100644 --- a/app/workers/delete_stored_files_worker.rb +++ b/app/workers/delete_stored_files_worker.rb @@ -3,6 +3,8 @@ class DeleteStoredFilesWorker include ApplicationWorker + feature_category_not_owned! + def perform(class_name, keys) klass = begin class_name.constantize diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index efa8794b214..0e49e787d8a 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -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) diff --git a/app/workers/deployments/finished_worker.rb b/app/workers/deployments/finished_worker.rb index c9d448d5d18..79a1caccc92 100644 --- a/app/workers/deployments/finished_worker.rb +++ b/app/workers/deployments/finished_worker.rb @@ -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) diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb index 3c7e384365a..f6520307186 100644 --- a/app/workers/deployments/success_worker.rb +++ b/app/workers/deployments/success_worker.rb @@ -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| diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb index 838c3be78f0..954d0f9336b 100644 --- a/app/workers/detect_repository_languages_worker.rb +++ b/app/workers/detect_repository_languages_worker.rb @@ -6,6 +6,7 @@ class DetectRepositoryLanguagesWorker include ExclusiveLeaseGuard sidekiq_options retry: 1 + feature_category :source_code_management LEASE_TIMEOUT = 300 diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index e70bf17d5a9..c82728be329 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -3,6 +3,8 @@ class EmailReceiverWorker include ApplicationWorker + feature_category :issue_tracking + def perform(raw) return unless Gitlab::IncomingEmail.enabled? diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index ed3e354e4c2..2231c91a720 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -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!( diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb index 6f0e0fd33f7..9545227fa31 100644 --- a/app/workers/expire_build_artifacts_worker.rb +++ b/app/workers/expire_build_artifacts_worker.rb @@ -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 diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb index 71e61dcb878..db5240d5c8e 100644 --- a/app/workers/expire_build_instance_artifacts_worker.rb +++ b/app/workers/expire_build_instance_artifacts_worker.rb @@ -3,6 +3,8 @@ class ExpireBuildInstanceArtifactsWorker include ApplicationWorker + feature_category :continuous_integration + # rubocop: disable CodeReuse/ActiveRecord def perform(build_id) build = Ci::Build diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index 5499e12e49b..ad119917774 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -4,6 +4,7 @@ class GitGarbageCollectWorker include ApplicationWorker sidekiq_options retry: false + feature_category :gitaly # Timeout set to 24h LEASE_TIMEOUT = 86400 diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb index 0b3437a8a33..44e69e48694 100644 --- a/app/workers/gitlab/github_import/advance_stage_worker.rb +++ b/app/workers/gitlab/github_import/advance_stage_worker.rb @@ -10,6 +10,7 @@ module Gitlab include ApplicationWorker sidekiq_options dead: false + feature_category :importers INTERVAL = 30.seconds.to_i diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb index 0e4d40acc5c..9766331cf4b 100644 --- a/app/workers/gitlab_shell_worker.rb +++ b/app/workers/gitlab_shell_worker.rb @@ -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 diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb index a5e22f88a3b..ad8302a844a 100644 --- a/app/workers/gitlab_usage_ping_worker.rb +++ b/app/workers/gitlab_usage_ping_worker.rb @@ -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 diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb index b4a3ddcae51..553fd359baf 100644 --- a/app/workers/group_destroy_worker.rb +++ b/app/workers/group_destroy_worker.rb @@ -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) diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb index 237e278c537..1ab2108f6bb 100644 --- a/app/workers/hashed_storage/base_worker.rb +++ b/app/workers/hashed_storage/base_worker.rb @@ -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' diff --git a/app/workers/hashed_storage/migrator_worker.rb b/app/workers/hashed_storage/migrator_worker.rb index 49e347d4060..72a3faec5f4 100644 --- a/app/workers/hashed_storage/migrator_worker.rb +++ b/app/workers/hashed_storage/migrator_worker.rb @@ -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 diff --git a/app/workers/hashed_storage/rollbacker_worker.rb b/app/workers/hashed_storage/rollbacker_worker.rb index a4da8443787..8babdcfb96d 100644 --- a/app/workers/hashed_storage/rollbacker_worker.rb +++ b/app/workers/hashed_storage/rollbacker_worker.rb @@ -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 diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb index da3debdeede..07c29d40b54 100644 --- a/app/workers/import_export_project_cleanup_worker.rb +++ b/app/workers/import_export_project_cleanup_worker.rb @@ -4,6 +4,8 @@ class ImportExportProjectCleanupWorker include ApplicationWorker include CronjobQueue + feature_category :importers + def perform ImportExportCleanUpService.new.execute end diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb index b9d7099af71..d9834320318 100644 --- a/app/workers/import_issues_csv_worker.rb +++ b/app/workers/import_issues_csv_worker.rb @@ -3,6 +3,8 @@ class ImportIssuesCsvWorker include ApplicationWorker + feature_category :issue_tracking + sidekiq_retries_exhausted do |job| Upload.find(job['args'][2]).destroy end diff --git a/app/workers/invalid_gpg_signature_update_worker.rb b/app/workers/invalid_gpg_signature_update_worker.rb index fc8a731b427..573efdf9fb1 100644 --- a/app/workers/invalid_gpg_signature_update_worker.rb +++ b/app/workers/invalid_gpg_signature_update_worker.rb @@ -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) diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 29631c6b7ac..a133ed6ed1b 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -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) diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb index 476cba47ad7..d4d47659ef0 100644 --- a/app/workers/issue_due_scheduler_worker.rb +++ b/app/workers/issue_due_scheduler_worker.rb @@ -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] } diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb index 1e1dde1e829..6df816de71f 100644 --- a/app/workers/mail_scheduler/issue_due_worker.rb +++ b/app/workers/mail_scheduler/issue_due_worker.rb @@ -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| diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb index 421fbf04e28..0d06dab3b2e 100644 --- a/app/workers/mail_scheduler/notification_service_worker.rb +++ b/app/workers/mail_scheduler/notification_service_worker.rb @@ -7,6 +7,8 @@ module MailScheduler include ApplicationWorker include MailSchedulerQueue + feature_category :issue_tracking + def perform(meth, *args) check_arguments!(args) diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index ee864b733cd..70b909afea8 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -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) diff --git a/app/workers/migrate_external_diffs_worker.rb b/app/workers/migrate_external_diffs_worker.rb index fe757968d49..d248e2b5500 100644 --- a/app/workers/migrate_external_diffs_worker.rb +++ b/app/workers/migrate_external_diffs_worker.rb @@ -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 diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb index f6e98746055..113afc268f2 100644 --- a/app/workers/namespaceless_project_destroy_worker.rb +++ b/app/workers/namespaceless_project_destroy_worker.rb @@ -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 diff --git a/app/workers/namespaces/prune_aggregation_schedules_worker.rb b/app/workers/namespaces/prune_aggregation_schedules_worker.rb index 4e40feee702..16259ffbfa6 100644 --- a/app/workers/namespaces/prune_aggregation_schedules_worker.rb +++ b/app/workers/namespaces/prune_aggregation_schedules_worker.rb @@ -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 diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb index 0c1ca5eb975..fd772c8cff6 100644 --- a/app/workers/namespaces/root_statistics_worker.rb +++ b/app/workers/namespaces/root_statistics_worker.rb @@ -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) diff --git a/app/workers/namespaces/schedule_aggregation_worker.rb b/app/workers/namespaces/schedule_aggregation_worker.rb index b7d580220d6..87e135fbf21 100644 --- a/app/workers/namespaces/schedule_aggregation_worker.rb +++ b/app/workers/namespaces/schedule_aggregation_worker.rb @@ -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? diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb index 85b53973f56..1b0fec597e7 100644 --- a/app/workers/new_issue_worker.rb +++ b/app/workers/new_issue_worker.rb @@ -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) diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb index fa48c1b29a8..0a5b2f86331 100644 --- a/app/workers/new_merge_request_worker.rb +++ b/app/workers/new_merge_request_worker.rb @@ -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) diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb index 7648af3a8b9..d0d2a563738 100644 --- a/app/workers/new_note_worker.rb +++ b/app/workers/new_note_worker.rb @@ -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 diff --git a/app/workers/new_release_worker.rb b/app/workers/new_release_worker.rb index b80553a60db..28d2517238e 100644 --- a/app/workers/new_release_worker.rb +++ b/app/workers/new_release_worker.rb @@ -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) diff --git a/app/workers/object_storage/background_move_worker.rb b/app/workers/object_storage/background_move_worker.rb index 19ccae7739c..55f8e1c3ede 100644 --- a/app/workers/object_storage/background_move_worker.rb +++ b/app/workers/object_storage/background_move_worker.rb @@ -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 diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb index c9fd19cf9d7..01e6fdb2d3e 100644 --- a/app/workers/object_storage/migrate_uploads_worker.rb +++ b/app/workers/object_storage/migrate_uploads_worker.rb @@ -5,6 +5,8 @@ module ObjectStorage include ApplicationWorker include ObjectStorageQueue + feature_category_not_owned! + SanityCheckError = Class.new(StandardError) class MigrationResult diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb index 79f38e1b89f..25e747c78d0 100644 --- a/app/workers/pages_domain_removal_cron_worker.rb +++ b/app/workers/pages_domain_removal_cron_worker.rb @@ -4,6 +4,8 @@ class PagesDomainRemovalCronWorker include ApplicationWorker include CronjobQueue + feature_category :pages + def perform PagesDomain.for_removal.find_each do |domain| domain.destroy! diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb index e5dde07a648..f7a243e9b3b 100644 --- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb +++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb @@ -4,6 +4,8 @@ class PagesDomainSslRenewalCronWorker include ApplicationWorker include CronjobQueue + feature_category :pages + def perform return unless ::Gitlab::LetsEncrypt.enabled? diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb index 87fd8059946..4db7d22ef7e 100644 --- a/app/workers/pages_domain_ssl_renewal_worker.rb +++ b/app/workers/pages_domain_ssl_renewal_worker.rb @@ -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? diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb index 60703c83e9e..bb3a7fede9a 100644 --- a/app/workers/pages_domain_verification_cron_worker.rb +++ b/app/workers/pages_domain_verification_cron_worker.rb @@ -4,6 +4,8 @@ class PagesDomainVerificationCronWorker include ApplicationWorker include CronjobQueue + feature_category :pages + def perform return if Gitlab::Database.read_only? diff --git a/app/workers/pages_domain_verification_worker.rb b/app/workers/pages_domain_verification_worker.rb index 7817b2ee5fc..b0888036498 100644 --- a/app/workers/pages_domain_verification_worker.rb +++ b/app/workers/pages_domain_verification_worker.rb @@ -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? diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index fa0dfa2ff4b..484d9053849 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -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 diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb index 96524d93f8d..96f3725dbbe 100644 --- a/app/workers/pipeline_process_worker.rb +++ b/app/workers/pipeline_process_worker.rb @@ -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) diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb index 9410fd1a786..f500ea08353 100644 --- a/app/workers/pipeline_schedule_worker.rb +++ b/app/workers/pipeline_schedule_worker.rb @@ -4,6 +4,8 @@ class PipelineScheduleWorker include ApplicationWorker include CronjobQueue + feature_category :continuous_integration + def perform Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules| schedules.each do |schedule| diff --git a/app/workers/plugin_worker.rb b/app/workers/plugin_worker.rb index c293e28be4a..e708031abdf 100644 --- a/app/workers/plugin_worker.rb +++ b/app/workers/plugin_worker.rb @@ -4,6 +4,7 @@ class PluginWorker include ApplicationWorker sidekiq_options retry: false + feature_category :integrations def perform(file_name, data) success, message = Gitlab::Plugin.execute(file_name, data) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 4f193e95faa..a3bc7e5b9c9 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -3,6 +3,8 @@ class PostReceive include ApplicationWorker + feature_category :source_code_management + def perform(gl_repository, identifier, changes, push_options = {}) project, repo_type = Gitlab::GlRepository.parse(gl_repository) diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index f6ebe4ab006..1e4561fc6ea 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -10,6 +10,8 @@ class ProcessCommitWorker include ApplicationWorker + feature_category :source_code_management + # project_id - The ID of the project this commit belongs to. # user_id - The ID of the user that pushed the commit. # commit_hash - Hash containing commit details to use for constructing a diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index e3f1f61991c..57a01c0dd8e 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -5,6 +5,8 @@ class ProjectCacheWorker include ApplicationWorker LEASE_TIMEOUT = 15.minutes.to_i + feature_category :source_code_management + # project_id - The ID of the project for which to flush the cache. # files - An Array containing extra types of files to refresh such as # `:readme` to flush the README and `:changelog` to flush the diff --git a/app/workers/project_daily_statistics_worker.rb b/app/workers/project_daily_statistics_worker.rb index 101f5c28459..19c2fd67763 100644 --- a/app/workers/project_daily_statistics_worker.rb +++ b/app/workers/project_daily_statistics_worker.rb @@ -3,6 +3,8 @@ class ProjectDailyStatisticsWorker include ApplicationWorker + feature_category :source_code_management + def perform(project_id) project = Project.find_by_id(project_id) diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb index 4447e867240..1d20837faa2 100644 --- a/app/workers/project_destroy_worker.rb +++ b/app/workers/project_destroy_worker.rb @@ -4,6 +4,8 @@ class ProjectDestroyWorker include ApplicationWorker include ExceptionBacktrace + feature_category :source_code_management + def perform(project_id, user_id, params) project = Project.find(project_id) user = User.find(user_id) diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb index ed9da39c7c3..bbcf3b72718 100644 --- a/app/workers/project_export_worker.rb +++ b/app/workers/project_export_worker.rb @@ -5,6 +5,7 @@ class ProjectExportWorker include ExceptionBacktrace sidekiq_options retry: 3 + feature_category :source_code_management def perform(current_user_id, project_id, after_export_strategy = {}, params = {}) current_user = User.find(current_user_id) diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb index 25567cec08b..8041404fc71 100644 --- a/app/workers/project_service_worker.rb +++ b/app/workers/project_service_worker.rb @@ -4,6 +4,7 @@ class ProjectServiceWorker include ApplicationWorker sidekiq_options dead: false + feature_category :integrations def perform(hook_id, data) data = data.with_indifferent_access diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb index 3ccd7615697..73a2b453207 100644 --- a/app/workers/propagate_service_template_worker.rb +++ b/app/workers/propagate_service_template_worker.rb @@ -4,6 +4,8 @@ class PropagateServiceTemplateWorker include ApplicationWorker + feature_category :source_code_management + LEASE_TIMEOUT = 4.hours.to_i # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index e2d1fb3ed35..f421e8dbf59 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -4,6 +4,8 @@ class PruneOldEventsWorker include ApplicationWorker include CronjobQueue + feature_category_not_owned! + # rubocop: disable CodeReuse/ActiveRecord def perform # Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity. diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb index 38054069f4e..8e48b45fc34 100644 --- a/app/workers/prune_web_hook_logs_worker.rb +++ b/app/workers/prune_web_hook_logs_worker.rb @@ -6,6 +6,8 @@ class PruneWebHookLogsWorker include ApplicationWorker include CronjobQueue + feature_category :integrations + # The maximum number of rows to remove in a single job. DELETE_LIMIT = 50_000 diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb index b30864db802..af4a3def062 100644 --- a/app/workers/reactive_caching_worker.rb +++ b/app/workers/reactive_caching_worker.rb @@ -3,6 +3,8 @@ class ReactiveCachingWorker include ApplicationWorker + feature_category_not_owned! + def perform(class_name, id, *args) klass = begin class_name.constantize diff --git a/app/workers/rebase_worker.rb b/app/workers/rebase_worker.rb index 8d06adcd993..7343226fdcd 100644 --- a/app/workers/rebase_worker.rb +++ b/app/workers/rebase_worker.rb @@ -5,6 +5,8 @@ class RebaseWorker include ApplicationWorker + feature_category :source_code_management + def perform(merge_request_id, current_user_id) current_user = User.find(current_user_id) merge_request = MergeRequest.find(merge_request_id) diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb index 368abfeda99..8bc19230caf 100644 --- a/app/workers/remote_mirror_notification_worker.rb +++ b/app/workers/remote_mirror_notification_worker.rb @@ -3,6 +3,8 @@ class RemoteMirrorNotificationWorker include ApplicationWorker + feature_category :source_code_management + def perform(remote_mirror_id) remote_mirror = RemoteMirror.find_by_id(remote_mirror_id) diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb index 25128caf72f..147b412b772 100644 --- a/app/workers/remove_expired_group_links_worker.rb +++ b/app/workers/remove_expired_group_links_worker.rb @@ -4,6 +4,8 @@ class RemoveExpiredGroupLinksWorker include ApplicationWorker include CronjobQueue + feature_category :authentication_and_authorization + def perform ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll end diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb index 3497a1f9280..75f06fd9f6b 100644 --- a/app/workers/remove_expired_members_worker.rb +++ b/app/workers/remove_expired_members_worker.rb @@ -4,6 +4,8 @@ class RemoveExpiredMembersWorker include ApplicationWorker include CronjobQueue + feature_category :authentication_and_authorization + def perform Member.expired.find_each do |member| Members::DestroyService.new.execute(member, skip_authorization: true) diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb index 95e7a9f537f..7f2c23f4685 100644 --- a/app/workers/remove_unreferenced_lfs_objects_worker.rb +++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb @@ -4,6 +4,8 @@ class RemoveUnreferencedLfsObjectsWorker include ApplicationWorker include CronjobQueue + feature_category :source_code_management + def perform LfsObject.destroy_unreferenced end diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb index c1dff8ced90..ebc83c1b17a 100644 --- a/app/workers/repository_archive_cache_worker.rb +++ b/app/workers/repository_archive_cache_worker.rb @@ -4,6 +4,8 @@ class RepositoryArchiveCacheWorker include ApplicationWorker include CronjobQueue + feature_category :source_code_management + def perform RepositoryArchiveCleanUpService.new.execute end diff --git a/app/workers/repository_check/dispatch_worker.rb b/app/workers/repository_check/dispatch_worker.rb index 0a7d9a14c6a..d2bd5f9b967 100644 --- a/app/workers/repository_check/dispatch_worker.rb +++ b/app/workers/repository_check/dispatch_worker.rb @@ -7,6 +7,8 @@ module RepositoryCheck include ::EachShardWorker include ExclusiveLeaseGuard + feature_category :source_code_management + LEASE_TIMEOUT = 1.hour def perform diff --git a/app/workers/repository_cleanup_worker.rb b/app/workers/repository_cleanup_worker.rb index aa26c173a72..dd2cbd42d1f 100644 --- a/app/workers/repository_cleanup_worker.rb +++ b/app/workers/repository_cleanup_worker.rb @@ -4,6 +4,7 @@ class RepositoryCleanupWorker include ApplicationWorker sidekiq_options retry: 3 + feature_category :source_code_management sidekiq_retries_exhausted do |msg, err| next if err.is_a?(ActiveRecord::RecordNotFound) diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index 35e9c58eb13..0adf745c7ac 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -6,6 +6,8 @@ class RepositoryForkWorker include ProjectStartImport include ProjectImportOptions + feature_category :source_code_management + def perform(*args) target_project_id = args.shift target_project = Project.find(target_project_id) diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 85771fa8b31..bc2d0366fdd 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -6,6 +6,8 @@ class RepositoryImportWorker include ProjectStartImport include ProjectImportOptions + feature_category :importers + # technical debt: https://gitlab.com/gitlab-org/gitlab/issues/33991 sidekiq_options memory_killer_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MEMORY_GROWTH_KB', 50).to_i sidekiq_options memory_killer_max_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MAX_MEMORY_GROWTH_KB', 300_000).to_i diff --git a/app/workers/repository_remove_remote_worker.rb b/app/workers/repository_remove_remote_worker.rb index a85e9fa9394..3e55ebc77ed 100644 --- a/app/workers/repository_remove_remote_worker.rb +++ b/app/workers/repository_remove_remote_worker.rb @@ -4,6 +4,8 @@ class RepositoryRemoveRemoteWorker include ApplicationWorker include ExclusiveLeaseGuard + feature_category :source_code_management + LEASE_TIMEOUT = 1.hour attr_reader :project, :remote_name diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb index d13c7641eb3..b4d96546fa4 100644 --- a/app/workers/repository_update_remote_mirror_worker.rb +++ b/app/workers/repository_update_remote_mirror_worker.rb @@ -7,6 +7,7 @@ class RepositoryUpdateRemoteMirrorWorker include Gitlab::ExclusiveLeaseHelpers sidekiq_options retry: 3, dead: false + feature_category :source_code_management LOCK_WAIT_TIME = 30.seconds MAX_TRIES = 3 diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb index ae022d43e29..6ab020afb10 100644 --- a/app/workers/requests_profiles_worker.rb +++ b/app/workers/requests_profiles_worker.rb @@ -4,6 +4,8 @@ class RequestsProfilesWorker include ApplicationWorker include CronjobQueue + feature_category :source_code_management + def perform Gitlab::RequestProfiler.remove_all_profiles end diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb index 659f8b80397..853f774875a 100644 --- a/app/workers/run_pipeline_schedule_worker.rb +++ b/app/workers/run_pipeline_schedule_worker.rb @@ -5,6 +5,7 @@ class RunPipelineScheduleWorker include PipelineQueue queue_namespace :pipeline_creation + feature_category :continuous_integration # rubocop: disable CodeReuse/ActiveRecord def perform(schedule_id, user_id) diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb index 04a370f01af..8abb5922b54 100644 --- a/app/workers/schedule_migrate_external_diffs_worker.rb +++ b/app/workers/schedule_migrate_external_diffs_worker.rb @@ -5,6 +5,8 @@ class ScheduleMigrateExternalDiffsWorker include CronjobQueue include Gitlab::ExclusiveLeaseHelpers + feature_category :source_code_management + def perform in_lock(self.class.name.underscore, ttl: 2.hours, retries: 0) do MergeRequests::MigrateExternalDiffsService.enqueue! diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb index 7e002d8822c..971edb1f14f 100644 --- a/app/workers/stuck_ci_jobs_worker.rb +++ b/app/workers/stuck_ci_jobs_worker.rb @@ -4,6 +4,8 @@ class StuckCiJobsWorker include ApplicationWorker include CronjobQueue + feature_category :continuous_integration + EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease' BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb index a9ff5b22b25..4993cd1220c 100644 --- a/app/workers/stuck_import_jobs_worker.rb +++ b/app/workers/stuck_import_jobs_worker.rb @@ -4,6 +4,8 @@ class StuckImportJobsWorker include ApplicationWorker include CronjobQueue + feature_category :importers + IMPORT_JOBS_EXPIRATION = 15.hours.to_i def perform diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb index e840ae47421..024863ab530 100644 --- a/app/workers/stuck_merge_jobs_worker.rb +++ b/app/workers/stuck_merge_jobs_worker.rb @@ -4,6 +4,8 @@ class StuckMergeJobsWorker include ApplicationWorker include CronjobQueue + feature_category :source_code_management + def self.logger Rails.logger # rubocop:disable Gitlab/RailsLogger end @@ -31,7 +33,7 @@ class StuckMergeJobsWorker def apply_current_state!(completed_jids, completed_ids) merge_requests = MergeRequest.where(id: completed_ids) - merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) + merge_requests.where.not(merge_commit_sha: nil).update_all(state_id: MergeRequest.available_states[:merged]) merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil) diff --git a/app/workers/system_hook_push_worker.rb b/app/workers/system_hook_push_worker.rb index 15e369ebcfb..fc6237f359a 100644 --- a/app/workers/system_hook_push_worker.rb +++ b/app/workers/system_hook_push_worker.rb @@ -3,6 +3,8 @@ class SystemHookPushWorker include ApplicationWorker + feature_category :source_code_management + def perform(push_data, hook_id) SystemHooksService.new.execute_hooks(push_data, hook_id) end diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb index 55b599ba38f..4c8ee1ee425 100644 --- a/app/workers/trending_projects_worker.rb +++ b/app/workers/trending_projects_worker.rb @@ -4,6 +4,8 @@ class TrendingProjectsWorker include ApplicationWorker include CronjobQueue + feature_category :source_code_management + def perform Rails.logger.info('Refreshing trending projects') # rubocop:disable Gitlab/RailsLogger diff --git a/app/workers/update_external_pull_requests_worker.rb b/app/workers/update_external_pull_requests_worker.rb index c5acfa82ada..8b0952528fa 100644 --- a/app/workers/update_external_pull_requests_worker.rb +++ b/app/workers/update_external_pull_requests_worker.rb @@ -3,6 +3,8 @@ class UpdateExternalPullRequestsWorker include ApplicationWorker + feature_category :source_code_management + def perform(project_id, user_id, ref) project = Project.find_by_id(project_id) return unless project diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb index 4ec2b9d8fbe..77859abfea4 100644 --- a/app/workers/update_head_pipeline_for_merge_request_worker.rb +++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb @@ -5,6 +5,7 @@ class UpdateHeadPipelineForMergeRequestWorker include PipelineQueue queue_namespace :pipeline_processing + feature_category :continuous_integration def perform(merge_request_id) MergeRequest.find_by_id(merge_request_id).try do |merge_request| diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb index 6c0e472e05a..8e1703cdd0b 100644 --- a/app/workers/update_merge_requests_worker.rb +++ b/app/workers/update_merge_requests_worker.rb @@ -3,6 +3,8 @@ class UpdateMergeRequestsWorker include ApplicationWorker + feature_category :source_code_management + LOG_TIME_THRESHOLD = 90 # seconds # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/workers/update_project_statistics_worker.rb b/app/workers/update_project_statistics_worker.rb index 3abb7e34a9d..e36cebf6f4f 100644 --- a/app/workers/update_project_statistics_worker.rb +++ b/app/workers/update_project_statistics_worker.rb @@ -4,6 +4,8 @@ class UpdateProjectStatisticsWorker include ApplicationWorker + feature_category :source_code_management + # project_id - The ID of the project for which to flush the cache. # statistics - An Array containing columns from ProjectStatistics to # refresh, if empty all columns will be refreshed diff --git a/app/workers/upload_checksum_worker.rb b/app/workers/upload_checksum_worker.rb index 834dcaa435d..d35367145b8 100644 --- a/app/workers/upload_checksum_worker.rb +++ b/app/workers/upload_checksum_worker.rb @@ -3,6 +3,8 @@ class UploadChecksumWorker include ApplicationWorker + feature_category :geo_replication + def perform(upload_id) upload = Upload.find(upload_id) upload.calculate_checksum! diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb index 09219a24a16..fd7ca93683e 100644 --- a/app/workers/web_hook_worker.rb +++ b/app/workers/web_hook_worker.rb @@ -3,6 +3,7 @@ class WebHookWorker include ApplicationWorker + feature_category :integrations sidekiq_options retry: 4, dead: false def perform(hook_id, data, hook_name) diff --git a/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml b/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml new file mode 100644 index 00000000000..db55148cf65 --- /dev/null +++ b/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Make commit status created for any pipelines +merge_request: 17524 +author: Aufar Gilbran +type: changed diff --git a/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml b/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml new file mode 100644 index 00000000000..efdd47bb061 --- /dev/null +++ b/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml @@ -0,0 +1,5 @@ +--- +title: Check if mapping is empty before caching in File Collections +merge_request: 18290 +author: briankabiro +type: performance diff --git a/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml b/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml new file mode 100644 index 00000000000..668e25f4749 --- /dev/null +++ b/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml @@ -0,0 +1,5 @@ +--- +title: Improve time window filtering on metrics dashboard +merge_request: 17554 +author: +type: added diff --git a/changelogs/unreleased/ac-fix-only-os-uplods.yml b/changelogs/unreleased/ac-fix-only-os-uplods.yml new file mode 100644 index 00000000000..d63ddc059b6 --- /dev/null +++ b/changelogs/unreleased/ac-fix-only-os-uplods.yml @@ -0,0 +1,5 @@ +--- +title: Avoid dumping files on disk when direct_upload is enabled +merge_request: 18135 +author: +type: performance diff --git a/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml b/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml new file mode 100644 index 00000000000..6f5832dfef2 --- /dev/null +++ b/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml @@ -0,0 +1,5 @@ +--- +title: Attribute each Sidekiq worker to a feature category +merge_request: 18462 +author: +type: other diff --git a/changelogs/unreleased/issue_28457.yml b/changelogs/unreleased/issue_28457.yml new file mode 100644 index 00000000000..51e19660ffc --- /dev/null +++ b/changelogs/unreleased/issue_28457.yml @@ -0,0 +1,5 @@ +--- +title: Deprecate usage of state column for issues and merge requests +merge_request: 18099 +author: +type: changed diff --git a/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml b/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml new file mode 100644 index 00000000000..f786df41528 --- /dev/null +++ b/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml @@ -0,0 +1,5 @@ +--- +title: Do not show new issue button on archived projects +merge_request: 18590 +author: +type: changed diff --git a/config/feature_categories.yml b/config/feature_categories.yml new file mode 100644 index 00000000000..59752a81f60 --- /dev/null +++ b/config/feature_categories.yml @@ -0,0 +1,103 @@ +# +# This file contains a list of all feature categories in GitLab +# It is generated from the stages file at https://gitlab.com/gitlab-com/www-gitlab-com/raw/master/data/stages.yml. +# If you would like to update it, please run +# `./scripts/update-feature-categories` to generate a new copy +# +# PLEASE DO NOT EDIT THIS FILE MANUALLY. +# +--- +- accessibility_testing +- account-management +- agile_portfolio_management +- analysis +- audit_management +- authentication_and_authorization +- auto_devops +- backup_restore +- behavior_analytics +- chaos_engineering +- chatops +- cloud_native_installation +- cluster_cost_optimization +- cluster_monitoring +- code_analytics +- code_quality +- code_review +- collection +- container_network_security +- container_registry +- container_scanning +- continuous_delivery +- continuous_integration +- data_loss_prevention +- dependency_proxy +- dependency_scanning +- design_management +- devops_score +- disaster_recovery +- dynamic_application_security_testing +- error_tracking +- feature_flags +- fuzzing +- geo_replication +- gitaly +- gitter +- groups +- helm_chart_registry +- importers +- incident_management +- incremental_rollout +- infrastructure_as_code +- integration_testing +- integrations +- interactive_application_security_testing +- internationalization +- issue_tracking +- kanban_boards +- kubernetes_configuration +- language_specific +- license_compliance +- live_coding +- load_testing +- logging +- metrics +- omnibus_package +- package_registry +- pages +- quality_management +- release_governance +- release_orchestration +- requirements_management +- review_apps +- runbooks +- runner +- runtime_application_self_protection +- sdk +- search +- secret_detection +- secrets_management +- serverless +- service_desk +- snippets +- source_code_management +- static_application_security_testing +- status_page +- storage_security +- synthetic_monitoring +- system_testing +- templates +- threat_detection +- time_tracking +- tracing +- unit_testing +- usability_testing +- users +- value_stream_management +- vulnerability_database +- vulnerability_management +- web_firewall +- web_ide +- web_performance +- wiki +- workflow_policies diff --git a/db/migrate/20190927055500_create_description_versions.rb b/db/migrate/20190927055500_create_description_versions.rb new file mode 100644 index 00000000000..6ad34d4a89e --- /dev/null +++ b/db/migrate/20190927055500_create_description_versions.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class CreateDescriptionVersions < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :description_versions do |t| + t.timestamps_with_timezone + t.references :issue, index: false, foreign_key: { on_delete: :cascade }, type: :integer + t.references :merge_request, index: false, foreign_key: { on_delete: :cascade }, type: :integer + t.references :epic, index: false, foreign_key: { on_delete: :cascade }, type: :integer + t.text :description + end + + add_index :description_versions, :issue_id, where: 'issue_id IS NOT NULL' + add_index :description_versions, :merge_request_id, where: 'merge_request_id IS NOT NULL' + add_index :description_versions, :epic_id, where: 'epic_id IS NOT NULL' + + add_column :system_note_metadata, :description_version_id, :bigint + end + + def down + remove_column :system_note_metadata, :description_version_id + + drop_table :description_versions + end +end diff --git a/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb b/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb new file mode 100644 index 00000000000..695ba955043 --- /dev/null +++ b/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddIndexToSytemNoteMetadataDescriptionVersionId < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :system_note_metadata, :description_version_id, unique: true, where: 'description_version_id IS NOT NULL' + add_concurrent_foreign_key :system_note_metadata, :description_versions, column: :description_version_id, on_delete: :nullify + end + + def down + remove_foreign_key :system_note_metadata, column: :description_version_id + remove_concurrent_index :system_note_metadata, :description_version_id + end +end diff --git a/db/migrate/20191008180203_add_issuable_state_id_indexes.rb b/db/migrate/20191008180203_add_issuable_state_id_indexes.rb new file mode 100644 index 00000000000..a9a8b8b6359 --- /dev/null +++ b/db/migrate/20191008180203_add_issuable_state_id_indexes.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +class AddIssuableStateIdIndexes < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + # Creates the same indexes that are currently using state:string column + # for issues and merge_requests tables + create_indexes_for_issues + create_indexes_for_merge_requests + end + + def down + # Removes indexes for issues + remove_concurrent_index_by_name :issues, 'idx_issues_on_state_id' + remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_created_at_and_id_and_state_id' + remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_due_date_and_id_and_state_id' + remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id' + remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id' + + # Removes indexes from merge_requests + remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_id_and_merge_jid' + remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_source_project_and_branch_state_opened' + remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_state_id_and_merge_status' + remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_target_project_id_and_iid_opened' + end + + def create_indexes_for_issues + add_concurrent_index :issues, :state_id, name: 'idx_issues_on_state_id' + + add_concurrent_index :issues, + [:project_id, :created_at, :id, :state_id], + name: 'idx_issues_on_project_id_and_created_at_and_id_and_state_id' + + add_concurrent_index :issues, + [:project_id, :due_date, :id, :state_id], + where: 'due_date IS NOT NULL', + name: 'idx_issues_on_project_id_and_due_date_and_id_and_state_id' + + add_concurrent_index :issues, + [:project_id, :relative_position, :state_id, :id], + order: { id: :desc }, + name: 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id' + + add_concurrent_index :issues, + [:project_id, :updated_at, :id, :state_id], + name: 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id' + end + + def create_indexes_for_merge_requests + add_concurrent_index :merge_requests, + [:id, :merge_jid], + where: 'merge_jid IS NOT NULL and state_id = 4', + name: 'idx_merge_requests_on_id_and_merge_jid' + + add_concurrent_index :merge_requests, + [:source_project_id, :source_branch], + where: 'state_id = 1', + name: 'idx_merge_requests_on_source_project_and_branch_state_opened' + + add_concurrent_index :merge_requests, + [:state_id, :merge_status], + where: "state_id = 1 AND merge_status = 'can_be_merged'", + name: 'idx_merge_requests_on_state_id_and_merge_status' + + add_concurrent_index :merge_requests, + [:target_project_id, :iid], + where: 'state_id = 1', + name: 'idx_merge_requests_on_target_project_id_and_iid_opened' + end +end diff --git a/db/migrate/20191008200204_add_state_id_default_value.rb b/db/migrate/20191008200204_add_state_id_default_value.rb new file mode 100644 index 00000000000..15a80163ca8 --- /dev/null +++ b/db/migrate/20191008200204_add_state_id_default_value.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddStateIdDefaultValue < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + change_column_default :issues, :state_id, 1 + change_column_null :issues, :state_id, false + change_column_default :merge_requests, :state_id, 1 + change_column_null :merge_requests, :state_id, false + end + + def down + change_column_default :issues, :state_id, nil + change_column_null :issues, :state_id, true + change_column_default :merge_requests, :state_id, nil + change_column_null :merge_requests, :state_id, true + end +end diff --git a/db/schema.rb b/db/schema.rb index e43eaef911d..ef9cd696db5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1266,6 +1266,18 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status" end + create_table "description_versions", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "issue_id" + t.integer "merge_request_id" + t.integer "epic_id" + t.text "description" + t.index ["epic_id"], name: "index_description_versions_on_epic_id", where: "(epic_id IS NOT NULL)" + t.index ["issue_id"], name: "index_description_versions_on_issue_id", where: "(issue_id IS NOT NULL)" + t.index ["merge_request_id"], name: "index_description_versions_on_merge_request_id", where: "(merge_request_id IS NOT NULL)" + end + create_table "design_management_designs", force: :cascade do |t| t.integer "project_id", null: false t.integer "issue_id" @@ -1916,7 +1928,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do t.boolean "discussion_locked" t.datetime_with_timezone "closed_at" t.integer "closed_by_id" - t.integer "state_id", limit: 2 + t.integer "state_id", limit: 2, default: 1, null: false t.integer "duplicated_to_id" t.index ["author_id"], name: "index_issues_on_author_id" t.index ["closed_by_id"], name: "index_issues_on_closed_by_id" @@ -1926,12 +1938,17 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do t.index ["milestone_id"], name: "index_issues_on_milestone_id" t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)" t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state" + t.index ["project_id", "created_at", "id", "state_id"], name: "idx_issues_on_project_id_and_created_at_and_id_and_state_id" t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)" + t.index ["project_id", "due_date", "id", "state_id"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_id", where: "(due_date IS NOT NULL)" t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true t.index ["project_id", "relative_position", "state", "id"], name: "index_issues_on_project_id_and_rel_position_and_state_and_id", order: { id: :desc } + t.index ["project_id", "relative_position", "state_id", "id"], name: "idx_issues_on_project_id_and_rel_position_and_state_id_and_id", order: { id: :desc } t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state" + t.index ["project_id", "updated_at", "id", "state_id"], name: "idx_issues_on_project_id_and_updated_at_and_id_and_state_id" t.index ["relative_position"], name: "index_issues_on_relative_position" t.index ["state"], name: "index_issues_on_state" + t.index ["state_id"], name: "idx_issues_on_state_id" t.index ["title"], name: "index_issues_on_title_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["updated_at"], name: "index_issues_on_updated_at" t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)" @@ -2276,23 +2293,27 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do t.boolean "discussion_locked" t.integer "latest_merge_request_diff_id" t.boolean "allow_maintainer_to_push" - t.integer "state_id", limit: 2 + t.integer "state_id", limit: 2, default: 1, null: false t.string "rebase_jid" t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id" t.index ["author_id"], name: "index_merge_requests_on_author_id" t.index ["created_at"], name: "index_merge_requests_on_created_at" t.index ["description"], name: "index_merge_requests_on_description_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id" + t.index ["id", "merge_jid"], name: "idx_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND (state_id = 4))" t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))" t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id" t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)" t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id" t.index ["source_branch"], name: "index_merge_requests_on_source_branch" + t.index ["source_project_id", "source_branch"], name: "idx_merge_requests_on_source_project_and_branch_state_opened", where: "(state_id = 1)" t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)" t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch" t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))" + t.index ["state_id", "merge_status"], name: "idx_merge_requests_on_state_id_and_merge_status", where: "((state_id = 1) AND ((merge_status)::text = 'can_be_merged'::text))" t.index ["target_branch"], name: "index_merge_requests_on_target_branch" t.index ["target_project_id", "created_at"], name: "index_merge_requests_target_project_id_created_at" + t.index ["target_project_id", "iid"], name: "idx_merge_requests_on_target_project_id_and_iid_opened", where: "(state_id = 1)" t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)" t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id" @@ -3494,6 +3515,8 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do t.string "action" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "description_version_id" + t.index ["description_version_id"], name: "index_system_note_metadata_on_description_version_id", unique: true, where: "(description_version_id IS NOT NULL)" t.index ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true end @@ -4089,6 +4112,9 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deployments", "clusters", name: "fk_289bba3222", on_delete: :nullify add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade + add_foreign_key "description_versions", "epics", on_delete: :cascade + add_foreign_key "description_versions", "issues", on_delete: :cascade + add_foreign_key "description_versions", "merge_requests", on_delete: :cascade add_foreign_key "design_management_designs", "issues", on_delete: :cascade add_foreign_key "design_management_designs", "projects", on_delete: :cascade add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade @@ -4325,6 +4351,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do add_foreign_key "software_license_policies", "software_licenses", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "suggestions", "notes", on_delete: :cascade + add_foreign_key "system_note_metadata", "description_versions", name: "fk_fbd87415c9", on_delete: :nullify add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade add_foreign_key "term_agreements", "application_setting_terms", column: "term_id" add_foreign_key "term_agreements", "users", on_delete: :cascade diff --git a/doc/api/issues.md b/doc/api/issues.md index 12a63ce6e24..075abf6c360 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -615,6 +615,7 @@ POST /projects/:id/issues | `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values.| | `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. | | `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. | +| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. | ```bash curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug @@ -716,6 +717,7 @@ PUT /projects/:id/issues/:issue_iid | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | | `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. 0 | | `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. | +| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. | ```bash curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md index c181b31f069..d52a3e652e3 100644 --- a/doc/development/sidekiq_style_guide.md +++ b/doc/development/sidekiq_style_guide.md @@ -61,6 +61,56 @@ the extra jobs will take resources away from jobs from workers that were already there, if the resources available to the Sidekiq process handling the namespace are not adjusted appropriately. +## Feature Categorization + +Each Sidekiq worker, or one of its ancestor classes, must declare a +`feature_category` attribute. This attribute maps each worker to a feature +category. This is done for error budgeting, alert routing, and team attribution +for Sidekiq workers. + +The declaration uses the `feature_category` class method, as shown below. + +```ruby +class SomeScheduledTaskWorker + include ApplicationWorker + + # Declares that this feature is part of the + # `continuous_integration` feature category + feature_category :continuous_integration + + # ... +end +``` + +The list of value values can be found in the file `config/feature_categories.yml`. +This file is, in turn generated from the [`stages.yml` from the GitLab Company Handbook +source](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml). + +### Updating `config/feature_categories.yml` + +Occassionally new features will be added to GitLab stages. When this occurs, you +can automatically update `config/feature_categories.yml` by running +`scripts/update-feature-categories`. This script will fetch and parse +[`stages.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) +and generare a new version of the file, which needs to be checked into source control. + +### Excluding Sidekiq workers from feature categorization + +A few Sidekiq workers, that are used across all features, cannot be mapped to a +single category. These should be declared as such using the `feature_category_not_owned!` + declaration, as shown below: + +```ruby +class SomeCrossCuttingConcernWorker + include ApplicationWorker + + # Declares that this worker does not map to a feature category + feature_category_not_owned! + + # ... +end +``` + ## Tests Each Sidekiq worker must be tested using RSpec, just like any other class. These diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md index 129876f0c8e..0823c2e02b8 100644 --- a/doc/development/testing_guide/flaky_tests.md +++ b/doc/development/testing_guide/flaky_tests.md @@ -80,6 +80,9 @@ This was originally implemented in: <https://gitlab.com/gitlab-org/gitlab-foss/m - [Bis](https://gitlab.com/gitlab-org/gitlab-foss/issues/34609#note_34048715): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12604> - [Bis](https://gitlab.com/gitlab-org/gitlab-foss/issues/34698#note_34276286): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12664> - [Assert against the underlying database state instead of against a page's content](https://gitlab.com/gitlab-org/gitlab-foss/issues/31437): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10934> +- In JS tests, shifting elements can cause Capybara to misclick when the element moves at the exact time Capybara sends the click + - [Dropdowns rendering upward or downward due to window size and scroll position](https://gitlab.com/gitlab-org/gitlab/merge_requests/17660) + - [Lazy loaded images can cause Capybara to misclick](https://gitlab.com/gitlab-org/gitlab/merge_requests/18713) #### Capybara viewport size related issues diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md index 87a92fcbf2a..7313a25e797 100644 --- a/doc/user/analytics/productivity_analytics.md +++ b/doc/user/analytics/productivity_analytics.md @@ -1,6 +1,6 @@ # Productivity Analytics **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 (enabled by feature flags `productivity_analytics`). +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 (enabled by feature flags `productivity_analytics`, `productivity_analytics_scatterplot_enabled`). Track development velocity with Productivity Analytics. diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index d58a5e214ed..d108c811f4b 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -58,7 +58,6 @@ module API post ':id/statuses/:sha' do authorize! :create_commit_status, user_project - commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline) @@ -68,14 +67,15 @@ module API # If we don't receive it, we will attach the CommitStatus to # the first found branch on that commit + pipeline = all_matching_pipelines.first + ref = params[:ref] + ref ||= pipeline&.ref ref ||= @project.repository.branch_names_contains(commit.sha).first not_found! 'References for commit' unless ref name = params[:name] || params[:context] || 'default' - pipeline = @project.pipeline_for(ref, commit.sha, params[:pipeline_id]) - unless pipeline pipeline = @project.ci_pipelines.create!( source: :external, @@ -126,6 +126,20 @@ module API end end # rubocop: enable CodeReuse/ActiveRecord + helpers do + def commit + strong_memoize(:commit) do + user_project.commit(params[:sha]) + end + end + + def all_matching_pipelines + pipelines = user_project.ci_pipelines.newest_first(sha: commit.sha) + pipelines = pipelines.for_ref(params[:ref]) if params[:ref] + pipelines = pipelines.for_id(params[:pipeline_id]) if params[:pipeline_id] + pipelines + end + end end end end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 24bc73e0de5..e01ffb631ba 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -104,7 +104,7 @@ module Gitlab iid: issue.iid, title: issue.title, description: description, - state: issue.state, + state_id: Issue.available_states[issue.state], author_id: gitlab_user_id(project, issue.author), milestone: milestone, created_at: issue.created_at, diff --git a/lib/gitlab/discussions_diff/file_collection.rb b/lib/gitlab/discussions_diff/file_collection.rb index 6692dd76438..7a9d4c5c0c2 100644 --- a/lib/gitlab/discussions_diff/file_collection.rb +++ b/lib/gitlab/discussions_diff/file_collection.rb @@ -27,12 +27,14 @@ module Gitlab # - The cache content is not updated (there's no need to do so) def load_highlight ids = highlightable_collection_ids + return if ids.empty? + cached_content = read_cache(ids) uncached_ids = ids.select.each_with_index { |_, i| cached_content[i].nil? } mapping = highlighted_lines_by_ids(uncached_ids) - HighlightCache.write_multiple(mapping) + HighlightCache.write_multiple(mapping) if mapping.any? diffs = diff_files_indexed_by_id.values_at(*ids) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 1e7203cb82a..4da2004b74f 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -117,7 +117,7 @@ module Gitlab description: body, author_id: project.creator_id, assignee_ids: [assignee_id], - state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' + state_id: raw_issue['state'] == 'closed' ? Issue.available_states[:closed] : Issue.available_states[:opened] ) issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true) diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index 1f64e440141..9d9db6cf94f 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -27,7 +27,7 @@ module Gitlab duplicated_to_id project_id relative_position - state + state_id time_estimate title updated_at @@ -46,7 +46,8 @@ module Gitlab human_time_estimate: issue.human_time_estimate, assignee_ids: issue.assignee_ids, assignee_id: issue.assignee_ids.first, # This key is deprecated - labels: issue.labels_hook_attrs + labels: issue.labels_hook_attrs, + state: issue.state } issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes) diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9ec244b0960..cb85af91f75 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -292,7 +292,11 @@ module Gitlab existing_object else - relation_class.new(parsed_relation_hash) + object = relation_class.new + + # Use #assign_attributes here to call object custom setters + object.assign_attributes(parsed_relation_hash) + object end end end diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb index b69c65e78f8..d2c2ef8db48 100644 --- a/lib/gitlab/phabricator_import/base_worker.rb +++ b/lib/gitlab/phabricator_import/base_worker.rb @@ -23,6 +23,8 @@ module Gitlab include ProjectImportOptions # This marks the project as failed after too many tries include Gitlab::ExclusiveLeaseHelpers + feature_category :importers + class << self def schedule(project_id, *args) perform_async(project_id, *args) diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index aae542f02ac..424db653fb8 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -6,6 +6,7 @@ require "fileutils" class UploadedFile InvalidPathError = Class.new(StandardError) + UnknownSizeError = Class.new(StandardError) # The filename, *not* including the path, of the "uploaded" file attr_reader :original_filename @@ -18,37 +19,50 @@ class UploadedFile attr_reader :remote_id attr_reader :sha256 - - def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil) - raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path) + attr_reader :size + + def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil, size: nil) + if path.present? + raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path) + + @tempfile = File.new(path, 'rb') + @size = @tempfile.size + else + begin + @size = Integer(size) + rescue ArgumentError, TypeError + raise UnknownSizeError, 'Unable to determine file size' + end + end @content_type = content_type - @original_filename = sanitize_filename(filename || path) + @original_filename = sanitize_filename(filename || path || '') @content_type = content_type @sha256 = sha256 @remote_id = remote_id - @tempfile = File.new(path, 'rb') end def self.from_params(params, field, upload_paths) - unless params["#{field}.path"] - raise InvalidPathError, "file is invalid" if params["#{field}.remote_id"] - - return - end - - file_path = File.realpath(params["#{field}.path"]) - - paths = Array(upload_paths) << Dir.tmpdir - unless self.allowed_path?(file_path, paths.compact) - raise InvalidPathError, "insecure path used '#{file_path}'" + path = params["#{field}.path"] + remote_id = params["#{field}.remote_id"] + return if path.blank? && remote_id.blank? + + file_path = nil + if path + file_path = File.realpath(path) + + paths = Array(upload_paths) << Dir.tmpdir + unless self.allowed_path?(file_path, paths.compact) + raise InvalidPathError, "insecure path used '#{file_path}'" + end end UploadedFile.new(file_path, filename: params["#{field}.name"], content_type: params["#{field}.type"] || 'application/octet-stream', sha256: params["#{field}.sha256"], - remote_id: params["#{field}.remote_id"]) + remote_id: remote_id, + size: params["#{field}.size"]) end def self.allowed_path?(file_path, paths) @@ -68,7 +82,11 @@ class UploadedFile end def path - @tempfile.path + @tempfile&.path + end + + def close + @tempfile&.close end alias_method :local_path, :path diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 524ac9046b5..b3f4b0f4931 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -250,6 +250,9 @@ msgstr "" msgid "%{firstLabel} +%{labelCount} more" msgstr "" +msgid "%{from} to %{to}" +msgstr "" + msgid "%{gitlab_ci_yml} not found in this commit" msgstr "" @@ -1776,6 +1779,9 @@ msgstr "" msgid "Applied" msgstr "" +msgid "Apply" +msgstr "" + msgid "Apply a label" msgstr "" @@ -4855,6 +4861,9 @@ msgstr "" msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates." msgstr "" +msgid "Custom range" +msgstr "" + msgid "CustomCycleAnalytics|Add a stage" msgstr "" @@ -7331,6 +7340,9 @@ msgstr "" msgid "Format" msgstr "" +msgid "Format: %{dateFormat}" +msgstr "" + msgid "Forward external support email address to" msgstr "" @@ -10412,6 +10424,9 @@ msgstr "" msgid "Metrics|Legend label (optional)" msgstr "" +msgid "Metrics|Link contains an invalid time window." +msgstr "" + msgid "Metrics|Max" msgstr "" @@ -13379,6 +13394,9 @@ msgstr "" msgid "Quick actions can be used in the issues description and comment boxes." msgstr "" +msgid "Quick range" +msgstr "" + msgid "README" msgstr "" @@ -17155,6 +17173,9 @@ msgstr "" msgid "Titles and Filenames" msgstr "" +msgid "To" +msgstr "" + msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration." msgstr "" diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index 20d4a00c020..ff5e118cefa 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -113,6 +113,7 @@ describe QA::Page::Element do describe 'data-qa selectors' do subject { described_class.new(:my_element) } + it 'properly translates to a data-qa-selector' do expect(subject.selector_css).to include(%q([data-qa-selector="my_element"])) end diff --git a/scripts/update-feature-categories b/scripts/update-feature-categories new file mode 100755 index 00000000000..ed5d8dccdd6 --- /dev/null +++ b/scripts/update-feature-categories @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +require 'uri' +require 'net/http' +require 'yaml' + +url = URI("https://gitlab.com/gitlab-com/www-gitlab-com/raw/master/data/stages.yml") + +http = Net::HTTP.new(url.host, url.port) +http.use_ssl = true + +request = Net::HTTP::Get.new(url) + +response = http.request(request) + +stages_doc = YAML.safe_load(response.read_body) +feature_categories = stages_doc["stages"].values + .flat_map { |stage| stage["groups"].values } + .flat_map { |group| group["categories"] } + .select(&:itself) + .uniq + .sort + +File.open("#{__dir__}/../config/feature_categories.yml", 'w') do |file| + file.puts(<<~HEADER_COMMENT) + # + # This file contains a list of all feature categories in GitLab + # It is generated from the stages file at #{url}. + # If you would like to update it, please run + # `./scripts/update-feature-categories` to generate a new copy + # + # PLEASE DO NOT EDIT THIS FILE MANUALLY. + # + HEADER_COMMENT + file.write(feature_categories.to_yaml) +end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 2edc0aa5536..c9558abab33 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1180,6 +1180,7 @@ describe Projects::IssuesController do name: emoji_name }) end + let(:emoji_name) { 'thumbsup' } it "toggles the award emoji" do diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 4db77921f24..3ab191c0032 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -713,6 +713,7 @@ describe Projects::NotesController do end subject { post(:toggle_award_emoji, params: request_params.merge(name: emoji_name)) } + let(:emoji_name) { 'thumbsup' } it "toggles the award emoji" do diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 9f1ef3a4be8..eccc8e1d5de 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -107,26 +107,50 @@ describe Projects::Serverless::FunctionsController do end end - context 'valid data', :use_clean_rails_memory_store_caching do - before do - stub_kubeclient_service_pods - stub_reactive_cache(knative_services_finder, - { - services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], - pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }, - *knative_services_finder.cache_args) + context 'with valid data', :use_clean_rails_memory_store_caching do + shared_examples 'GET #show with valid data' do + it 'has a valid function name' do + get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name }) + expect(response).to have_gitlab_http_status(200) + + expect(json_response).to include( + "name" => project.name, + "url" => "http://#{project.name}.#{namespace.namespace}.example.com", + "podcount" => 1 + ) + end end - it 'has a valid function name' do - get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name }) - expect(response).to have_gitlab_http_status(200) + context 'on Knative 0.5' do + before do + stub_kubeclient_service_pods + stub_reactive_cache(knative_services_finder, + { + services: kube_knative_services_body( + legacy_knative: true, + namespace: namespace.namespace, + name: cluster.project.name + )["items"], + pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] + }, + *knative_services_finder.cache_args) + end - expect(json_response).to include( - "name" => project.name, - "url" => "http://#{project.name}.#{namespace.namespace}.example.com", - "podcount" => 1 - ) + include_examples 'GET #show with valid data' + end + + context 'on Knative 0.6 or 0.7' do + before do + stub_kubeclient_service_pods + stub_reactive_cache(knative_services_finder, + { + services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], + pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] + }, + *knative_services_finder.cache_args) + end + + include_examples 'GET #show with valid data' end end end @@ -141,38 +165,60 @@ describe Projects::Serverless::FunctionsController do end describe 'GET #index with data', :use_clean_rails_memory_store_caching do - before do - stub_kubeclient_service_pods - stub_reactive_cache(knative_services_finder, - { - services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], - pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }, - *knative_services_finder.cache_args) + shared_examples 'GET #index with data' do + it 'has data' do + get :index, params: params({ format: :json }) + + expect(response).to have_gitlab_http_status(200) + + expect(json_response).to match({ + "knative_installed" => "checking", + "functions" => [ + a_hash_including( + "name" => project.name, + "url" => "http://#{project.name}.#{namespace.namespace}.example.com" + ) + ] + }) + end + + it 'has data in html' do + get :index, params: params + + expect(response).to have_gitlab_http_status(200) + end end - it 'has data' do - get :index, params: params({ format: :json }) - - expect(response).to have_gitlab_http_status(200) - - expect(json_response).to match( - { - "knative_installed" => "checking", - "functions" => [ - a_hash_including( - "name" => project.name, - "url" => "http://#{project.name}.#{namespace.namespace}.example.com" - ) - ] - } - ) + context 'on Knative 0.5' do + before do + stub_kubeclient_service_pods + stub_reactive_cache(knative_services_finder, + { + services: kube_knative_services_body( + legacy_knative: true, + namespace: namespace.namespace, + name: cluster.project.name + )["items"], + pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] + }, + *knative_services_finder.cache_args) + end + + include_examples 'GET #index with data' end - it 'has data in html' do - get :index, params: params + context 'on Knative 0.6 or 0.7' do + before do + stub_kubeclient_service_pods + stub_reactive_cache(knative_services_finder, + { + services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], + pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] + }, + *knative_services_finder.cache_args) + end - expect(response).to have_gitlab_http_status(200) + include_examples 'GET #index with data' end end end diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 93507b58910..c67e7f7dadd 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -78,6 +78,7 @@ describe Projects::Settings::CiCdController do describe 'PUT #reset_registration_token' do subject { put :reset_registration_token, params: { namespace_id: project.namespace, project_id: project } } + it 'resets runner registration token' do expect { subject }.to change { project.reload.runners_token } end diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb index 954b5338846..ef5301db770 100644 --- a/spec/factories/gitaly/commit.rb +++ b/spec/factories/gitaly/commit.rb @@ -12,6 +12,7 @@ FactoryBot.define do Google::Protobuf::RepeatedField.new(:string, ids) end subject { "My commit" } + body { subject + "\nMy body" } author { build(:gitaly_commit_author) } committer { build(:gitaly_commit_author) } diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 70f480a3bcb..46910078ee5 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -12,7 +12,7 @@ FactoryBot.define do end trait :opened do - state { :opened } + state_id { Issue.available_states[:opened] } end trait :locked do @@ -20,10 +20,14 @@ FactoryBot.define do end trait :closed do - state { :closed } + state_id { Issue.available_states[:closed] } closed_at { Time.now } end + after(:build) do |issue, evaluator| + issue.state_id = Issue.available_states[evaluator.state] + end + factory :closed_issue, traits: [:closed] factory :reopened_issue, traits: [:opened] diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 28a3f76d485..d16e0c10671 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -40,7 +40,7 @@ FactoryBot.define do end trait :merged do - state { :merged } + state_id { MergeRequest.available_states[:merged] } end trait :merged_target do @@ -57,7 +57,7 @@ FactoryBot.define do end trait :closed do - state { :closed } + state_id { MergeRequest.available_states[:closed] } end trait :closed_last_month do @@ -69,7 +69,7 @@ FactoryBot.define do end trait :opened do - state { :opened } + state_id { MergeRequest.available_states[:opened] } end trait :invalid do @@ -78,7 +78,7 @@ FactoryBot.define do end trait :locked do - state { :locked } + state_id { MergeRequest.available_states[:locked] } end trait :simple do @@ -186,6 +186,10 @@ FactoryBot.define do end end + after(:build) do |merge_request, evaluator| + merge_request.state_id = MergeRequest.available_states[evaluator.state] + end + after(:create) do |merge_request, evaluator| merge_request.cache_merge_request_closes_issues! end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 0cb24ef856b..5d87c9d7be8 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -93,6 +93,7 @@ describe 'Group issues page' do end it 'shows projects only with issues feature enabled', :js do + find('.empty-state .js-lazy-loaded') find('.new-project-item-link').click page.within('.select2-results') do diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index d6575ec9de1..a182b6b9d57 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -16,6 +16,7 @@ describe 'Internal Group access' do describe "Group should be internal" do describe '#internal?' do subject { group.internal? } + it { is_expected.to be_truthy } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 2dc863a6e73..5e3e9824aaa 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -16,6 +16,7 @@ describe 'Private Group access' do describe "Group should be private" do describe '#private?' do subject { group.private? } + it { is_expected.to be_truthy } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 4066a19fce2..efc84205980 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -16,6 +16,7 @@ describe 'Public Group access' do describe "Group should be public" do describe '#public?' do subject { group.public? } + it { is_expected.to be_truthy } end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index d089fa718d2..768b883a90e 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -14,6 +14,7 @@ describe "Internal Project Access" do describe "Project should be internal" do describe '#internal?' do subject { project.internal? } + it { is_expected.to be_truthy } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index b868cd595cb..c2d44c05a22 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -14,6 +14,7 @@ describe "Private Project Access" do describe "Project should be private" do describe '#private?' do subject { project.private? } + it { is_expected.to be_truthy } end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 8db2f2d69e5..19f01257713 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -14,6 +14,7 @@ describe "Public Project Access" do describe "Project should be public" do describe '#public?' do subject { project.public? } + it { is_expected.to be_truthy } end end diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb index 159724b3c1f..7ad64cc3bca 100644 --- a/spec/finders/clusters/knative_services_finder_spec.rb +++ b/spec/finders/clusters/knative_services_finder_spec.rb @@ -77,6 +77,7 @@ describe Clusters::KnativeServicesFinder do describe '#knative_detected' do subject { finder.knative_detected } + before do synchronous_reactive_cache(finder) end diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb index 7f6190f96e0..6ba98b79176 100644 --- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb +++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb @@ -24,6 +24,7 @@ describe FinderWithCrossProjectAccess do let(:user) { create(:user) } subject(:finder) { finder_class.new(user) } + let!(:result) { create(:issue) } before do diff --git a/spec/fixtures/lib/gitlab/import_export/project.group.json b/spec/fixtures/lib/gitlab/import_export/project.group.json index 66f5bb4c87b..47faf271cca 100644 --- a/spec/fixtures/lib/gitlab/import_export/project.group.json +++ b/spec/fixtures/lib/gitlab/import_export/project.group.json @@ -129,7 +129,7 @@ "updated_at": "2017-08-15T18:37:40.807Z", "branch_name": null, "description": "Quam totam fuga numquam in eveniet.", - "state": "opened", + "state": "closed", "iid": 2, "updated_by_id": 1, "confidential": false, diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js new file mode 100644 index 00000000000..1315e1226a4 --- /dev/null +++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js @@ -0,0 +1,66 @@ +import { mount } from '@vue/test-utils'; +import DateTimePickerInput from '~/monitoring/components/date_time_picker/date_time_picker_input.vue'; + +const inputLabel = 'This is a label'; +const inputValue = 'something'; + +describe('DateTimePickerInput', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = mount(DateTimePickerInput, { + propsData: { + state: null, + value: '', + label: '', + ...propsData, + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders label above the input', () => { + createComponent({ + label: inputLabel, + }); + + expect(wrapper.find('.gl-form-group label').text()).toBe(inputLabel); + }); + + it('renders the same `ID` for input and `for` for label', () => { + createComponent({ label: inputLabel }); + + expect(wrapper.find('.gl-form-group label').attributes('for')).toBe( + wrapper.find('input').attributes('id'), + ); + }); + + it('renders valid input in gray color instead of green', () => { + createComponent({ + state: true, + }); + + expect(wrapper.find('input').classes('is-valid')).toBe(false); + }); + + it('renders invalid input in red color', () => { + createComponent({ + state: false, + }); + + expect(wrapper.find('input').classes('is-invalid')).toBe(true); + }); + + it('input event is emitted when focus is lost', () => { + createComponent(); + jest.spyOn(wrapper.vm, '$emit'); + wrapper.find('input').setValue(inputValue); + wrapper.find('input').trigger('blur'); + + expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', inputValue); + }); +}); diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js new file mode 100644 index 00000000000..be544435671 --- /dev/null +++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js @@ -0,0 +1,157 @@ +import { mount } from '@vue/test-utils'; +import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue'; +import { timeWindows } from '~/monitoring/constants'; + +const timeWindowsCount = Object.keys(timeWindows).length; +const selectedTimeWindow = { + start: '2019-10-10T07:00:00.000Z', + end: '2019-10-13T07:00:00.000Z', +}; +const selectedTimeWindowText = `3 days`; + +describe('DateTimePicker', () => { + let dateTimePicker; + + const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle'); + const dropdownMenu = () => dateTimePicker.find('.dropdown-menu'); + const applyButtonElement = () => dateTimePicker.find('button[variant="success"]').element; + const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element; + const fillInputAndBlur = (input, val) => { + dateTimePicker.find(input).setValue(val); + dateTimePicker.find(input).trigger('blur'); + }; + + const createComponent = props => { + dateTimePicker = mount(DateTimePicker, { + propsData: { + timeWindows, + selectedTimeWindow, + ...props, + }, + sync: false, + }); + }; + + afterEach(() => { + dateTimePicker.destroy(); + }); + + it('renders dropdown toggle button with selected text', done => { + createComponent(); + dateTimePicker.vm.$nextTick(() => { + expect(dropdownToggle().text()).toBe(selectedTimeWindowText); + done(); + }); + }); + + it('renders dropdown with 2 custom time range inputs', () => { + createComponent(); + dateTimePicker.vm.$nextTick(() => { + expect(dateTimePicker.findAll('input').length).toBe(2); + }); + }); + + it('renders inputs with h/m/s truncated if its all 0s', done => { + createComponent({ + selectedTimeWindow: { + start: '2019-10-10T00:00:00.000Z', + end: '2019-10-14T00:10:00.000Z', + }, + }); + dateTimePicker.vm.$nextTick(() => { + expect(dateTimePicker.find('#custom-time-from').element.value).toBe('2019-10-10'); + expect(dateTimePicker.find('#custom-time-to').element.value).toBe('2019-10-14 00:10:00'); + done(); + }); + }); + + it(`renders dropdown with ${timeWindowsCount} items in quick range`, done => { + createComponent(); + dropdownToggle().trigger('click'); + dateTimePicker.vm.$nextTick(() => { + expect(dateTimePicker.findAll('.dropdown-item').length).toBe(timeWindowsCount); + done(); + }); + }); + + it(`renders dropdown with correct quick range item selected`, done => { + createComponent(); + dropdownToggle().trigger('click'); + dateTimePicker.vm.$nextTick(() => { + expect(dateTimePicker.find('.dropdown-item.active').text()).toBe(selectedTimeWindowText); + + expect(dateTimePicker.find('.dropdown-item.active svg').isVisible()).toBe(true); + done(); + }); + }); + + it('renders a disabled apply button on load', () => { + createComponent(); + + expect(applyButtonElement().getAttribute('disabled')).toBe('disabled'); + }); + + it('displays inline error message if custom time range inputs are invalid', done => { + createComponent(); + fillInputAndBlur('#custom-time-from', '2019-10-01abc'); + fillInputAndBlur('#custom-time-to', '2019-10-10abc'); + + dateTimePicker.vm.$nextTick(() => { + expect(dateTimePicker.findAll('.invalid-feedback').length).toBe(2); + done(); + }); + }); + + it('keeps apply button disabled with invalid custom time range inputs', done => { + createComponent(); + fillInputAndBlur('#custom-time-from', '2019-10-01abc'); + fillInputAndBlur('#custom-time-to', '2019-09-19'); + + dateTimePicker.vm.$nextTick(() => { + expect(applyButtonElement().getAttribute('disabled')).toBe('disabled'); + done(); + }); + }); + + it('enables apply button with valid custom time range inputs', done => { + createComponent(); + fillInputAndBlur('#custom-time-from', '2019-10-01'); + fillInputAndBlur('#custom-time-to', '2019-10-19'); + + dateTimePicker.vm.$nextTick(() => { + expect(applyButtonElement().getAttribute('disabled')).toBeNull(); + done(); + }); + }); + + it('returns an object when apply is clicked', done => { + createComponent(); + fillInputAndBlur('#custom-time-from', '2019-10-01'); + fillInputAndBlur('#custom-time-to', '2019-10-19'); + + dateTimePicker.vm.$nextTick(() => { + jest.spyOn(dateTimePicker.vm, '$emit'); + applyButtonElement().click(); + + expect(dateTimePicker.vm.$emit).toHaveBeenCalledWith('onApply', { + end: '2019-10-19T00:00:00Z', + start: '2019-10-01T00:00:00Z', + }); + done(); + }); + }); + + it('hides the popover with cancel button', done => { + createComponent(); + dropdownToggle().trigger('click'); + + dateTimePicker.vm.$nextTick(() => { + cancelButtonElement().click(); + + dateTimePicker.vm.$nextTick(() => { + expect(dropdownMenu().classes('show')).toBe(false); + done(); + }); + }); + }); +}); diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 193390d2f2c..695d1520897 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -39,6 +39,7 @@ describe MergeRequestsHelper do let(:forked_project) { fork_project(project) } let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) } subject { format_mr_branch_names(merge_request) } + let(:source_title) { "#{forked_project.full_path}:#{merge_request.source_branch}" } let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" } diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index 1b2b01d1c8c..75df2ce3103 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -4,7 +4,6 @@ import { GlToast } from '@gitlab/ui'; import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; -import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import * as types from '~/monitoring/stores/mutation_types'; import { createStore } from '~/monitoring/stores'; import axios from '~/lib/utils/axios_utils'; @@ -37,6 +36,12 @@ const propsData = { validateQueryPath: '', }; +const resetSpy = spy => { + if (spy) { + spy.calls.reset(); + } +}; + export default propsData; describe('Dashboard', () => { @@ -96,10 +101,15 @@ describe('Dashboard', () => { }); describe('requests information to the server', () => { + let spy; beforeEach(() => { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); }); + afterEach(() => { + resetSpy(spy); + }); + it('shows up a loading state', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -272,7 +282,7 @@ describe('Dashboard', () => { }); }); - it('renders the time window dropdown with a set of options', done => { + it('renders the datetimepicker dropdown', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { @@ -282,17 +292,9 @@ describe('Dashboard', () => { }, store, }); - const numberOfTimeWindows = Object.keys(timeWindows).length; setTimeout(() => { - const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); - const timeWindowDropdownEls = component.$el.querySelectorAll( - '.js-time-window-dropdown .dropdown-item', - ); - - expect(timeWindowDropdown).not.toBeNull(); - expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows); - + expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull(); done(); }); }); @@ -355,7 +357,7 @@ describe('Dashboard', () => { }); }); - it('defaults to the eight hours time window for non valid url parameters', done => { + it('shows an error message if invalid url parameters are passed', done => { spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([ '<script>alert("XSS")</script>', ]); @@ -366,9 +368,11 @@ describe('Dashboard', () => { store, }); - Vue.nextTick(() => { - expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours); + spy = spyOn(component, 'showInvalidDateError'); + component.$mount(); + component.$nextTick(() => { + expect(component.showInvalidDateError).toHaveBeenCalled(); done(); }); }); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index 7030156931f..512dd2a0eb3 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -1,4 +1,13 @@ -import { getTimeDiff, getTimeWindow, graphDataValidatorForValues } from '~/monitoring/utils'; +import { + getTimeDiff, + getTimeWindow, + graphDataValidatorForValues, + isDateTimePickerInputValid, + truncateZerosInDateTime, + stringToISODate, + ISODateToString, + isValidDate, +} from '~/monitoring/utils'; import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; @@ -57,7 +66,7 @@ describe('getTimeWindow', () => { end: '2019-10-01T21:27:47.000Z', }, ], - expected: timeWindowsKeyNames.eightHours, + expected: null, }, { args: [ @@ -66,7 +75,7 @@ describe('getTimeWindow', () => { end: '', }, ], - expected: timeWindowsKeyNames.eightHours, + expected: null, }, { args: [ @@ -75,11 +84,11 @@ describe('getTimeWindow', () => { end: null, }, ], - expected: timeWindowsKeyNames.eightHours, + expected: null, }, { args: [{}], - expected: timeWindowsKeyNames.eightHours, + expected: null, }, ].forEach(({ args, expected }) => { it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => { @@ -111,3 +120,190 @@ describe('graphDataValidatorForValues', () => { expect(validGraphData).toBe(true); }); }); + +describe('stringToISODate', () => { + ['', 'null', undefined, 'abc'].forEach(input => { + it(`throws error for invalid input like ${input}`, done => { + try { + stringToISODate(input); + } catch (e) { + expect(e).toBeDefined(); + done(); + } + }); + }); + [ + { + input: '2019-09-09 01:01:01', + output: '2019-09-09T01:01:01Z', + }, + { + input: '2019-09-09 00:00:00', + output: '2019-09-09T00:00:00Z', + }, + { + input: '2019-09-09 23:59:59', + output: '2019-09-09T23:59:59Z', + }, + { + input: '2019-09-09', + output: '2019-09-09T00:00:00Z', + }, + ].forEach(({ input, output }) => { + it(`returns ${output} from ${input}`, () => { + expect(stringToISODate(input)).toBe(output); + }); + }); +}); + +describe('ISODateToString', () => { + [ + { + input: new Date('2019-09-09T00:00:00.000Z'), + output: '2019-09-09 00:00:00', + }, + { + input: new Date('2019-09-09T07:00:00.000Z'), + output: '2019-09-09 07:00:00', + }, + ].forEach(({ input, output }) => { + it(`ISODateToString return ${output} for ${input}`, () => { + expect(ISODateToString(input)).toBe(output); + }); + }); +}); + +describe('truncateZerosInDateTime', () => { + [ + { + input: '', + output: '', + }, + { + input: '2019-10-10', + output: '2019-10-10', + }, + { + input: '2019-10-10 00:00:01', + output: '2019-10-10 00:00:01', + }, + { + input: '2019-10-10 00:00:00', + output: '2019-10-10', + }, + ].forEach(({ input, output }) => { + it(`truncateZerosInDateTime return ${output} for ${input}`, () => { + expect(truncateZerosInDateTime(input)).toBe(output); + }); + }); +}); + +describe('isValidDate', () => { + [ + { + input: '2019-09-09T00:00:00.000Z', + output: true, + }, + { + input: '2019-09-09T000:00.000Z', + output: false, + }, + { + input: 'a2019-09-09T000:00.000Z', + output: false, + }, + { + input: '2019-09-09T', + output: false, + }, + { + input: '2019-09-09', + output: true, + }, + { + input: '2019-9-9', + output: true, + }, + { + input: '2019-9-', + output: true, + }, + { + input: '2019--', + output: false, + }, + { + input: '2019', + output: true, + }, + { + input: '', + output: false, + }, + { + input: null, + output: false, + }, + ].forEach(({ input, output }) => { + it(`isValidDate return ${output} for ${input}`, () => { + expect(isValidDate(input)).toBe(output); + }); + }); +}); + +describe('isDateTimePickerInputValid', () => { + [ + { + input: null, + output: false, + }, + { + input: '', + output: false, + }, + { + input: 'xxxx-xx-xx', + output: false, + }, + { + input: '9999-99-19', + output: false, + }, + { + input: '2019-19-23', + output: false, + }, + { + input: '2019-09-23', + output: true, + }, + { + input: '2019-09-23 x', + output: false, + }, + { + input: '2019-09-29 0:0:0', + output: false, + }, + { + input: '2019-09-29 00:00:00', + output: true, + }, + { + input: '2019-09-29 24:24:24', + output: false, + }, + { + input: '2019-09-29 23:24:24', + output: true, + }, + { + input: '2019-09-29 23:24:24 ', + output: false, + }, + ].forEach(({ input, output }) => { + it(`returns ${output} for ${input}`, () => { + expect(isDateTimePickerInputValid(input)).toBe(output); + }); + }); +}); diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb index e903eada62d..b75f3bafeef 100644 --- a/spec/lib/backup/files_spec.rb +++ b/spec/lib/backup/files_spec.rb @@ -24,6 +24,7 @@ describe Backup::Files do describe '#restore' do subject { described_class.new('registry', '/var/gitlab-registry') } + let(:timestamp) { Time.utc(2017, 3, 22) } around do |example| diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb index 927d226c400..d0b4542d503 100644 --- a/spec/lib/banzai/filter/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -15,6 +15,7 @@ describe Banzai::Filter::ProjectReferenceFilter do let(:project) { create(:project, :public) } subject { project } + let(:subject_name) { "project" } let(:reference) { get_reference(project) } diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 6bc87d245f5..a09aeb7d7f6 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -12,6 +12,7 @@ describe Banzai::Filter::UserReferenceFilter do let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { user } + let(:subject_name) { "user" } let(:reference) { get_reference(user) } diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index b44ae67e430..eac1cf16a8f 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb index da853233018..78b337466aa 100644 --- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb @@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb index 0f29a95bdcc..9343d52e44b 100644 --- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb @@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb index cf8adb57ffc..8b66a891e69 100644 --- a/spec/lib/banzai/reference_parser/label_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb @@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::LabelParser do let(:user) { create(:user) } let(:label) { create(:label, project: project) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb index 1561dabcdbf..cb65893aea0 100644 --- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb @@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, source_project: project) } subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb index 006f8e37690..25ba41dd8a0 100644 --- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb @@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MilestoneParser do let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#nodes_visible_to_user' do diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb index e4936aa9e57..356dde1e9c2 100644 --- a/spec/lib/banzai/reference_parser/project_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ProjectParser do let(:project) { create(:project, :public) } let(:user) { create(:user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#referenced_by' do diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb index 528f79ed020..05dc1cb4d2d 100644 --- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb @@ -12,6 +12,7 @@ describe Banzai::ReferenceParser::SnippetParser do let(:project_member) { create(:user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } def visible_references(snippet_visibility, user = nil) diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index a5b4e59a3a1..931fb1e3953 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::UserParser do let(:user) { create(:user) } let(:project) { create(:project, :public, group: group, creator: user) } subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } describe '#referenced_by' do diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 3d0d3f91859..7f7a285c453 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -308,8 +308,8 @@ describe Gitlab::BitbucketImport::Importer do importer.execute - expect(project.issues.where(state: "closed").size).to eq(5) - expect(project.issues.where(state: "opened").size).to eq(2) + expect(project.issues.where(state_id: Issue.available_states[:closed]).size).to eq(5) + expect(project.issues.where(state_id: Issue.available_states[:opened]).size).to eq(2) end describe 'wiki import' do diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index 24d17eb0fb3..73c3cad88bc 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -34,27 +34,32 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe '#basename' do subject { |example| path(example).basename } + it { is_expected.to eq 'absolute_path' } end end describe 'path/dir_1/', path: 'path/dir_1/' do subject { |example| path(example) } + it { is_expected.to have_parent } it { is_expected.to be_directory } describe '#basename' do subject { |example| path(example).basename } + it { is_expected.to eq 'dir_1/' } end describe '#name' do subject { |example| path(example).name } + it { is_expected.to eq 'dir_1' } end describe '#parent' do subject { |example| path(example).parent } + it { is_expected.to eq entry('path/') } end @@ -102,21 +107,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe '#nodes' do subject { |example| path(example).nodes } + it { is_expected.to eq 2 } end describe '#exists?' do subject { |example| path(example).exists? } + it { is_expected.to be true } end describe '#empty?' do subject { |example| path(example).empty? } + it { is_expected.to be false } end describe '#total_size' do subject { |example| path(example).total_size } + it { is_expected.to eq(30) } end end @@ -124,10 +133,12 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe 'empty path', path: '' do subject { |example| path(example) } + it { is_expected.not_to have_parent } describe '#children' do subject { |example| path(example).children } + it { expect(subject.count).to eq 3 } end end @@ -135,6 +146,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe 'path/dir_1/subdir/subfile', path: 'path/dir_1/subdir/subfile' do describe '#nodes' do subject { |example| path(example).nodes } + it { is_expected.to eq 4 } end @@ -153,11 +165,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe 'non-existent/', path: 'non-existent/' do describe '#empty?' do subject { |example| path(example).empty? } + it { is_expected.to be true } end describe '#exists?' do subject { |example| path(example).exists? } + it { is_expected.to be false } end end @@ -165,6 +179,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do describe 'another_directory/', path: 'another_directory/' do describe '#empty?' do subject { |example| path(example).empty? } + it { is_expected.to be true } end end diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb index ff189c4701e..bfa65c66b33 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb @@ -76,21 +76,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do describe '#to_entry' do subject { metadata('').to_entry } + it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) } end describe '#full_version' do subject { metadata('').full_version } + it { is_expected.to eq 'GitLab Build Artifacts Metadata 0.0.1' } end describe '#version' do subject { metadata('').version } + it { is_expected.to eq '0.0.1' } end describe '#errors' do subject { metadata('').errors } + it { is_expected.to eq({}) } end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 4cb63168ec7..9aab3664e1c 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -69,6 +69,7 @@ describe Gitlab::Ci::Config::Entry::Cache do context 'when entry value is not correct' do describe '#errors' do subject { entry.errors } + context 'when is not a hash' do let(:config) { 'ls' } diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb index 48d0864cfca..877e3ec6216 100644 --- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb @@ -11,11 +11,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe '#errors' do subject { entry.errors } + it { is_expected.to include(/coverage config must be a regular expression/) } end describe '#valid?' do subject { entry } + it { is_expected.not_to be_valid } end end @@ -25,16 +27,19 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe '#value' do subject { entry.value } + it { is_expected.to eq(config[1...-1]) } end describe '#errors' do subject { entry.errors } + it { is_expected.to be_empty } end describe '#valid?' do subject { entry } + it { is_expected.to be_valid } end end @@ -44,11 +49,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do describe '#errors' do subject { entry.errors } + it { is_expected.to include(/coverage config must be a regular expression/) } end describe '#valid?' do subject { entry } + it { is_expected.not_to be_valid } end end diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 7bad788e44e..5787cce7d20 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Cleanup::ProjectUploads do subject { described_class.new(logger: logger) } + let(:logger) { double(:logger) } before do diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index 1e9cf0017be..bd1c2b10dc8 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -8,6 +8,7 @@ require 'gitlab/danger/teammate' describe Gitlab::Danger::Teammate do subject { described_class.new(options.stringify_keys) } + let(:options) { { username: 'luigi', projects: projects, role: role } } let(:projects) { { project => capabilities } } let(:role) { 'Engineer, Manage' } diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb index 6ef1e41450f..a13727b62ea 100644 --- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb +++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb @@ -40,6 +40,14 @@ describe Gitlab::DiscussionsDiff::FileCollection do subject.load_highlight end + it 'does not write cache for empty mapping' do + allow(subject).to receive(:highlighted_lines_by_ids).and_return([]) + + expect(Gitlab::DiscussionsDiff::HighlightCache).not_to receive(:write_multiple) + + subject.load_highlight + end + it 'does not write cache for resolved notes' do diff_note_a.update_column(:resolved_at, Time.now) diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb index 56ad49d528f..5a5e34961a4 100644 --- a/spec/lib/gitlab/downtime_check_spec.rb +++ b/spec/lib/gitlab/downtime_check_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::DowntimeCheck do subject { described_class.new } + let(:path) { 'foo.rb' } describe '#check' do diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index 84c5b38127e..b57764bceef 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -28,90 +28,107 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#project' do subject { message.project } + it { is_expected.to eq project } it { is_expected.to be_an_instance_of Project } end describe '#project_namespace' do subject { message.project_namespace } + it { is_expected.to eq group } it { is_expected.to be_kind_of Namespace } end describe '#project_name_with_namespace' do subject { message.project_name_with_namespace } + it { is_expected.to eq "#{group.name} / #{project.path}" } end describe '#author' do subject { message.author } + it { is_expected.to eq author } it { is_expected.to be_an_instance_of User } end describe '#author_name' do subject { message.author_name } + it { is_expected.to eq 'Author' } end describe '#commits' do subject { message.commits } + it { is_expected.to be_kind_of Array } it { is_expected.to all(be_instance_of Commit) } end describe '#diffs' do subject { message.diffs } + it { is_expected.to all(be_an_instance_of Gitlab::Diff::File) } end describe '#diffs_count' do subject { message.diffs_count } + it { is_expected.to eq raw_compare.diffs.size } end describe '#compare' do subject { message.compare } + it { is_expected.to be_an_instance_of Compare } end describe '#compare_timeout' do subject { message.compare_timeout } + it { is_expected.to eq raw_compare.diffs.overflow? } end describe '#reverse_compare?' do subject { message.reverse_compare? } + it { is_expected.to eq false } end describe '#disable_diffs?' do subject { message.disable_diffs? } + it { is_expected.to eq false } end describe '#send_from_committer_email?' do subject { message.send_from_committer_email? } + it { is_expected.to eq true } end describe '#action_name' do subject { message.action_name } + it { is_expected.to eq 'pushed to' } end describe '#ref_name' do subject { message.ref_name } + it { is_expected.to eq 'master' } end describe '#ref_type' do subject { message.ref_type } + it { is_expected.to eq 'branch' } end describe '#target_url' do subject { message.target_url } + it { is_expected.to include 'compare' } it { is_expected.to include compare.commits.first.parents.first.id } it { is_expected.to include compare.commits.last.id } @@ -119,6 +136,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#subject' do subject { message.subject } + it { is_expected.to include "[Git][#{project.full_path}]" } it { is_expected.to include "#{compare.commits.length} commits" } it { is_expected.to include compare.commits.first.message.split("\n").first } diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index 4d473731f39..2e5fd16d370 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -75,6 +75,7 @@ describe Gitlab::Experimentation do describe '.enabled?' do subject { described_class.enabled?(:test_experiment, experimentation_subject_index) } + let(:experimentation_subject_index) { 9 } context 'feature toggle is enabled, we are on the right environment and we are selected' do diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb index d24f5c45107..eef3b9de476 100644 --- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb @@ -84,11 +84,13 @@ describe Gitlab::Gfm::UploadsRewriter do describe '#needs_rewrite?' do subject { rewriter.needs_rewrite? } + it { is_expected.to eq true } end describe '#files' do subject { rewriter.files } + it { is_expected.to be_an(Array) } end end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 0764e525ede..02ef7b92538 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -44,6 +44,7 @@ describe Gitlab::Git::Branch, :seed_helper do describe '#size' do subject { super().size } + it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 3f0e6b34291..23651e3d7f2 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -174,6 +174,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#id' do subject { super().id } + it { is_expected.to eq(SeedRepo::LastCommit::ID) } end end @@ -183,6 +184,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#id' do subject { super().id } + it { is_expected.to eq(SeedRepo::Commit::ID) } end end @@ -192,6 +194,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#id' do subject { super().id } + it { is_expected.to eq(SeedRepo::BigCommit::ID) } end end @@ -544,11 +547,13 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#id' do subject { super().id } + it { is_expected.to eq(sample_commit_hash[:id])} end describe '#message' do subject { super().message } + it { is_expected.to eq(sample_commit_hash[:message])} end end @@ -558,16 +563,19 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#additions' do subject { super().additions } + it { is_expected.to eq(11) } end describe '#deletions' do subject { super().deletions } + it { is_expected.to eq(6) } end describe '#total' do subject { super().total } + it { is_expected.to eq(17) } end end @@ -596,6 +604,7 @@ describe Gitlab::Git::Commit, :seed_helper do describe '#keys' do subject { super().keys.sort } + it { is_expected.to match(sample_commit_hash.keys.sort) } end end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index ded173c49ef..ce45d6e24ba 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -10,6 +10,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do expanded: expanded ) end + let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) } let(:file_count) { 0 } let(:line_length) { 1 } @@ -21,6 +22,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#to_a' do subject { super().to_a } + it { is_expected.to be_kind_of ::Array } end @@ -52,16 +54,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('3') } end @@ -76,6 +81,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -84,16 +90,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('3') } end @@ -108,6 +117,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end end @@ -118,21 +128,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_truthy } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('0+') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq 1000 } end @@ -143,21 +157,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('3') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -174,21 +192,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_truthy } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('10+') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq 10 } end @@ -199,21 +221,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('11') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -226,21 +252,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_truthy } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('3+') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq 120 } end @@ -251,21 +281,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('11') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -282,21 +316,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('10') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -310,21 +348,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_truthy } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('9+') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -335,21 +377,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('10') } end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq file_count * line_count } end @@ -363,26 +409,31 @@ describe Gitlab::Git::DiffCollection, :seed_helper do describe '#overflow?' do subject { super().overflow? } + it { is_expected.to be_falsey } end describe '#empty?' do subject { super().empty? } + it { is_expected.to be_truthy } end describe '#size' do subject { super().size } + it { is_expected.to eq(0) } end describe '#real_size' do subject { super().real_size } + it { is_expected.to eq('0')} end describe '#line_count' do subject { super().line_count } + it { is_expected.to eq 0 } end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 04a648a0da0..44c41da7560 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -93,6 +93,7 @@ describe Gitlab::Git::Repository, :seed_helper do describe '#last' do subject { super().last } + it { is_expected.to eq("v1.2.1") } end it { is_expected.to include("v1.0.0") } @@ -215,11 +216,13 @@ describe Gitlab::Git::Repository, :seed_helper do describe '#first' do subject { super().first } + it { is_expected.to eq('feature') } end describe '#last' do subject { super().last } + it { is_expected.to eq('v1.2.1') } end end diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb index 22ad88e28cb..0f1745fcc02 100644 --- a/spec/lib/gitlab/gitlab_import/client_spec.rb +++ b/spec/lib/gitlab/gitlab_import/client_spec.rb @@ -52,6 +52,7 @@ describe Gitlab::GitlabImport::Client do describe '#projects' do subject(:method) { :projects } + let(:args) { [] } let(:element_list) { build_list(:project, 2) } @@ -67,6 +68,7 @@ describe Gitlab::GitlabImport::Client do describe '#issues' do subject(:method) { :issues } + let(:args) { [1] } let(:element_list) { build_list(:issue, 2) } @@ -82,6 +84,7 @@ describe Gitlab::GitlabImport::Client do describe '#issue_comments' do subject(:method) { :issue_comments } + let(:args) { [1, 1] } let(:element_list) { build_list(:note_on_issue, 2) } diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb index 530d4a981bf..09654e0439e 100644 --- a/spec/lib/gitlab/graphs/commits_spec.rb +++ b/spec/lib/gitlab/graphs/commits_spec.rb @@ -11,12 +11,14 @@ describe Gitlab::Graphs::Commits do describe '#commit_per_day' do context 'when range is only commits from today' do subject { described_class.new([commit2, commit1]).commit_per_day } + it { is_expected.to eq 2 } end end context 'when range is only commits from today' do subject { described_class.new([commit2, commit1]) } + describe '#commit_per_day' do it { expect(subject.commit_per_day).to eq 2 } end @@ -28,6 +30,7 @@ describe Gitlab::Graphs::Commits do context 'with commits from yesterday and today' do subject { described_class.new([commit2, commit1_yesterday]) } + describe '#commit_per_day' do it { expect(subject.commit_per_day).to eq 1.0 } end diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb index 99d10312c15..36e2fd04aeb 100644 --- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb +++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb @@ -30,6 +30,7 @@ describe Gitlab::HealthChecks::GitalyCheck do describe '#metrics' do subject { described_class.metrics } + let(:server) { double(storage: 'default', read_writeable?: up) } before do diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb index c3d55a11909..03a7cf249cf 100644 --- a/spec/lib/gitlab/health_checks/simple_check_shared.rb +++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb @@ -1,6 +1,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| describe '#metrics' do subject { described_class.metrics } + context 'Check is passing' do before do allow(described_class).to receive(:check).and_return success_result @@ -34,6 +35,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result| describe '#readiness' do subject { described_class.readiness } + context 'Check returns ok' do before do allow(described_class).to receive(:check).and_return success_result diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index 6013fb78bc7..ebd7feb0055 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::HookData::IssueBuilder do duplicated_to_id project_id relative_position - state + state_id time_estimate title updated_at @@ -41,6 +41,7 @@ describe Gitlab::HookData::IssueBuilder do expect(data).to include(:human_time_estimate) expect(data).to include(:human_total_time_spent) expect(data).to include(:assignee_ids) + expect(data).to include(:state) expect(data).to include('labels' => [label.hook_attrs]) end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 1efd7bf5c71..4fd61383c6b 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -25,6 +25,7 @@ issues: - epic - designs - design_versions +- description_versions - prometheus_alerts - prometheus_alert_events - self_managed_prometheus_alert_events @@ -132,6 +133,7 @@ merge_requests: - blocks_as_blockee - blocking_merge_requests - blocked_merge_requests +- description_versions external_pull_requests: - project merge_request_diff: diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 218031784cb..676973ff5e7 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -434,6 +434,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do labels: 0, milestones: 0, first_issue_labels: 1 + + it 'restores issue states' do + expect(project.issues.with_state(:closed).count).to eq(1) + expect(project.issues.with_state(:opened).count).to eq(1) + end end context 'with existing group models' do diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 51b2fd06b46..a23e68a8f00 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -85,7 +85,7 @@ describe Gitlab::ImportExport::RelationFactory do class FooModel include ActiveModel::Model - def initialize(params) + def initialize(params = {}) params.each { |key, value| send("#{key}=", value) } end diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb index b8add3c1324..1097d26c320 100644 --- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::Metrics::Samplers::PumaSampler do subject { described_class.new(5) } + let(:null_metric) { double('null_metric', set: nil, observe: nil) } before do diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb index b6f2524a9d0..51514dd0ffd 100644 --- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb +++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do subject(:state) { described_class.new('weird-project-id') } + let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' } describe '#add_job' do diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index f6ace0d8bf5..6bc9b6365d1 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -259,6 +259,7 @@ describe Gitlab::ReferenceExtractor do describe '.references_pattern' do subject { described_class.references_pattern } + it { is_expected.to be_kind_of Regexp } end diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb index a744f48da1f..cde12d4b310 100644 --- a/spec/lib/gitlab/request_context_spec.rb +++ b/spec/lib/gitlab/request_context_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Gitlab::RequestContext do describe '#client_ip' do subject { described_class.client_ip } + let(:app) { -> (env) {} } let(:env) { Hash.new } diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index ff9e31ec346..a17e9a31212 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -396,6 +396,7 @@ describe Gitlab::Shell do describe 'namespace actions' do subject { described_class.new } + let(:storage) { Gitlab.config.repositories.storages.keys.first } describe '#add_namespace' do diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb index bf3bc8e1add..b5be43ec96c 100644 --- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Gitlab::SidekiqMiddleware::MemoryKiller do subject { described_class.new } + let(:pid) { 999 } let(:worker) { double(:worker, class: ProjectCacheWorker) } diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb index 5855c4374a9..e2776efac85 100644 --- a/spec/lib/gitlab/utils/override_spec.rb +++ b/spec/lib/gitlab/utils/override_spec.rb @@ -151,6 +151,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is prepending it' do subject { extension } + let(:klass) { prepending_class } it_behaves_like 'checking as intended' @@ -158,6 +159,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is including it' do subject { extension } + let(:klass) { including_class } it_behaves_like 'checking as intended, nothing was overridden' @@ -177,6 +179,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is prepending it' do subject { extension } + let(:klass) { prepending_class } it_behaves_like 'nothing happened' @@ -184,6 +187,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is including it' do subject { extension } + let(:klass) { including_class } it 'does not complain when it is overriding something' do @@ -215,6 +219,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is prepending it' do subject { extension } + let(:klass) { prepending_class_methods } it_behaves_like 'checking as intended' @@ -222,6 +227,7 @@ describe Gitlab::Utils::Override do context 'when subject is a module, and class is extending it' do subject { extension } + let(:klass) { extending_class_methods } it_behaves_like 'checking as intended, nothing was overridden' diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 91b076c31d6..0f7f57095df 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -54,6 +54,7 @@ describe GoogleApi::CloudPlatform::Client do describe '#projects_zones_clusters_get' do subject { client.projects_zones_clusters_get(spy, spy, spy) } + let(:gke_cluster) { double } before do @@ -160,6 +161,7 @@ describe GoogleApi::CloudPlatform::Client do describe '#projects_zones_operations' do subject { client.projects_zones_operations(spy, spy, spy) } + let(:operation) { double } before do diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb index 916d11ce0ed..ca587a6ebcd 100644 --- a/spec/lib/json_web_token/token_spec.rb +++ b/spec/lib/json_web_token/token_spec.rb @@ -16,6 +16,7 @@ describe JSONWebToken::Token do context 'embeds default payload' do subject { token.payload } + let(:default) { token.send(:default_payload) } it { is_expected.to include(default) } diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb index bdf3ea6be98..a8c565aa705 100644 --- a/spec/lib/omni_auth/strategies/jwt_spec.rb +++ b/spec/lib/omni_auth/strategies/jwt_spec.rb @@ -8,6 +8,7 @@ describe OmniAuth::Strategies::Jwt do context '#decoded' do subject { described_class.new({}) } + let(:timestamp) { Time.now.to_i } let(:jwt_config) { Devise.omniauth_configs[:jwt] } let(:claims) do diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb index 2cb4727bd4b..2bbbd67b13c 100644 --- a/spec/lib/uploaded_file_spec.rb +++ b/spec/lib/uploaded_file_spec.rb @@ -72,16 +72,6 @@ describe UploadedFile do end end - context 'when only remote id is specified' do - let(:params) do - { 'file.remote_id' => 'remote_id' } - end - - it "raises an error" do - expect { subject }.to raise_error(UploadedFile::InvalidPathError, /file is invalid/) - end - end - context 'when verifying allowed paths' do let(:params) do { 'file.path' => temp_file.path } @@ -120,6 +110,52 @@ describe UploadedFile do end end + describe '.initialize' do + context 'when no size is provided' do + it 'determine size from local path' do + file = described_class.new(temp_file.path) + + expect(file.size).to eq(temp_file.size) + end + + it 'raises an exception if is a remote file' do + expect do + described_class.new(nil, remote_id: 'id') + end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size') + end + end + + context 'when size is a number' do + let_it_be(:size) { 1.gigabyte } + + it 'is overridden by the size of the local file' do + file = described_class.new(temp_file.path, size: size) + + expect(file.size).to eq(temp_file.size) + end + + it 'is respected if is a remote file' do + file = described_class.new(nil, remote_id: 'id', size: size) + + expect(file.size).to eq(size) + end + end + + context 'when size is a string' do + it 'is converted to a number' do + file = described_class.new(nil, remote_id: 'id', size: '1') + + expect(file.size).to eq(1) + end + + it 'raises an exception if does not represent a number' do + expect do + described_class.new(nil, remote_id: 'id', size: 'not a number') + end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size') + end + end + end + describe '#sanitize_filename' do it { expect(described_class.new(temp_file.path).sanitize_filename('spaced name')).to eq('spaced_name') } it { expect(described_class.new(temp_file.path).sanitize_filename('#$%^&')).to eq('_____') } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index da8ea00b2f6..058305bc04e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -574,6 +574,7 @@ describe Ci::Build do describe '#artifacts_metadata?' do subject { build.artifacts_metadata? } + context 'artifacts metadata does not exist' do it { is_expected.to be_falsy } end @@ -586,6 +587,7 @@ describe Ci::Build do describe '#artifacts_expire_in' do subject { build.artifacts_expire_in } + it { is_expected.to be_nil } context 'when artifacts_expire_at is specified' do @@ -1265,6 +1267,7 @@ describe Ci::Build do describe '#erasable?' do subject { build.erasable? } + it { is_expected.to be_truthy } end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 70ff3cf5dc4..ac438f7d473 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -686,11 +686,13 @@ describe Ci::Runner do describe '#has_tags?' do context 'when runner has tags' do subject { create(:ci_runner, tag_list: ['tag']) } + it { is_expected.to have_tags } end context 'when runner does not have tags' do subject { create(:ci_runner, tag_list: []) } + it { is_expected.not_to have_tags } end end diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb index 929b5f52c7c..f823ac0165f 100644 --- a/spec/models/concerns/noteable_spec.rb +++ b/spec/models/concerns/noteable_spec.rb @@ -6,6 +6,7 @@ describe Noteable do let!(:active_diff_note1) { create(:diff_note_on_merge_request) } let(:project) { active_diff_note1.project } subject { active_diff_note1.noteable } + let!(:active_diff_note2) { create(:diff_note_on_merge_request, project: project, noteable: subject, in_reply_to: active_diff_note1) } let!(:active_diff_note3) { create(:diff_note_on_merge_request, project: project, noteable: subject, position: active_position2) } let!(:outdated_diff_note1) { create(:diff_note_on_merge_request, project: project, noteable: subject, position: outdated_position) } diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 51e28974ae0..43b894b5957 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -17,6 +17,7 @@ describe User, 'TokenAuthenticatable' do describe 'ensures authentication token' do subject { create(:user).send(token_field) } + it { is_expected.to be_a String } end end diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index c137444763b..1dbae78a01d 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -16,6 +16,7 @@ describe DeployKeysProject do describe "Destroying" do let(:project) { create(:project) } subject { create(:deploy_keys_project, project: project) } + let(:deploy_key) { subject.deploy_key } context "when the deploy key is only used by this project" do diff --git a/spec/models/description_version_spec.rb b/spec/models/description_version_spec.rb new file mode 100644 index 00000000000..5ec34c0cde4 --- /dev/null +++ b/spec/models/description_version_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DescriptionVersion do + describe 'associations' do + it { is_expected.to belong_to :issue } + it { is_expected.to belong_to :merge_request } + end + + describe 'validations' do + describe 'exactly_one_issuable' do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(issue_id: issue_id, merge_request_id: merge_request_id).valid? } + + where(:issue_id, :merge_request_id, :valid?) do + nil | 1 | true + 1 | nil | true + nil | nil | false + 1 | 1 | false + end + + with_them do + it { is_expected.to eq(valid?) } + end + end + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 62663c247d1..ff2e1aa047e 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -100,26 +100,31 @@ describe Event do describe '#membership_changed?' do context "created" do subject { build(:event, :created).membership_changed? } + it { is_expected.to be_falsey } end context "updated" do subject { build(:event, :updated).membership_changed? } + it { is_expected.to be_falsey } end context "expired" do subject { build(:event, :expired).membership_changed? } + it { is_expected.to be_truthy } end context "left" do subject { build(:event, :left).membership_changed? } + it { is_expected.to be_truthy } end context "joined" do subject { build(:event, :joined).membership_changed? } + it { is_expected.to be_truthy } end end diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index dd18c8842ab..a780b8bfdf5 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -20,6 +20,7 @@ RSpec.describe GpgSignature do describe 'validation' do subject { described_class.new } + it { is_expected.to validate_presence_of(:commit_sha) } it { is_expected.to validate_presence_of(:project_id) } it { is_expected.to validate_presence_of(:gpg_key_primary_keyid) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 9c58d307c4c..18a1a30eee5 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -138,7 +138,10 @@ describe Issue do end it 'changes the state to closed' do - expect { issue.close }.to change { issue.state }.from('opened').to('closed') + open_state = described_class.available_states[:opened] + closed_state = described_class.available_states[:closed] + + expect { issue.close }.to change { issue.state_id }.from(open_state).to(closed_state) end end @@ -155,7 +158,7 @@ describe Issue do end it 'changes the state to opened' do - expect { issue.reopen }.to change { issue.state }.from('closed').to('opened') + expect { issue.reopen }.to change { issue.state_id }.from(described_class.available_states[:closed]).to(described_class.available_states[:opened]) end end @@ -277,6 +280,7 @@ describe Issue do context 'checking destination project also' do subject { issue.can_move?(user, to_project) } + let(:to_project) { create(:project) } context 'destination project allowed' do @@ -899,4 +903,6 @@ describe Issue do let(:default_params) { { project: project } } end end + + it_behaves_like 'versioned description' end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b146c767f82..ad79bee8801 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -470,7 +470,7 @@ describe MergeRequest do commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") allow(subject).to receive(:commits).and_return([commit]) - allow(subject).to receive(:state).and_return("closed") + allow(subject).to receive(:state_id).and_return(described_class.available_states[:closed]) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) end @@ -479,7 +479,7 @@ describe MergeRequest do issue = create :issue, project: subject.project commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") allow(subject).to receive(:commits).and_return([commit]) - allow(subject).to receive(:state).and_return("merged") + allow(subject).to receive(:state_id).and_return(described_class.available_states[:merged]) expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count) end @@ -541,6 +541,7 @@ describe MergeRequest do context 'with diffs' do subject { create(:merge_request, :with_diffs) } + it 'returns the sha of the source branch last commit' do expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end @@ -548,6 +549,7 @@ describe MergeRequest do context 'without diffs' do subject { create(:merge_request, :without_diffs) } + it 'returns the sha of the source branch last commit' do expect(subject.source_branch_sha).to eq(last_branch_commit.sha) end @@ -570,6 +572,7 @@ describe MergeRequest do context 'when the merge request is being created' do subject { build(:merge_request, source_branch: nil, compare_commits: []) } + it 'returns nil' do expect(subject.source_branch_sha).to be_nil end @@ -2072,7 +2075,7 @@ describe MergeRequest do end it 'refuses to enqueue a job if the MR is not open' do - merge_request.update_column(:state, 'foo') + merge_request.update_column(:state_id, 5) expect(RebaseWorker).not_to receive(:perform_async) @@ -2495,6 +2498,7 @@ describe MergeRequest do describe "#diff_refs" do context "with diffs" do subject { create(:merge_request, :with_diffs) } + let(:expected_diff_refs) do Gitlab::Diff::DiffRefs.new( base_sha: subject.merge_request_diff.base_commit_sha, @@ -2567,32 +2571,32 @@ describe MergeRequest do describe '#merge_ongoing?' do it 'returns true when the merge request is locked' do - merge_request = build_stubbed(:merge_request, state: :locked) + merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:locked]) expect(merge_request.merge_ongoing?).to be(true) end it 'returns true when merge_id, MR is not merged and it has no running job' do - merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') + merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo') allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true } expect(merge_request.merge_ongoing?).to be(true) end it 'returns false when merge_jid is nil' do - merge_request = build_stubbed(:merge_request, state: :open, merge_jid: nil) + merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: nil) expect(merge_request.merge_ongoing?).to be(false) end it 'returns false if MR is merged' do - merge_request = build_stubbed(:merge_request, state: :merged, merge_jid: 'foo') + merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:merged], merge_jid: 'foo') expect(merge_request.merge_ongoing?).to be(false) end it 'returns false if there is no merge job running' do - merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') + merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo') allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false } expect(merge_request.merge_ongoing?).to be(false) @@ -2726,7 +2730,7 @@ describe MergeRequest do context 'closed MR' do before do - merge_request.update_attribute(:state, :closed) + merge_request.update_attribute(:state_id, described_class.available_states[:closed]) end it 'is not mergeable' do @@ -2840,6 +2844,7 @@ describe MergeRequest do describe '#merge_request_diff_for' do subject { create(:merge_request, importing: true) } + let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) } let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') } @@ -2870,6 +2875,7 @@ describe MergeRequest do describe '#version_params_for' do subject { create(:merge_request, importing: true) } + let(:project) { subject.project } let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) } @@ -3331,4 +3337,6 @@ describe MergeRequest do it { expect(query).to contain_exactly(merge_request1, merge_request2) } end + + it_behaves_like 'versioned description' end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 8a47b8c206b..4c320b4b145 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -55,11 +55,13 @@ describe Note do context 'when noteable and note project are the same' do subject { create(:note) } + it { is_expected.to be_valid } end context 'when project is missing for a project related note' do subject { build(:note, project: nil, noteable: build_stubbed(:issue)) } + it { is_expected.to be_invalid } end @@ -741,6 +743,7 @@ describe Note do describe '#to_discussion' do subject { create(:discussion_note_on_merge_request) } + let!(:note2) { create(:discussion_note_on_merge_request, project: subject.project, noteable: subject.noteable, in_reply_to: subject) } it "returns a discussion with just this note" do @@ -808,6 +811,7 @@ describe Note do context 'for a note' do context 'when part of a discussion' do subject { create(:discussion_note_on_issue) } + let(:note) { create(:discussion_note_on_issue, in_reply_to: subject) } it 'checks if the note is in reply to the other discussion' do @@ -821,6 +825,7 @@ describe Note do context 'when not part of a discussion' do subject { create(:note) } + let(:note) { create(:note, in_reply_to: subject) } it 'checks if the note is in reply to the other noteable' do @@ -835,6 +840,7 @@ describe Note do context 'for a discussion' do context 'when part of the same discussion' do subject { create(:diff_note_on_merge_request) } + let(:note) { create(:diff_note_on_merge_request, in_reply_to: subject) } it 'returns true' do @@ -844,6 +850,7 @@ describe Note do context 'when not part of the same discussion' do subject { create(:diff_note_on_merge_request) } + let(:note) { create(:diff_note_on_merge_request) } it 'returns false' do @@ -855,6 +862,7 @@ describe Note do context 'for a noteable' do context 'when a comment on the same noteable' do subject { create(:note) } + let(:note) { create(:note, in_reply_to: subject) } it 'returns true' do @@ -864,6 +872,7 @@ describe Note do context 'when not a comment on the same noteable' do subject { create(:note) } + let(:note) { create(:note) } it 'returns false' do @@ -887,6 +896,7 @@ describe Note do context 'when not part of a discussion' do subject { create(:note) } + let(:note) { create(:note, in_reply_to: subject) } it 'returns the noteable' do diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 2e7b2b88432..4b65bf032d1 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -293,11 +293,13 @@ describe PagesDomain do describe "#https?" do context "when a certificate is present" do subject { build(:pages_domain) } + it { is_expected.to be_https } end context "when no certificate is present" do subject { build(:pages_domain, :without_certificate) } + it { is_expected.not_to be_https } end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a5048d78363..9f3313e67b5 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5014,6 +5014,7 @@ describe Project do describe '#git_objects_poolable?' do subject { project } + context 'when not using hashed storage' do let(:project) { create(:project, :legacy_storage, :public, :repository) } diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index b089544c810..31d1d1fd7d1 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -109,6 +109,7 @@ describe ProjectWiki do context "when the wiki repository is empty" do describe '#empty?' do subject { super().empty? } + it { is_expected.to be_truthy } end end @@ -121,6 +122,7 @@ describe ProjectWiki do describe '#empty?' do subject { super().empty? } + it { is_expected.to be_falsey } it 'only instantiates a Wiki page once' do diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index f4023dcb95a..f51041c9ddc 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe ResourceLabelEvent, type: :model do subject { build(:resource_label_event, issue: issue) } + let(:issue) { create(:issue) } let(:merge_request) { create(:merge_request) } diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb index bcd3c03f947..801f139355b 100644 --- a/spec/models/system_note_metadata_spec.rb +++ b/spec/models/system_note_metadata_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe SystemNoteMetadata do describe 'associations' do it { is_expected.to belong_to(:note) } + it { is_expected.to belong_to(:description_version) } end describe 'validation' do diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index 28fc82f2a32..7321a458817 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Timelog do subject { build(:timelog) } + let(:issue) { create(:issue) } let(:merge_request) { create(:merge_request) } diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb index 47d919c1d12..b96ff08e22d 100644 --- a/spec/models/user_interacted_project_spec.rb +++ b/spec/models/user_interacted_project_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe UserInteractedProject do describe '.track' do subject { described_class.track(event) } + let(:event) { build(:event) } Event::ACTIONS.each do |action| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 24e66fe14c3..240c917e7cf 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2170,6 +2170,7 @@ describe User do describe "#contributed_projects" do subject { create(:user) } + let!(:project1) { create(:project) } let!(:project2) { fork_project(project3) } let!(:project3) { create(:project) } diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb index 2520469d4e7..52b6d2c89ba 100644 --- a/spec/policies/identity_provider_policy_spec.rb +++ b/spec/policies/identity_provider_policy_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe IdentityProviderPolicy do subject(:policy) { described_class.new(user, provider) } + let(:user) { User.new } let(:provider) { :a_provider } diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 87205f56589..af4c9703eb4 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -53,21 +53,25 @@ describe MergeRequestPolicy do describe 'the author' do subject { author } + it_behaves_like 'a denied user' end describe 'a guest' do subject { guest } + it_behaves_like 'a denied user' end describe 'a developer' do subject { developer } + it_behaves_like 'a denied user' end describe 'any other user' do subject { non_team_member } + it_behaves_like 'a denied user' end end @@ -82,11 +86,13 @@ describe MergeRequestPolicy do describe 'a non-team-member' do subject { non_team_member } + it_behaves_like 'a denied user' end describe 'a developer' do subject { developer } + it_behaves_like 'a user with access' end end diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb index 81eb05a9a6b..b8b68a676e6 100644 --- a/spec/presenters/conversational_development_index/metric_presenter_spec.rb +++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe ConversationalDevelopmentIndex::MetricPresenter do subject { described_class.new(metric) } + let(:metric) { build(:conversational_development_index_metric) } describe '#cards' do diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 1be8883bd3c..6cb02ba2f6b 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -125,25 +125,55 @@ describe API::CommitStatuses do let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } context 'developer user' do - %w[pending running success failed canceled].each do |status| - context "for #{status}" do - context 'uses only required parameters' do - it 'creates commit status' do - post api(post_url, developer), params: { state: status } + context 'uses only required parameters' do + %w[pending running success failed canceled].each do |status| + context "for #{status}" do + context 'when pipeline for sha does not exists' do + it 'creates commit status' do + post api(post_url, developer), params: { state: status } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq(status) + expect(json_response['name']).to eq('default') + expect(json_response['ref']).not_to be_empty + expect(json_response['target_url']).to be_nil + expect(json_response['description']).to be_nil + + if status == 'failed' + expect(CommitStatus.find(json_response['id'])).to be_api_failure + end + end + end + end + end + + context 'when pipeline already exists for the specified sha' do + let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } + let(:params) { { state: 'pending' } } + + shared_examples_for 'creates a commit status for the existing pipeline' do + it do + expect do + post api(post_url, developer), params: params + end.not_to change { Ci::Pipeline.count } + + job = pipeline.statuses.find_by_name(json_response['name']) expect(response).to have_gitlab_http_status(201) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq(status) - expect(json_response['name']).to eq('default') - expect(json_response['ref']).not_to be_empty - expect(json_response['target_url']).to be_nil - expect(json_response['description']).to be_nil - - if status == 'failed' - expect(CommitStatus.find(json_response['id'])).to be_api_failure - end + expect(job.status).to eq('pending') end end + + it_behaves_like 'creates a commit status for the existing pipeline' + + context 'with pipeline for merge request' do + let!(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } + let!(:pipeline) { merge_request.all_pipelines.last } + let(:sha) { pipeline.sha } + + it_behaves_like 'creates a commit status for the existing pipeline' + end end end diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb index 815e9531ecf..2a95b99572f 100644 --- a/spec/requests/api/graphql/namespace/projects_spec.rb +++ b/spec/requests/api/graphql/namespace/projects_spec.rb @@ -67,6 +67,7 @@ describe 'getting projects' do context 'when the namespace is a user' do subject { user.namespace } + let(:include_subgroups) { false } it_behaves_like 'a graphql namespace' diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb index c41eabe0a48..28abe1a8456 100644 --- a/spec/requests/api/pages/internal_access_spec.rb +++ b/spec/requests/api/pages/internal_access_spec.rb @@ -27,6 +27,7 @@ describe "Internal Project Pages Access" do describe "Project should be internal" do describe '#internal?' do subject { project.internal? } + it { is_expected.to be_truthy } end end diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb index c647537038e..6af441caf74 100644 --- a/spec/requests/api/pages/private_access_spec.rb +++ b/spec/requests/api/pages/private_access_spec.rb @@ -27,6 +27,7 @@ describe "Private Project Pages Access" do describe "Project should be private" do describe '#private?' do subject { project.private? } + it { is_expected.to be_truthy } end end diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb index 16cc5697f30..d99224eca5b 100644 --- a/spec/requests/api/pages/public_access_spec.rb +++ b/spec/requests/api/pages/public_access_spec.rb @@ -27,6 +27,7 @@ describe "Public Project Pages Access" do describe "Project should be public" do describe '#public?' do subject { project.public? } + it { is_expected.to be_truthy } end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index bc3a04420f9..70a95663aea 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::Runner, :clean_gitlab_redis_shared_state do include StubGitlabCalls include RedisHelpers + include WorkhorseHelpers let(:registration_token) { 'abcdefg123456' } @@ -1395,7 +1396,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path) + expect(json_response).not_to have_key('TempPath') expect(json_response['RemoteObject']).to have_key('ID') expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') @@ -1562,15 +1563,16 @@ describe API::Runner, :clean_gitlab_redis_shared_state do let!(:fog_connection) do stub_artifacts_object_storage(direct_upload: true) end - - before do + let(:object) do fog_connection.directories.new(key: 'artifacts').files.create( key: 'tmp/uploads/12312300', body: 'content' ) + end + let(:file_upload) { fog_to_uploaded_file(object) } - upload_artifacts(file_upload, headers_with_token, - { 'file.remote_id' => remote_id }) + before do + upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => remote_id) end context 'when valid remote_id is used' do @@ -1804,12 +1806,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end def upload_artifacts(file, headers = {}, params = {}) - params = params.merge({ - 'file.path' => file.path, - 'file.name' => file.original_filename - }) - - post api("/jobs/#{job.id}/artifacts"), params: params, headers: headers + workhorse_finalize( + api("/jobs/#{job.id}/artifacts"), + method: :post, + file_key: :file, + params: params.merge(file: file), + headers: headers + ) end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index ae34f7d1f87..62b9ee1d361 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe 'Git LFS API and storage' do include LfsHttpHelpers include ProjectForksHelper + include WorkhorseHelpers set(:project) { create(:project, :repository) } set(:other_project) { create(:project, :repository) } @@ -933,7 +934,7 @@ describe 'Git LFS API and storage' do it_behaves_like 'a valid response' do it 'responds with status 200, location of LFS remote store and object details' do - expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) + expect(json_response).not_to have_key('TempPath') expect(json_response['RemoteObject']).to have_key('ID') expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') @@ -992,10 +993,17 @@ describe 'Git LFS API and storage' do stub_lfs_object_storage(direct_upload: true) end + let(:tmp_object) do + fog_connection.directories.new(key: 'lfs-objects').files.create( + key: 'tmp/uploads/12312300', + body: 'content' + ) + end + ['123123', '../../123123'].each do |remote_id| context "with invalid remote_id: #{remote_id}" do subject do - put_finalize(with_tempfile: true, args: { + put_finalize(remote_object: tmp_object, args: { 'file.remote_id' => remote_id }) end @@ -1009,15 +1017,8 @@ describe 'Git LFS API and storage' do end context 'with valid remote_id' do - before do - fog_connection.directories.new(key: 'lfs-objects').files.create( - key: 'tmp/uploads/12312300', - body: 'content' - ) - end - subject do - put_finalize(with_tempfile: true, args: { + put_finalize(remote_object: tmp_object, args: { 'file.remote_id' => '12312300', 'file.name' => 'name' }) @@ -1027,6 +1028,10 @@ describe 'Git LFS API and storage' do subject expect(response).to have_gitlab_http_status(200) + + object = LfsObject.find_by_oid(sample_oid) + expect(object).to be_present + expect(object.file.read).to eq(tmp_object.body) end it 'schedules migration of file to object storage' do @@ -1268,28 +1273,31 @@ describe 'Git LFS API and storage' do put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers end - def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, args: {}) - upload_path = LfsObjectUploader.workhorse_local_upload_path - file_path = upload_path + '/' + lfs_tmp if lfs_tmp + def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {}) + uploaded_file = nil if with_tempfile + upload_path = LfsObjectUploader.workhorse_local_upload_path + file_path = upload_path + '/' + lfs_tmp if lfs_tmp + FileUtils.mkdir_p(upload_path) FileUtils.touch(file_path) - end - - extra_args = { - 'file.path' => file_path, - 'file.name' => File.basename(file_path) - } - put_finalize_with_args(args.merge(extra_args).compact, verified: verified) - end + uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path)) + elsif remote_object + uploaded_file = fog_to_uploaded_file(remote_object) + end - def put_finalize_with_args(args, verified:) finalize_headers = headers finalize_headers.merge!(workhorse_internal_api_request_header) if verified - put objects_url(project, sample_oid, sample_size), params: args, headers: finalize_headers + workhorse_finalize( + objects_url(project, sample_oid, sample_size), + method: :put, + file_key: :file, + params: args.merge(file: uploaded_file), + headers: finalize_headers + ) end def lfs_tmp_file diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb index fae0177d5f5..33f1bb85af8 100644 --- a/spec/rubocop/cop/migration/add_timestamps_spec.rb +++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb @@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do include CopHelper subject(:cop) { described_class.new } + let(:migration_with_add_timestamps) do %q( class Users < ActiveRecord::Migration[4.2] diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb index 1812818692a..cafe255dc9a 100644 --- a/spec/rubocop/cop/migration/timestamps_spec.rb +++ b/spec/rubocop/cop/migration/timestamps_spec.rb @@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::Timestamps do include CopHelper subject(:cop) { described_class.new } + let(:migration_with_timestamps) do %q( class Users < ActiveRecord::Migration[4.2] diff --git a/spec/rubocop/cop/scalability/file_uploads_spec.rb b/spec/rubocop/cop/scalability/file_uploads_spec.rb index 2a94fde5ba2..a35d423581c 100644 --- a/spec/rubocop/cop/scalability/file_uploads_spec.rb +++ b/spec/rubocop/cop/scalability/file_uploads_spec.rb @@ -10,6 +10,7 @@ describe RuboCop::Cop::Scalability::FileUploads do include ExpectOffense subject(:cop) { described_class.new } + let(:message) { 'Do not upload files without workhorse acceleration. Please refer to https://docs.gitlab.com/ee/development/uploads.html' } context 'with required params' do diff --git a/spec/serializers/cluster_basic_entity_spec.rb b/spec/serializers/cluster_basic_entity_spec.rb index ab5d54fca16..be03ee91784 100644 --- a/spec/serializers/cluster_basic_entity_spec.rb +++ b/spec/serializers/cluster_basic_entity_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe ClusterBasicEntity do describe '#as_json' do subject { described_class.new(cluster, request: request).as_json } + let(:maintainer) { create(:user) } let(:developer) { create(:user) } let(:current_user) { maintainer } diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 9762c83ed6a..ce5264ec8bb 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -139,6 +139,7 @@ describe PipelineSerializer do describe 'number of queries when preloaded' do subject { serializer.represent(resource, preload: true) } + let(:resource) { Ci::Pipeline.all } before do diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb index c97d4d38b1c..abb5b9b130b 100644 --- a/spec/services/git/tag_hooks_service_spec.rb +++ b/spec/services/git/tag_hooks_service_spec.rb @@ -58,6 +58,7 @@ describe Git::TagHooksService, :service do describe 'Push data' do shared_examples_for 'tag push data expectations' do subject(:push_data) { service.send(:push_data) } + it 'has expected push data attributes' do is_expected.to match a_hash_including( object_kind: 'tag_push', diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index 07e0218e1df..51a5c51f6c3 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -13,6 +13,7 @@ describe MergeRequests::CreateFromIssueService do let(:custom_source_branch) { 'custom-source-branch' } subject(:service) { described_class.new(project, user, service_params) } + subject(:service_with_custom_source_branch) { described_class.new(project, user, branch_name: custom_source_branch, **service_params) } before do diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb index e59731207a5..aa4e41f4d8c 100644 --- a/spec/services/note_summary_spec.rb +++ b/spec/services/note_summary_spec.rb @@ -46,5 +46,17 @@ describe NoteSummary do it 'returns metadata hash' do expect(create_note_summary.metadata).to eq(action: 'icon', commit_count: 5) end + + context 'description action and noteable has saved_description_version' do + before do + noteable.saved_description_version = 1 + end + + subject { described_class.new(noteable, project, user, 'note', action: 'description') } + + it 'sets the description_version metadata' do + expect(subject.metadata).to include(description_version: 1) + end + end end end diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index f651db70cbd..c99054d9fd5 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Projects::HousekeepingService do subject { described_class.new(project) } + set(:project) { create(:project, :repository) } before do diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb index a914b34cb23..5023abad4cd 100644 --- a/spec/services/system_notes/issuables_service_spec.rb +++ b/spec/services/system_notes/issuables_service_spec.rb @@ -212,6 +212,15 @@ describe ::SystemNotes::IssuablesService do it 'sets the note text' do expect(subject.note).to eq('changed the description') end + + it 'associates the related description version' do + noteable.update!(description: 'New description') + + description_version_id = subject.system_note_metadata.description_version_id + + expect(description_version_id).not_to be_nil + expect(description_version_id).to eq(noteable.saved_description_version.id) + end end end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index da743e586f5..e74dbca4f93 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -319,10 +319,10 @@ module KubernetesHelpers } end - def kube_knative_services_body(**options) + def kube_knative_services_body(legacy_knative: false, **options) { "kind" => "List", - "items" => [kube_service(options)] + "items" => [legacy_knative ? knative_05_service(options) : kube_service(options)] } end @@ -421,6 +421,27 @@ module KubernetesHelpers } end + def knative_05_service(name: "kubetest", namespace: "default", domain: "example.com") + { + "metadata" => { + "creationTimestamp" => "2018-11-21T06:16:33Z", + "name" => name, + "namespace" => namespace, + "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}" + }, + "spec" => { + "generation" => 2 + }, + "status" => { + "domain" => "#{name}.#{namespace}.#{domain}", + "domainInternal" => "#{name}.#{namespace}.svc.cluster.local", + "latestCreatedRevisionName" => "#{name}-00002", + "latestReadyRevisionName" => "#{name}-00002", + "observedGeneration" => 2 + } + } + end + def kube_service_full(name: "kubetest", namespace: "kube-ns", domain: "example.com") { "metadata" => { diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb index 40007a14b85..e0fba191deb 100644 --- a/spec/support/helpers/workhorse_helpers.rb +++ b/spec/support/helpers/workhorse_helpers.rb @@ -22,16 +22,40 @@ module WorkhorseHelpers # workhorse_post_with_file will transform file_key inside params as if it was disk accelerated by workhorse def workhorse_post_with_file(url, file_key:, params:) + workhorse_request_with_file(:post, url, + file_key: file_key, + params: params, + env: { 'CONTENT_TYPE' => 'multipart/form-data' }, + send_rewritten_field: true + ) + end + + # workhorse_finalize will transform file_key inside params as if it was the finalize call of an inline object storage upload. + # note that based on the content of the params it can simulate a disc acceleration or an object storage upload + def workhorse_finalize(url, method: :post, file_key:, params:, headers: {}) + workhorse_request_with_file(method, url, + file_key: file_key, + params: params, + extra_headers: headers, + send_rewritten_field: false + ) + end + + def workhorse_request_with_file(method, url, file_key:, params:, env: {}, extra_headers: {}, send_rewritten_field:) workhorse_params = params.dup file = workhorse_params.delete(file_key) - workhorse_params.merge!(workhorse_disk_accelerated_file_params(file_key, file)) + workhorse_params = workhorse_disk_accelerated_file_params(file_key, file).merge(workhorse_params) + + headers = if send_rewritten_field + workhorse_rewritten_fields_header(file_key => file.path) + else + {} + end + + headers.merge!(extra_headers) - post(url, - params: workhorse_params, - headers: workhorse_rewritten_fields_header(file_key => file.path), - env: { 'CONTENT_TYPE' => 'multipart/form-data' } - ) + process(method, url, params: workhorse_params, headers: headers, env: env) end private @@ -45,9 +69,24 @@ module WorkhorseHelpers end def workhorse_disk_accelerated_file_params(key, file) + return {} unless file + { "#{key}.name" => file.original_filename, - "#{key}.path" => file.path - } + "#{key}.size" => file.size + }.tap do |params| + params["#{key}.path"] = file.path if file.path + params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id + end + end + + def fog_to_uploaded_file(file) + filename = File.basename(file.key) + + UploadedFile.new(nil, + filename: filename, + remote_id: filename, + size: file.content_length + ) end end diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index 7e47cdae866..97a23f02b3e 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -90,6 +90,7 @@ RSpec.shared_examples "redis_shared_examples" do describe '._raw_config' do subject { described_class._raw_config } + let(:config_file_name) { '/var/empty/doesnotexist' } it 'is frozen' do diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 0b4ab9941fc..c24418b2f90 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -338,7 +338,7 @@ shared_examples 'handle uploads authorize' do it_behaves_like 'a valid response' do it 'responds with status 200, location of uploads remote store and object details' do - expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path) + expect(json_response).not_to have_key('TempPath') expect(json_response['RemoteObject']).to have_key('ID') expect(json_response['RemoteObject']).to have_key('GetURL') expect(json_response['RemoteObject']).to have_key('StoreURL') diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index f95b612eb70..a6653f89377 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -111,6 +111,7 @@ shared_examples 'cluster application status specs' do |application_name| describe '#make_errored' do subject { create(application_name, :installing) } + let(:reason) { 'some errors' } it 'is errored' do diff --git a/spec/support/shared_examples/versioned_description_shared_examples.rb b/spec/support/shared_examples/versioned_description_shared_examples.rb new file mode 100644 index 00000000000..59124af19ec --- /dev/null +++ b/spec/support/shared_examples/versioned_description_shared_examples.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'versioned description' do + describe 'associations' do + it { is_expected.to have_many(:description_versions) } + end + + describe 'save_description_version' do + let(:factory_name) { described_class.name.underscore.to_sym } + let!(:model) { create(factory_name, description: 'Original description') } + + context 'when feature is enabled' do + before do + stub_feature_flags(save_description_versions: true) + end + + context 'when description was changed' do + before do + model.update!(description: 'New description') + end + + it 'saves the old and new description for the first update' do + expect(model.description_versions.first.description).to eq('Original description') + expect(model.description_versions.last.description).to eq('New description') + end + + it 'only saves the new description for subsequent updates' do + expect { model.update!(description: 'Another description') }.to change { model.description_versions.count }.by(1) + + expect(model.description_versions.last.description).to eq('Another description') + end + + it 'sets the new description version to `saved_description_version`' do + expect(model.saved_description_version).to eq(model.description_versions.last) + end + + it 'clears `saved_description_version` after another save that does not change description' do + model.save! + + expect(model.saved_description_version).to be_nil + end + end + + context 'when description was not changed' do + it 'does not save any description version' do + expect { model.save! }.not_to change { model.description_versions.count } + + expect(model.saved_description_version).to be_nil + end + end + end + + context 'when feature is disabled' do + before do + stub_feature_flags(save_description_versions: false) + end + + it 'does not save any description version' do + expect { model.update!(description: 'New description') }.not_to change { model.description_versions.count } + + expect(model.saved_description_version).to be_nil + end + end + end +end diff --git a/spec/tasks/gitlab/check_rake_spec.rb b/spec/tasks/gitlab/check_rake_spec.rb index 1469143d2ac..b3c8ca03aec 100644 --- a/spec/tasks/gitlab/check_rake_spec.rb +++ b/spec/tasks/gitlab/check_rake_spec.rb @@ -19,6 +19,7 @@ describe 'check.rake' do describe 'gitlab:check rake task' do subject { run_rake_task('gitlab:check') } + let(:name) { 'GitLab subtasks' } it_behaves_like 'system check rake task' @@ -26,6 +27,7 @@ describe 'check.rake' do describe 'gitlab:gitlab_shell:check rake task' do subject { run_rake_task('gitlab:gitlab_shell:check') } + let(:name) { 'GitLab Shell' } it_behaves_like 'system check rake task' @@ -33,6 +35,7 @@ describe 'check.rake' do describe 'gitlab:gitaly:check rake task' do subject { run_rake_task('gitlab:gitaly:check') } + let(:name) { 'Gitaly' } it_behaves_like 'system check rake task' @@ -40,6 +43,7 @@ describe 'check.rake' do describe 'gitlab:sidekiq:check rake task' do subject { run_rake_task('gitlab:sidekiq:check') } + let(:name) { 'Sidekiq' } it_behaves_like 'system check rake task' @@ -47,6 +51,7 @@ describe 'check.rake' do describe 'gitlab:incoming_email:check rake task' do subject { run_rake_task('gitlab:incoming_email:check') } + let(:name) { 'Incoming Email' } it_behaves_like 'system check rake task' @@ -56,6 +61,7 @@ describe 'check.rake' do include LdapHelpers subject { run_rake_task('gitlab:ldap:check') } + let(:name) { 'LDAP' } it_behaves_like 'system check rake task' diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 8fddd8540ef..b7ba4d61723 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -35,4 +35,32 @@ describe 'Every Sidekiq worker' do expect(config_queues).to include(queue).or(include(queue_namespace)) end end + + describe "feature category declarations" do + let(:feature_categories) do + YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:to_sym).to_set + end + + # All Sidekiq worker classes should declare a valid `feature_category` + # or explicitely be excluded with the `feature_category_not_owned!` annotation. + # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details. + it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do + Gitlab::SidekiqConfig.workers.each do |worker| + expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!" + end + end + + # All Sidekiq worker classes should declare a valid `feature_category`. + # The category should match a value in `config/feature_categories.yml`. + # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details. + it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do + workers_with_feature_categories = Gitlab::SidekiqConfig.workers + .select(&:get_feature_category) + .reject(&:feature_category_not_owned?) + + workers_with_feature_categories.each do |worker| + expect(feature_categories).to include(worker.get_feature_category), "expected #{worker.inspect} to declare a valid feature_category, but got #{worker.get_feature_category}" + end + end + end end diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb index a318cdd003e..12c1a26104e 100644 --- a/spec/workers/hashed_storage/migrator_worker_spec.rb +++ b/spec/workers/hashed_storage/migrator_worker_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe HashedStorage::MigratorWorker do subject(:worker) { described_class.new } + let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) } let(:ids) { projects.map(&:id) } diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb index 4055f380978..5fcb1adf9ae 100644 --- a/spec/workers/hashed_storage/rollbacker_worker_spec.rb +++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe HashedStorage::RollbackerWorker do subject(:worker) { described_class.new } + let(:projects) { create_list(:project, 2, :empty_repo) } let(:ids) { projects.map(&:id) } diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 259148fa18f..259148fa18f 100755..100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore index a1c2a238a96..a1c2a238a96 100755..100644 --- a/vendor/gitignore/Java.gitignore +++ b/vendor/gitignore/Java.gitignore |