Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
82f5f2485b
commit
0ec841b7f9
|
@ -22,17 +22,17 @@ rules:
|
|||
- allow:
|
||||
- __
|
||||
- _links
|
||||
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
|
||||
no-else-return:
|
||||
- error
|
||||
- allowElseIf: true
|
||||
import/no-unresolved:
|
||||
- error
|
||||
- ignore:
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/38226
|
||||
- '^ee_component/'
|
||||
import/no-useless-path-segments: off
|
||||
import/order: off
|
||||
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
|
||||
no-else-return:
|
||||
- error
|
||||
- allowElseIf: true
|
||||
import/no-useless-path-segments: off
|
||||
lines-between-class-members: off
|
||||
# Disabled for now, to make the plugin-vue 4.5 -> 5.0 update smoother
|
||||
vue/no-confusing-v-for-v-if: error
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-else-return, consistent-return, one-var, no-return-assign */
|
||||
/* eslint-disable func-names, consistent-return, one-var, no-return-assign */
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
@ -201,9 +201,8 @@ export default class ImageFile {
|
|||
if (domImg) {
|
||||
if (domImg.complete) {
|
||||
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
|
||||
} else {
|
||||
return img.on('load', () => callback.call(this, domImg.naturalWidth, domImg.naturalHeight));
|
||||
}
|
||||
return img.on('load', () => callback.call(this, domImg.naturalWidth, domImg.naturalHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-else-return */
|
||||
/* eslint-disable func-names */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { __ } from './locale';
|
||||
|
@ -52,9 +52,8 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
|
|||
return $('<li />')
|
||||
.addClass('dropdown-header')
|
||||
.text(ref.header);
|
||||
} else {
|
||||
return $('<li />').append(link);
|
||||
}
|
||||
return $('<li />').append(link);
|
||||
},
|
||||
id(obj, $el) {
|
||||
return $el.attr('data-ref');
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-else-return, no-lonely-if */
|
||||
/* global CommentsStore */
|
||||
|
||||
import $ from 'jquery';
|
||||
|
@ -22,27 +21,19 @@ const CommentAndResolveBtn = Vue.extend({
|
|||
showButton() {
|
||||
if (this.discussion) {
|
||||
return this.discussion.isResolvable();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isDiscussionResolved() {
|
||||
return this.discussion.isResolved();
|
||||
},
|
||||
buttonText() {
|
||||
if (this.isDiscussionResolved) {
|
||||
if (this.textareaIsEmpty) {
|
||||
return __('Unresolve thread');
|
||||
} else {
|
||||
return __('Comment & unresolve thread');
|
||||
}
|
||||
} else {
|
||||
if (this.textareaIsEmpty) {
|
||||
return __('Resolve thread');
|
||||
} else {
|
||||
return __('Comment & resolve thread');
|
||||
}
|
||||
if (this.textareaIsEmpty) {
|
||||
return this.isDiscussionResolved ? __('Unresolve thread') : __('Resolve thread');
|
||||
}
|
||||
return this.isDiscussionResolved
|
||||
? __('Comment & unresolve thread')
|
||||
: __('Comment & resolve thread');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-else-return, guard-for-in, no-restricted-syntax, no-lonely-if, no-continue */
|
||||
/* eslint-disable func-names, guard-for-in, no-restricted-syntax, no-lonely-if, no-continue */
|
||||
/* global CommentsStore */
|
||||
|
||||
import $ from 'jquery';
|
||||
|
@ -25,9 +25,8 @@ const JumpToDiscussion = Vue.extend({
|
|||
buttonText() {
|
||||
if (this.discussionId) {
|
||||
return __('Jump to next unresolved thread');
|
||||
} else {
|
||||
return __('Jump to first unresolved thread');
|
||||
}
|
||||
return __('Jump to first unresolved thread');
|
||||
},
|
||||
allResolved() {
|
||||
return this.unresolvedDiscussionCount === 0;
|
||||
|
@ -36,12 +35,10 @@ const JumpToDiscussion = Vue.extend({
|
|||
if (this.discussionId) {
|
||||
if (this.unresolvedDiscussionCount > 1) {
|
||||
return true;
|
||||
} else {
|
||||
return this.discussionId !== this.lastResolvedId;
|
||||
}
|
||||
} else {
|
||||
return this.unresolvedDiscussionCount >= 1;
|
||||
return this.discussionId !== this.lastResolvedId;
|
||||
}
|
||||
return this.unresolvedDiscussionCount >= 1;
|
||||
},
|
||||
lastResolvedId() {
|
||||
let lastId;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable no-useless-return, func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, no-else-return, dot-notation, no-empty */
|
||||
/* eslint-disable no-useless-return, func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, dot-notation, no-empty */
|
||||
/* global Issuable */
|
||||
/* global ListLabel */
|
||||
|
||||
|
@ -311,9 +311,8 @@ export default class LabelsSelect {
|
|||
firstLabel: selectedLabels[0],
|
||||
labelCount: selectedLabels.length - 1,
|
||||
});
|
||||
} else {
|
||||
return defaultLabel;
|
||||
}
|
||||
return defaultLabel;
|
||||
},
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
id(label) {
|
||||
|
@ -325,9 +324,8 @@ export default class LabelsSelect {
|
|||
|
||||
if ($dropdown.hasClass('js-filter-submit') && label.isAny == null) {
|
||||
return label.title;
|
||||
} else {
|
||||
return label.id;
|
||||
}
|
||||
return label.id;
|
||||
},
|
||||
hidden() {
|
||||
const page = $('body').attr('data-page');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-param-reassign, operator-assignment, no-else-return, consistent-return */
|
||||
/* eslint-disable func-names, no-param-reassign, operator-assignment, consistent-return */
|
||||
import $ from 'jquery';
|
||||
import { insertText } from '~/lib/utils/common_utils';
|
||||
|
||||
|
@ -217,9 +217,8 @@ export function insertMarkdownText({
|
|||
}
|
||||
if (val.indexOf(tag) === 0) {
|
||||
return String(val.replace(tag, ''));
|
||||
} else {
|
||||
return String(tag) + val;
|
||||
}
|
||||
return String(tag) + val;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return, no-else-return */
|
||||
/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
@ -128,9 +128,8 @@ LineHighlighter.prototype.hashToRange = function(hash) {
|
|||
const first = parseInt(matches[1], 10);
|
||||
const last = matches[2] ? parseInt(matches[2], 10) : null;
|
||||
return [first, last];
|
||||
} else {
|
||||
return [null, null];
|
||||
}
|
||||
return [null, null];
|
||||
};
|
||||
|
||||
// Highlight a single line
|
||||
|
@ -153,9 +152,8 @@ LineHighlighter.prototype.highlightRange = function(range) {
|
|||
}
|
||||
|
||||
return results;
|
||||
} else {
|
||||
return this.highlightLine(range[0]);
|
||||
}
|
||||
return this.highlightLine(range[0]);
|
||||
};
|
||||
|
||||
// Set the URL hash string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable one-var, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
|
||||
/* eslint-disable one-var, no-self-compare, consistent-return, no-param-reassign, no-shadow */
|
||||
/* global Issuable */
|
||||
/* global ListMilestone */
|
||||
|
||||
|
@ -123,9 +123,8 @@ export default class MilestoneSelect {
|
|||
toggleLabel: (selected, el) => {
|
||||
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
|
||||
return selected.title;
|
||||
} else {
|
||||
return defaultLabel;
|
||||
}
|
||||
return defaultLabel;
|
||||
},
|
||||
defaultLabel,
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
|
@ -133,9 +132,8 @@ export default class MilestoneSelect {
|
|||
id: milestone => {
|
||||
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
|
||||
return milestone.name;
|
||||
} else {
|
||||
return milestone.id;
|
||||
}
|
||||
return milestone.id;
|
||||
},
|
||||
hidden: () => {
|
||||
$selectBox.hide();
|
||||
|
@ -244,13 +242,12 @@ export default class MilestoneSelect {
|
|||
)
|
||||
.find('span')
|
||||
.text(data.milestone.title);
|
||||
} else {
|
||||
$value.html(milestoneLinkNoneTemplate);
|
||||
return $sidebarCollapsedValue
|
||||
.attr('data-original-title', __('Milestone'))
|
||||
.find('span')
|
||||
.text(__('None'));
|
||||
}
|
||||
$value.html(milestoneLinkNoneTemplate);
|
||||
return $sidebarCollapsedValue
|
||||
.attr('data-original-title', __('Milestone'))
|
||||
.find('span')
|
||||
.text(__('None'));
|
||||
})
|
||||
.catch(() => {
|
||||
// eslint-disable-next-line no-jquery/no-fade
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable no-else-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
import '~/gl_dropdown';
|
||||
import Api from './api';
|
||||
|
@ -23,9 +21,8 @@ export default class NamespaceSelect {
|
|||
toggleLabel(selected) {
|
||||
if (selected.id == null) {
|
||||
return selected.text;
|
||||
} else {
|
||||
return `${selected.kind}: ${selected.full_path}`;
|
||||
}
|
||||
return `${selected.kind}: ${selected.full_path}`;
|
||||
},
|
||||
data(term, dataCallback) {
|
||||
return Api.namespaces(term, namespaces => {
|
||||
|
@ -43,9 +40,8 @@ export default class NamespaceSelect {
|
|||
text(namespace) {
|
||||
if (namespace.id == null) {
|
||||
return namespace.text;
|
||||
} else {
|
||||
return `${namespace.kind}: ${namespace.full_path}`;
|
||||
}
|
||||
return `${namespace.kind}: ${namespace.full_path}`;
|
||||
},
|
||||
renderRow: this.renderRow,
|
||||
clicked(options) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, consistent-return, no-return-assign, no-else-return, @gitlab/require-i18n-strings */
|
||||
/* eslint-disable func-names, consistent-return, no-return-assign, @gitlab/require-i18n-strings */
|
||||
|
||||
import $ from 'jquery';
|
||||
import RefSelectDropdown from './ref_select_dropdown';
|
||||
|
@ -76,9 +76,8 @@ export default class NewBranchForm {
|
|||
const matched = this.name.val().match(restriction.pattern);
|
||||
if (matched) {
|
||||
return errors.concat(formatter(matched.reduce(unique, []), restriction));
|
||||
} else {
|
||||
return errors;
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
const errors = this.restrictions.reduce(validator, []);
|
||||
if (errors.length > 0) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-restricted-properties, babel/camelcase,
|
||||
no-unused-expressions, default-case,
|
||||
consistent-return, no-alert, no-param-reassign, no-else-return,
|
||||
consistent-return, no-alert, no-param-reassign,
|
||||
no-shadow, no-useless-escape,
|
||||
class-methods-use-this */
|
||||
|
||||
|
@ -1123,10 +1123,9 @@ export default class Notes {
|
|||
if (row.is('.js-temp-notes-holder')) {
|
||||
// remove temporary row for diff lines
|
||||
return row.remove();
|
||||
} else {
|
||||
// only remove the form
|
||||
return form.remove();
|
||||
}
|
||||
// only remove the form
|
||||
return form.remove();
|
||||
}
|
||||
|
||||
cancelDiscussionForm(e) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-else-return */
|
||||
/* eslint-disable func-names */
|
||||
|
||||
import $ from 'jquery';
|
||||
import Api from './api';
|
||||
|
@ -74,18 +74,17 @@ const projectSelect = () => {
|
|||
},
|
||||
projectsCallback,
|
||||
);
|
||||
} else {
|
||||
return Api.projects(
|
||||
query.term,
|
||||
{
|
||||
order_by: this.orderBy,
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
membership: !this.allProjects,
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
}
|
||||
return Api.projects(
|
||||
query.term,
|
||||
{
|
||||
order_by: this.orderBy,
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
membership: !this.allProjects,
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
},
|
||||
id(project) {
|
||||
if (simpleFilter) return project.id;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable consistent-return, no-else-return */
|
||||
/* eslint-disable consistent-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
@ -16,11 +16,10 @@ export default function syntaxHighlight(el) {
|
|||
if ($(el).hasClass('js-syntax-highlight')) {
|
||||
// Given the element itself, apply highlighting
|
||||
return $(el).addClass(gon.user_color_scheme);
|
||||
} else {
|
||||
// Given a parent element, recurse to any of its applicable children
|
||||
const $children = $(el).find('.js-syntax-highlight');
|
||||
if ($children.length) {
|
||||
return syntaxHighlight($children);
|
||||
}
|
||||
}
|
||||
// Given a parent element, recurse to any of its applicable children
|
||||
const $children = $(el).find('.js-syntax-highlight');
|
||||
if ($children.length) {
|
||||
return syntaxHighlight($children);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, consistent-return, one-var, no-else-return, class-methods-use-this */
|
||||
/* eslint-disable func-names, consistent-return, one-var, class-methods-use-this */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { visitUrl } from './lib/utils/url_utility';
|
||||
|
@ -15,9 +15,8 @@ export default class TreeView {
|
|||
if (e.metaKey || e.which === 2) {
|
||||
e.preventDefault();
|
||||
return window.open(path, '_blank');
|
||||
} else {
|
||||
return visitUrl(path);
|
||||
}
|
||||
return visitUrl(path);
|
||||
}
|
||||
});
|
||||
// Show the "Loading commit data" for only the first element
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-else-return, no-self-compare, no-unused-expressions, yoda, prefer-spread, babel/camelcase, no-param-reassign */
|
||||
/* eslint-disable func-names, prefer-rest-params, consistent-return, no-shadow, no-self-compare, no-unused-expressions, yoda, prefer-spread, babel/camelcase, no-param-reassign */
|
||||
/* global Issuable */
|
||||
/* global emitSidebarEvent */
|
||||
|
||||
|
@ -148,12 +148,11 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
name: selectedUser.name,
|
||||
length: otherSelected.length,
|
||||
});
|
||||
} else {
|
||||
return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
|
||||
name: firstUser.name,
|
||||
length: selectedUsers.length - 1,
|
||||
});
|
||||
}
|
||||
return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
|
||||
name: firstUser.name,
|
||||
length: selectedUsers.length - 1,
|
||||
});
|
||||
};
|
||||
|
||||
$('.assign-to-me-link').on('click', e => {
|
||||
|
@ -375,13 +374,11 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
$dropdown.find('.dropdown-toggle-text').removeClass('is-default');
|
||||
if (selected.text) {
|
||||
return selected.text;
|
||||
} else {
|
||||
return selected.name;
|
||||
}
|
||||
} else {
|
||||
$dropdown.find('.dropdown-toggle-text').addClass('is-default');
|
||||
return defaultLabel;
|
||||
return selected.name;
|
||||
}
|
||||
$dropdown.find('.dropdown-toggle-text').addClass('is-default');
|
||||
return defaultLabel;
|
||||
},
|
||||
defaultLabel,
|
||||
hidden() {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Metrics
|
||||
class UsersStarredDashboard < ApplicationRecord
|
||||
self.table_name = 'metrics_users_starred_dashboards'
|
||||
|
||||
belongs_to :user, inverse_of: :metrics_users_starred_dashboards
|
||||
belongs_to :project, inverse_of: :metrics_users_starred_dashboards
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :project_id, presence: true
|
||||
validates :dashboard_path, presence: true, length: { maximum: 255 }
|
||||
validates :dashboard_path, uniqueness: { scope: %i[user_id project_id] }
|
||||
end
|
||||
end
|
|
@ -257,6 +257,7 @@ class Project < ApplicationRecord
|
|||
has_many :prometheus_alerts, inverse_of: :project
|
||||
has_many :prometheus_alert_events, inverse_of: :project
|
||||
has_many :self_managed_prometheus_alert_events, inverse_of: :project
|
||||
has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project
|
||||
|
||||
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project
|
||||
|
||||
|
|
|
@ -167,6 +167,8 @@ class User < ApplicationRecord
|
|||
has_many :term_agreements
|
||||
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
|
||||
|
||||
has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :user
|
||||
|
||||
has_one :status, class_name: 'UserStatus'
|
||||
has_one :user_preference
|
||||
has_one :user_detail
|
||||
|
|
|
@ -1294,7 +1294,7 @@
|
|||
- :name: reactive_caching
|
||||
:feature_category: :not_owned
|
||||
:has_external_dependencies:
|
||||
:urgency: :high
|
||||
:urgency: :low
|
||||
:resource_boundary: :cpu
|
||||
:weight: 1
|
||||
:idempotent:
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
class ReactiveCachingWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ReactiveCacheableWorker
|
||||
|
||||
urgency :high
|
||||
urgency :low
|
||||
worker_resource_boundary :cpu
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Add database relation to preserve users starred
|
||||
metrics dashboard information.
|
||||
merge_request: 29912
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateMetricsUsersStarredDashboard < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
# limit added in following migration db/migrate/20200424101920_add_text_limit_to_metrics_users_starred_dashboards_dashboard_path.rb
|
||||
# to allow this migration to be run inside the transaction
|
||||
# rubocop: disable Migration/AddLimitToTextColumns
|
||||
def up
|
||||
create_table :metrics_users_starred_dashboards do |t|
|
||||
t.timestamps_with_timezone
|
||||
t.bigint :project_id, null: false
|
||||
t.bigint :user_id, null: false
|
||||
t.text :dashboard_path, null: false
|
||||
|
||||
t.index :project_id
|
||||
t.index %i(user_id project_id dashboard_path), name: "idx_metrics_users_starred_dashboard_on_user_project_dashboard", unique: true
|
||||
end
|
||||
end
|
||||
# rubocop: enable Migration/AddLimitToTextColumns
|
||||
|
||||
def down
|
||||
drop_table :metrics_users_starred_dashboards
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddForeignKeyFromUsersToMetricsUsersStarredDashboars < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :metrics_users_starred_dashboards, :users, column: :user_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do # rubocop:disable Migration/WithLockRetriesWithoutDdlTransaction
|
||||
remove_foreign_key_if_exists :metrics_users_starred_dashboards, column: :user_id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddForeignKeyFromProjectsToMetricsUsersStarredDashboars < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :metrics_users_starred_dashboards, :projects, column: :project_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do # rubocop:disable Migration/WithLockRetriesWithoutDdlTransaction
|
||||
remove_foreign_key_if_exists :metrics_users_starred_dashboards, column: :project_id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTextLimitToMetricsUsersStarredDashboardsDashboardPath < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_text_limit :metrics_users_starred_dashboards, :dashboard_path, 255
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :metrics_users_starred_dashboards, :dashboard_path
|
||||
end
|
||||
end
|
|
@ -4033,6 +4033,25 @@ CREATE SEQUENCE public.metrics_dashboard_annotations_id_seq
|
|||
|
||||
ALTER SEQUENCE public.metrics_dashboard_annotations_id_seq OWNED BY public.metrics_dashboard_annotations.id;
|
||||
|
||||
CREATE TABLE public.metrics_users_starred_dashboards (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
dashboard_path text NOT NULL,
|
||||
CONSTRAINT check_79a84a0f57 CHECK ((char_length(dashboard_path) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.metrics_users_starred_dashboards_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE public.metrics_users_starred_dashboards_id_seq OWNED BY public.metrics_users_starred_dashboards.id;
|
||||
|
||||
CREATE TABLE public.milestone_releases (
|
||||
milestone_id bigint NOT NULL,
|
||||
release_id bigint NOT NULL
|
||||
|
@ -7517,6 +7536,8 @@ ALTER TABLE ONLY public.merge_trains ALTER COLUMN id SET DEFAULT nextval('public
|
|||
|
||||
ALTER TABLE ONLY public.metrics_dashboard_annotations ALTER COLUMN id SET DEFAULT nextval('public.metrics_dashboard_annotations_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.metrics_users_starred_dashboards ALTER COLUMN id SET DEFAULT nextval('public.metrics_users_starred_dashboards_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.milestones ALTER COLUMN id SET DEFAULT nextval('public.milestones_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY public.namespace_statistics ALTER COLUMN id SET DEFAULT nextval('public.namespace_statistics_id_seq'::regclass);
|
||||
|
@ -8327,6 +8348,9 @@ ALTER TABLE ONLY public.merge_trains
|
|||
ALTER TABLE ONLY public.metrics_dashboard_annotations
|
||||
ADD CONSTRAINT metrics_dashboard_annotations_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public.metrics_users_starred_dashboards
|
||||
ADD CONSTRAINT metrics_users_starred_dashboards_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY public.milestones
|
||||
ADD CONSTRAINT milestones_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -8842,6 +8866,8 @@ CREATE INDEX idx_merge_requests_on_state_id_and_merge_status ON public.merge_req
|
|||
|
||||
CREATE INDEX idx_merge_requests_on_target_project_id_and_iid_opened ON public.merge_requests USING btree (target_project_id, iid) WHERE (state_id = 1);
|
||||
|
||||
CREATE UNIQUE INDEX idx_metrics_users_starred_dashboard_on_user_project_dashboard ON public.metrics_users_starred_dashboards USING btree (user_id, project_id, dashboard_path);
|
||||
|
||||
CREATE INDEX idx_mr_cc_diff_files_on_mr_cc_id_and_sha ON public.merge_request_context_commit_diff_files USING btree (merge_request_context_commit_id, sha);
|
||||
|
||||
CREATE INDEX idx_packages_packages_on_project_id_name_version_package_type ON public.packages_packages USING btree (project_id, name, version, package_type);
|
||||
|
@ -9872,6 +9898,8 @@ CREATE INDEX index_metrics_dashboard_annotations_on_cluster_id_and_3_columns ON
|
|||
|
||||
CREATE INDEX index_metrics_dashboard_annotations_on_environment_id_and_3_col ON public.metrics_dashboard_annotations USING btree (environment_id, dashboard_path, starting_at, ending_at) WHERE (environment_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_metrics_users_starred_dashboards_on_project_id ON public.metrics_users_starred_dashboards USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_milestone_releases_on_release_id ON public.milestone_releases USING btree (release_id);
|
||||
|
||||
CREATE INDEX index_milestones_on_description_trigram ON public.milestones USING gin (description public.gin_trgm_ops);
|
||||
|
@ -11217,6 +11245,9 @@ ALTER TABLE ONLY public.deployments
|
|||
ALTER TABLE ONLY public.gitlab_subscriptions
|
||||
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES public.plans(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY public.metrics_users_starred_dashboards
|
||||
ADD CONSTRAINT fk_bd6ae32fac FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY public.snippets
|
||||
ADD CONSTRAINT fk_be41fd4bb7 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -11271,6 +11302,9 @@ ALTER TABLE ONLY public.geo_event_log
|
|||
ALTER TABLE ONLY public.lists
|
||||
ADD CONSTRAINT fk_d6cf4279f7 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY public.metrics_users_starred_dashboards
|
||||
ADD CONSTRAINT fk_d76a2b9a8c FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY public.system_note_metadata
|
||||
ADD CONSTRAINT fk_d83a918cb1 FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -13578,6 +13612,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200420094444
|
||||
20200420104303
|
||||
20200420104323
|
||||
20200420115948
|
||||
20200420162730
|
||||
20200420172113
|
||||
20200420172752
|
||||
|
@ -13591,8 +13626,11 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200423080334
|
||||
20200423080607
|
||||
20200423081409
|
||||
20200423081441
|
||||
20200423081519
|
||||
20200423101529
|
||||
20200424050250
|
||||
20200424101920
|
||||
20200427064130
|
||||
\.
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ GitLab is the first single application for software development, security,
|
|||
and operations that enables [Concurrent DevOps](https://about.gitlab.com/concurrent-devops/),
|
||||
making the software lifecycle faster and radically improving the speed of business.
|
||||
|
||||
GitLab provides solutions for [all the stages of the DevOps lifecycle](https://about.gitlab.com/stages-devops-lifecycle/):
|
||||
GitLab provides solutions for [each of the stages of the DevOps lifecycle](https://about.gitlab.com/stages-devops-lifecycle/):
|
||||
|
||||
![DevOps Stages](img/devops-stages.png)
|
||||
|
||||
|
@ -65,7 +65,7 @@ The following sections provide links to documentation for each DevOps stage:
|
|||
|:------------------------|:------------------------------------------------------------|
|
||||
| [Manage](#manage) | Statistics and analytics features. |
|
||||
| [Plan](#plan) | Project planning and management features. |
|
||||
| [Create](#create) | Source code and data creation and management features. |
|
||||
| [Create](#create) | Source code, data creation, and management features. |
|
||||
| [Verify](#verify) | Testing, code quality, and continuous integration features. |
|
||||
| [Package](#package) | Docker container registry. |
|
||||
| [Release](#release) | Application release and delivery features. |
|
||||
|
@ -285,7 +285,7 @@ The following documentation relates to the DevOps **Release** stage:
|
|||
| [Canary Deployments](user/project/canary_deployments.md) **(PREMIUM)** | Employ a popular CI strategy where a small portion of the fleet is updated to the new version first. |
|
||||
| [Deploy Boards](user/project/deploy_boards.md) **(PREMIUM)** | View the current health and status of each CI environment running on Kubernetes, displaying the status of the pods in the deployment. |
|
||||
| [Environments and deployments](ci/environments.md) | With environments, you can control the continuous deployment of your software within GitLab. |
|
||||
| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables) | Limit scope of variables to specific environments. |
|
||||
| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables) | Limit the scope of variables to specific environments. |
|
||||
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Deployment and Delivery with GitLab. |
|
||||
| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy a static site directly from GitLab. |
|
||||
| [Protected Runners](ci/runners/README.md#protected-runners) | Select Runners to only pick jobs for protected branches and tags. |
|
||||
|
@ -300,7 +300,7 @@ The following documentation relates to the DevOps **Release** stage:
|
|||
### Configure
|
||||
|
||||
Automate your entire workflow from build to deploy and monitoring with GitLab
|
||||
Auto DevOps. Best practice templates get you started with minimal to zero
|
||||
Auto DevOps. Best practice templates help get you started with minimal to zero
|
||||
configuration. Then customize everything from buildpacks to CI/CD.
|
||||
|
||||
The following documentation relates to the DevOps **Configure** stage:
|
||||
|
|
|
@ -4076,8 +4076,7 @@ type Group {
|
|||
visibility: String
|
||||
|
||||
"""
|
||||
Vulnerabilities reported on the projects in the group and its subgroups.
|
||||
Available only when feature flag `first_class_vulnerabilities` is enabled
|
||||
Vulnerabilities reported on the projects in the group and its subgroups
|
||||
"""
|
||||
vulnerabilities(
|
||||
"""
|
||||
|
@ -7403,7 +7402,7 @@ type Project {
|
|||
visibility: String
|
||||
|
||||
"""
|
||||
Vulnerabilities reported on the project. Available only when feature flag `first_class_vulnerabilities` is enabled
|
||||
Vulnerabilities reported on the project
|
||||
"""
|
||||
vulnerabilities(
|
||||
"""
|
||||
|
@ -7448,8 +7447,7 @@ type Project {
|
|||
): VulnerabilityConnection
|
||||
|
||||
"""
|
||||
Counts for each severity of vulnerability of the project. Available only when
|
||||
feature flag `first_class_vulnerabilities` is enabled
|
||||
Counts for each severity of vulnerability of the project
|
||||
"""
|
||||
vulnerabilitySeveritiesCount: VulnerabilitySeveritiesCount
|
||||
|
||||
|
|
|
@ -11490,7 +11490,7 @@
|
|||
},
|
||||
{
|
||||
"name": "vulnerabilities",
|
||||
"description": "Vulnerabilities reported on the projects in the group and its subgroups. Available only when feature flag `first_class_vulnerabilities` is enabled",
|
||||
"description": "Vulnerabilities reported on the projects in the group and its subgroups",
|
||||
"args": [
|
||||
{
|
||||
"name": "projectId",
|
||||
|
@ -21926,7 +21926,7 @@
|
|||
},
|
||||
{
|
||||
"name": "vulnerabilities",
|
||||
"description": "Vulnerabilities reported on the project. Available only when feature flag `first_class_vulnerabilities` is enabled",
|
||||
"description": "Vulnerabilities reported on the project",
|
||||
"args": [
|
||||
{
|
||||
"name": "projectId",
|
||||
|
@ -22051,7 +22051,7 @@
|
|||
},
|
||||
{
|
||||
"name": "vulnerabilitySeveritiesCount",
|
||||
"description": "Counts for each severity of vulnerability of the project. Available only when feature flag `first_class_vulnerabilities` is enabled",
|
||||
"description": "Counts for each severity of vulnerability of the project",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
|
@ -1071,7 +1071,7 @@ Information about pagination in a connection.
|
|||
| `tagList` | String | List of project topics (not Git tags) |
|
||||
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
|
||||
| `visibility` | String | Visibility of the project |
|
||||
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each severity of vulnerability of the project. Available only when feature flag `first_class_vulnerabilities` is enabled |
|
||||
| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each severity of vulnerability of the project |
|
||||
| `webUrl` | String | Web URL of the project |
|
||||
| `wikiEnabled` | Boolean | Indicates if Wikis are enabled for the current user |
|
||||
|
||||
|
|
|
@ -381,11 +381,10 @@ other environments.
|
|||
## Currently supported languages
|
||||
|
||||
Note that not all buildpacks support Auto Test yet, as it's a relatively new
|
||||
enhancement. All of Heroku's [officially supported
|
||||
languages](https://devcenter.heroku.com/articles/heroku-ci#supported-languages)
|
||||
support buildpacks, and some third-party buildpacks as well (such as Go, Node, Java, PHP,
|
||||
Python, Ruby, Gradle, Scala, and Elixir) all support Auto Test, but notably the
|
||||
multi-buildpack does not.
|
||||
enhancement. All of Heroku's
|
||||
[officially supported languages](https://devcenter.heroku.com/articles/heroku-ci#supported-languages)
|
||||
support Auto Test. The languages supported by Heroku's Herokuish buildpacks all
|
||||
support Auto Test, but notably the multi-buildpack does not.
|
||||
|
||||
As of GitLab 10.0, the supported buildpacks are:
|
||||
|
||||
|
@ -438,28 +437,36 @@ spec:
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
- Auto Build and Auto Test may fail to detect your language or framework with the
|
||||
following error:
|
||||
### Unable to select a buildpack
|
||||
|
||||
```plaintext
|
||||
Step 5/11 : RUN /bin/herokuish buildpack build
|
||||
---> Running in eb468cd46085
|
||||
-----> Unable to select a buildpack
|
||||
The command '/bin/sh -c /bin/herokuish buildpack build' returned a non-zero code: 1
|
||||
```
|
||||
Auto Build and Auto Test may fail to detect your language or framework with the
|
||||
following error:
|
||||
|
||||
The following are possible reasons:
|
||||
```plaintext
|
||||
Step 5/11 : RUN /bin/herokuish buildpack build
|
||||
---> Running in eb468cd46085
|
||||
-----> Unable to select a buildpack
|
||||
The command '/bin/sh -c /bin/herokuish buildpack build' returned a non-zero code: 1
|
||||
```
|
||||
|
||||
- Your application may be missing the key files the buildpack is looking for. For
|
||||
example, for Ruby applications you must have a `Gemfile` to be properly detected,
|
||||
even though it's possible to write a Ruby app without a `Gemfile`.
|
||||
- There may be no buildpack for your application. Try specifying a
|
||||
[custom buildpack](customize.md#custom-buildpacks).
|
||||
- Auto Test may fail because of a mismatch between testing frameworks. In this
|
||||
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
|
||||
- Auto Deploy will fail if GitLab can't create a Kubernetes namespace and
|
||||
service account for your project. For help debugging this issue, see
|
||||
[Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
|
||||
The following are possible reasons:
|
||||
|
||||
- Your application may be missing the key files the buildpack is looking for.
|
||||
Ruby applications require a `Gemfile` to be properly detected,
|
||||
even though it's possible to write a Ruby app without a `Gemfile`.
|
||||
- No buildpack may exist for your application. Try specifying a
|
||||
[custom buildpack](customize.md#custom-buildpacks).
|
||||
|
||||
### Mismatch between testing frameworks
|
||||
|
||||
Auto Test may fail because of a mismatch between testing frameworks. In this
|
||||
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
|
||||
|
||||
### Failure to create a Kubernetes namespace
|
||||
|
||||
Auto Deploy will fail if GitLab can't create a Kubernetes namespace and
|
||||
service account for your project. For help debugging this issue, see
|
||||
[Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
|
||||
|
||||
## Development guides
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ create issues for the same project.
|
|||
|
||||
![Create issue from group-level issue tracker](img/create_issue_from_group_level_issue_tracker.png)
|
||||
|
||||
### New issue via Service Desk **(PREMIUM)**
|
||||
### New issue via Service Desk **(STARTER)**
|
||||
|
||||
Enable [Service Desk](../service_desk.md) for your project and offer email support.
|
||||
By doing so, when your customer sends a new email, a new issue can be created in
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Service Desk **(PREMIUM)**
|
||||
# Service Desk **(Starter)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/149) in [GitLab Premium 9.1](https://about.gitlab.com/releases/2017/04/22/gitlab-9-1-released/#service-desk-eep).
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ Some features depend on others:
|
|||
- If you disable the **Issues** option, GitLab also removes the following
|
||||
features:
|
||||
- **Issue Boards**
|
||||
- [**Service Desk**](#service-desk-premium) **(PREMIUM)**
|
||||
- [**Service Desk**](#service-desk-starter) **(STARTER)**
|
||||
|
||||
NOTE: **Note:**
|
||||
When the **Issues** option is disabled, you can still access **Milestones**
|
||||
|
@ -106,7 +106,7 @@ Set up your project's merge request settings:
|
|||
|
||||
![project's merge request settings](img/merge_requests_settings.png)
|
||||
|
||||
### Service Desk **(PREMIUM)**
|
||||
### Service Desk **(STARTER)**
|
||||
|
||||
Enable [Service Desk](../service_desk.md) for your project to offer customer support.
|
||||
|
||||
|
|
|
@ -16,11 +16,15 @@ module Gitlab
|
|||
|
||||
lease = Gitlab::ExclusiveLease.new(key, timeout: ttl)
|
||||
retried = false
|
||||
max_attempts = 1 + retries
|
||||
|
||||
until uuid = lease.try_obtain
|
||||
# Keep trying until we obtain the lease. To prevent hammering Redis too
|
||||
# much we'll wait for a bit.
|
||||
sleep(sleep_sec)
|
||||
attempt_number = max_attempts - retries
|
||||
delay = sleep_sec.respond_to?(:call) ? sleep_sec.call(attempt_number) : sleep_sec
|
||||
|
||||
sleep(delay)
|
||||
(retries -= 1) < 0 ? break : retried ||= true
|
||||
end
|
||||
|
||||
|
|
|
@ -96,7 +96,8 @@ module Gitlab
|
|||
class << self
|
||||
def configuration_toml(gitaly_dir, storage_paths)
|
||||
nodes = [{ storage: 'default', address: "unix:#{gitaly_dir}/gitaly.socket", primary: true, token: 'secret' }]
|
||||
config = { socket_path: "#{gitaly_dir}/praefect.socket", virtual_storage_name: 'default', token: 'secret', node: nodes }
|
||||
storages = [{ name: 'default', node: nodes }]
|
||||
config = { socket_path: "#{gitaly_dir}/praefect.socket", virtual_storage: storages }
|
||||
config[:token] = 'secret' if Rails.env.test?
|
||||
|
||||
TomlRB.dump(config)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :metrics_users_starred_dashboard, class: '::Metrics::UsersStarredDashboard' do
|
||||
dashboard_path { "custom_dashboard.yml" }
|
||||
user
|
||||
project
|
||||
end
|
||||
end
|
|
@ -12,6 +12,8 @@ describe 'Project navbar' do
|
|||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
stub_licensed_features(service_desk: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */
|
||||
/* eslint-disable dot-notation, no-return-assign, no-new, no-underscore-dangle */
|
||||
|
||||
import $ from 'jquery';
|
||||
import LineHighlighter from '~/line_highlighter';
|
||||
|
@ -8,10 +8,9 @@ describe('LineHighlighter', function() {
|
|||
const clickLine = function(number, eventData = {}) {
|
||||
if ($.isEmptyObject(eventData)) {
|
||||
return $(`#L${number}`).click();
|
||||
} else {
|
||||
const e = $.Event('click', eventData);
|
||||
return $(`#L${number}`).trigger(e);
|
||||
}
|
||||
const e = $.Event('click', eventData);
|
||||
return $(`#L${number}`).trigger(e);
|
||||
};
|
||||
beforeEach(function() {
|
||||
loadFixtures('static/line_highlighter.html');
|
||||
|
|
|
@ -82,10 +82,22 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
|
||||
context 'when sleep second is specified' do
|
||||
let(:options) { { retries: 0, sleep_sec: 0.05.seconds } }
|
||||
let(:options) { { retries: 1, sleep_sec: 0.05.seconds } }
|
||||
|
||||
it 'receives the specified argument' do
|
||||
expect(class_instance).to receive(:sleep).with(0.05.seconds).once
|
||||
expect(class_instance).to receive(:sleep).with(0.05.seconds).twice
|
||||
|
||||
expect { subject }.to raise_error('Failed to obtain a lock')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sleep second is specified as a lambda' do
|
||||
let(:options) { { retries: 2, sleep_sec: ->(num) { 0.1 + num } } }
|
||||
|
||||
it 'receives the specified argument' do
|
||||
expect(class_instance).to receive(:sleep).with(1.1.seconds).once
|
||||
expect(class_instance).to receive(:sleep).with(2.1.seconds).once
|
||||
expect(class_instance).to receive(:sleep).with(3.1.seconds).once
|
||||
|
||||
expect { subject }.to raise_error('Failed to obtain a lock')
|
||||
end
|
||||
|
|
|
@ -487,6 +487,7 @@ project:
|
|||
- daily_report_results
|
||||
- jira_imports
|
||||
- compliance_framework_setting
|
||||
- metrics_users_starred_dashboards
|
||||
- alert_management_alerts
|
||||
award_emoji:
|
||||
- awardable
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Metrics::UsersStarredDashboard do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project).inverse_of(:metrics_users_starred_dashboards) }
|
||||
it { is_expected.to belong_to(:user).inverse_of(:metrics_users_starred_dashboards) }
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
subject { build(:metrics_users_starred_dashboard) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:user_id) }
|
||||
it { is_expected.to validate_presence_of(:project_id) }
|
||||
it { is_expected.to validate_presence_of(:dashboard_path) }
|
||||
it { is_expected.to validate_length_of(:dashboard_path).is_at_most(255) }
|
||||
it { is_expected.to validate_uniqueness_of(:dashboard_path).scoped_to(%i[user_id project_id]) }
|
||||
end
|
||||
end
|
|
@ -113,6 +113,7 @@ describe Project do
|
|||
it { is_expected.to have_many(:self_managed_prometheus_alert_events) }
|
||||
it { is_expected.to have_many(:alert_management_alerts) }
|
||||
it { is_expected.to have_many(:jira_imports) }
|
||||
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
|
||||
|
||||
it_behaves_like 'model with repository' do
|
||||
let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
|
||||
|
|
|
@ -54,6 +54,7 @@ describe User, :do_not_mock_admin_mode do
|
|||
it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') }
|
||||
it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') }
|
||||
it { is_expected.to have_many(:releases).dependent(:nullify) }
|
||||
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) }
|
||||
|
||||
describe "#bio" do
|
||||
it 'syncs bio with `user_details.bio` on create' do
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ConcurrentHelpers
|
||||
Cancelled = Class.new(StandardError)
|
||||
|
||||
# To test for contention, we may need to run some actions in parallel. This
|
||||
# helper takes an array of blocks and schedules them all on different threads
|
||||
# in a fixed-size thread pool.
|
||||
#
|
||||
# @param [Array[Proc]] blocks
|
||||
# @param [Integer] task_wait_time: time to wait for each task (upper bound on
|
||||
# reasonable task execution time)
|
||||
# @param [Integer] max_concurrency: maximum number of tasks to run at once
|
||||
#
|
||||
def run_parallel(blocks, task_wait_time: 20.seconds, max_concurrency: Concurrent.processor_count - 1)
|
||||
thread_pool = Concurrent::FixedThreadPool.new(
|
||||
[2, max_concurrency].max, { max_queue: blocks.size }
|
||||
)
|
||||
opts = { executor: thread_pool }
|
||||
|
||||
error = Concurrent::MVar.new
|
||||
|
||||
blocks.map { |block| Concurrent::Future.execute(opts, &block) }.each do |future|
|
||||
future.wait(task_wait_time)
|
||||
|
||||
if future.complete?
|
||||
error.put(future.reason) if future.reason && error.empty?
|
||||
else
|
||||
future.cancel
|
||||
error.put(Cancelled.new) if error.empty?
|
||||
end
|
||||
end
|
||||
|
||||
raise error.take if error.full?
|
||||
ensure
|
||||
thread_pool.shutdown
|
||||
thread_pool.wait_for_termination(10)
|
||||
thread_pool.kill if thread_pool.running?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RenameableUpload < SimpleDelegator
|
||||
attr_accessor :original_filename
|
||||
|
||||
# Get a fixture file with a new unique name, and the same extension
|
||||
def self.unique_file(name)
|
||||
upload = new(fixture_file_upload("spec/fixtures/#{name}"))
|
||||
ext = File.extname(name)
|
||||
new_name = File.basename(FactoryBot.generate(:filename), '.*')
|
||||
upload.original_filename = new_name + ext
|
||||
|
||||
upload
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue