Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
91e8c3a6ef
commit
57a3a42c88
|
@ -172,6 +172,8 @@ review-qa-smoke:
|
||||||
- .review-qa-base
|
- .review-qa-base
|
||||||
- .review:rules:review-qa-smoke
|
- .review:rules:review-qa-smoke
|
||||||
retry: 1 # This is confusing but this means "2 runs at max".
|
retry: 1 # This is confusing but this means "2 runs at max".
|
||||||
|
variables:
|
||||||
|
QA_RUN_TYPE: review-qa-smoke
|
||||||
script:
|
script:
|
||||||
- bin/test Test::Instance::Smoke "${CI_ENVIRONMENT_URL}"
|
- bin/test Test::Instance::Smoke "${CI_ENVIRONMENT_URL}"
|
||||||
|
|
||||||
|
@ -180,6 +182,8 @@ review-qa-all:
|
||||||
- .review-qa-base
|
- .review-qa-base
|
||||||
- .review:rules:review-qa-all
|
- .review:rules:review-qa-all
|
||||||
parallel: 5
|
parallel: 5
|
||||||
|
variables:
|
||||||
|
QA_RUN_TYPE: review-qa-all
|
||||||
script:
|
script:
|
||||||
- export KNAPSACK_REPORT_PATH=knapsack/master_report.json
|
- export KNAPSACK_REPORT_PATH=knapsack/master_report.json
|
||||||
- export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
|
- export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
|
||||||
|
|
|
@ -8,7 +8,9 @@ import CiLint from './components/ci_lint.vue';
|
||||||
Vue.use(VueApollo);
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
const apolloProvider = new VueApollo({
|
||||||
defaultClient: createDefaultClient(resolvers),
|
defaultClient: createDefaultClient(resolvers, {
|
||||||
|
assumeImmutableResults: true,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default (containerId = '#js-ci-lint') => {
|
export default (containerId = '#js-ci-lint') => {
|
||||||
|
|
|
@ -90,17 +90,18 @@ export default {
|
||||||
showMore() {
|
showMore() {
|
||||||
this.$emit('showMore');
|
this.$emit('showMore');
|
||||||
},
|
},
|
||||||
generateRowNumber(id) {
|
generateRowNumber(path, id, index) {
|
||||||
|
const key = `${path}-${id}-${index}`;
|
||||||
if (!this.glFeatures.lazyLoadCommits) {
|
if (!this.glFeatures.lazyLoadCommits) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.rowNumbers[id] && this.rowNumbers[id] !== 0) {
|
if (!this.rowNumbers[key] && this.rowNumbers[key] !== 0) {
|
||||||
this.$options.totalRowsLoaded += 1;
|
this.$options.totalRowsLoaded += 1;
|
||||||
this.rowNumbers[id] = this.$options.totalRowsLoaded;
|
this.rowNumbers[key] = this.$options.totalRowsLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.rowNumbers[id];
|
return this.rowNumbers[key];
|
||||||
},
|
},
|
||||||
getCommit(fileName, type) {
|
getCommit(fileName, type) {
|
||||||
if (!this.glFeatures.lazyLoadCommits) {
|
if (!this.glFeatures.lazyLoadCommits) {
|
||||||
|
@ -150,7 +151,7 @@ export default {
|
||||||
:lfs-oid="entry.lfsOid"
|
:lfs-oid="entry.lfsOid"
|
||||||
:loading-path="loadingPath"
|
:loading-path="loadingPath"
|
||||||
:total-entries="totalEntries"
|
:total-entries="totalEntries"
|
||||||
:row-number="generateRowNumber(entry.id)"
|
:row-number="generateRowNumber(entry.flatPath, entry.id, index)"
|
||||||
:commit-info="getCommit(entry.name, entry.type)"
|
:commit-info="getCommit(entry.name, entry.type)"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -35,13 +35,17 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.rulesLeft.length) {
|
if (!this.rulesLeft.length) {
|
||||||
return n__('Requires approval.', 'Requires %d more approvals.', this.approvalsLeft);
|
return n__(
|
||||||
|
'Requires %d approval from eligible users.',
|
||||||
|
'Requires %d approvals from eligible users.',
|
||||||
|
this.approvalsLeft,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
n__(
|
n__(
|
||||||
'Requires approval from %{names}.',
|
'Requires %{count} approval from %{names}.',
|
||||||
'Requires %{count} more approvals from %{names}.',
|
'Requires %{count} approvals from %{names}.',
|
||||||
this.approvalsLeft,
|
this.approvalsLeft,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex mr-source-target gl-mb-3">
|
<div class="gl-display-flex mr-source-target">
|
||||||
<mr-widget-icon name="git-merge" />
|
<mr-widget-icon name="git-merge" />
|
||||||
<div class="git-merge-container d-flex">
|
<div class="git-merge-container d-flex">
|
||||||
<div class="normal">
|
<div class="normal">
|
||||||
|
|
|
@ -25,19 +25,15 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
||||||
def index
|
def index
|
||||||
@sort = params[:sort].presence || sort_value_name
|
@sort = params[:sort].presence || sort_value_name
|
||||||
|
|
||||||
@members = GroupMembersFinder
|
|
||||||
.new(@group, current_user, params: filter_params)
|
|
||||||
.execute(include_relations: requested_relations)
|
|
||||||
|
|
||||||
if can?(current_user, :admin_group_member, @group)
|
if can?(current_user, :admin_group_member, @group)
|
||||||
@skip_groups = @group.related_group_ids
|
@skip_groups = @group.related_group_ids
|
||||||
|
|
||||||
@invited_members = @members.invite
|
@invited_members = invited_members
|
||||||
@invited_members = @invited_members.search_invite_email(params[:search_invited]) if params[:search_invited].present?
|
@invited_members = @invited_members.search_invite_email(params[:search_invited]) if params[:search_invited].present?
|
||||||
@invited_members = present_invited_members(@invited_members)
|
@invited_members = present_invited_members(@invited_members)
|
||||||
end
|
end
|
||||||
|
|
||||||
@members = present_group_members(@members.non_invite)
|
@members = present_group_members(non_invited_members)
|
||||||
|
|
||||||
@requesters = present_members(
|
@requesters = present_members(
|
||||||
AccessRequestsFinder.new(@group).execute(current_user)
|
AccessRequestsFinder.new(@group).execute(current_user)
|
||||||
|
@ -51,6 +47,20 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def group_members
|
||||||
|
@group_members ||= GroupMembersFinder
|
||||||
|
.new(@group, current_user, params: filter_params)
|
||||||
|
.execute(include_relations: requested_relations)
|
||||||
|
end
|
||||||
|
|
||||||
|
def invited_members
|
||||||
|
group_members.invite
|
||||||
|
end
|
||||||
|
|
||||||
|
def non_invited_members
|
||||||
|
group_members.non_invite
|
||||||
|
end
|
||||||
|
|
||||||
def present_invited_members(invited_members)
|
def present_invited_members(invited_members)
|
||||||
present_members(invited_members
|
present_members(invited_members
|
||||||
.page(params[:invited_members_page])
|
.page(params[:invited_members_page])
|
||||||
|
|
|
@ -19,16 +19,12 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
||||||
@group_links = @project.project_group_links
|
@group_links = @project.project_group_links
|
||||||
@group_links = @group_links.search(params[:search_groups]) if params[:search_groups].present?
|
@group_links = @group_links.search(params[:search_groups]) if params[:search_groups].present?
|
||||||
|
|
||||||
project_members = MembersFinder
|
|
||||||
.new(@project, current_user, params: filter_params)
|
|
||||||
.execute(include_relations: requested_relations)
|
|
||||||
|
|
||||||
if can?(current_user, :admin_project_member, @project)
|
if can?(current_user, :admin_project_member, @project)
|
||||||
@invited_members = present_members(project_members.invite)
|
@invited_members = present_members(invited_members)
|
||||||
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
|
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
|
||||||
end
|
end
|
||||||
|
|
||||||
@project_members = present_members(project_members.non_invite.page(params[:page]))
|
@project_members = present_members(non_invited_members.page(params[:page]))
|
||||||
|
|
||||||
@project_member = @project.project_members.new
|
@project_member = @project.project_members.new
|
||||||
end
|
end
|
||||||
|
@ -55,6 +51,20 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def members
|
||||||
|
@members ||= MembersFinder
|
||||||
|
.new(@project, current_user, params: filter_params)
|
||||||
|
.execute(include_relations: requested_relations)
|
||||||
|
end
|
||||||
|
|
||||||
|
def invited_members
|
||||||
|
members.invite
|
||||||
|
end
|
||||||
|
|
||||||
|
def non_invited_members
|
||||||
|
members.non_invite
|
||||||
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.permit(:search).merge(sort: @sort)
|
params.permit(:search).merge(sort: @sort)
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,6 +45,11 @@ class RegistrationsController < Devise::RegistrationsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
if current_user.required_terms_not_accepted?
|
||||||
|
redirect_to profile_account_path, status: :see_other, alert: s_('Profiles|You must accept the Terms of Service in order to perform this action.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if destroy_confirmation_valid?
|
if destroy_confirmation_valid?
|
||||||
current_user.delete_async(deleted_by: current_user)
|
current_user.delete_async(deleted_by: current_user)
|
||||||
session.try(:destroy)
|
session.try(:destroy)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Resolvers
|
||||||
|
class BoardListResolver < BaseResolver.single
|
||||||
|
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||||
|
include BoardItemFilterable
|
||||||
|
|
||||||
|
type Types::BoardListType, null: true
|
||||||
|
description 'Find an issue board list.'
|
||||||
|
|
||||||
|
authorize :read_issue_board_list
|
||||||
|
|
||||||
|
argument :id, Types::GlobalIDType[List],
|
||||||
|
required: true,
|
||||||
|
description: 'Global ID of the list.'
|
||||||
|
|
||||||
|
argument :issue_filters, Types::Boards::BoardIssueInputType,
|
||||||
|
required: false,
|
||||||
|
description: 'Filters applied when getting issue metadata in the board list.'
|
||||||
|
|
||||||
|
def resolve(id: nil, issue_filters: {})
|
||||||
|
context.scoped_set!(:issue_filters, item_filters(issue_filters))
|
||||||
|
|
||||||
|
Gitlab::Graphql::Lazy.with_value(find_list(id: id)) do |list|
|
||||||
|
list if authorized_resource?(list)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_list(id:)
|
||||||
|
GitlabSchema.object_from_id(id, expected_type: ::List)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,6 +47,19 @@ module Types
|
||||||
.metadata
|
.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# board lists have a data dependency on label - so we batch load them here
|
||||||
|
def title
|
||||||
|
if object.association(:label).loaded? && object.label_id.present?
|
||||||
|
object.title
|
||||||
|
else
|
||||||
|
loader = Gitlab::Graphql::Loaders::BatchModelLoader.new(Label, object.label_id)
|
||||||
|
Gitlab::Graphql::Lazy.with_value(loader.find) do |label|
|
||||||
|
object.label = label
|
||||||
|
object.title
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
# rubocop: enable Graphql/AuthorizeTypes
|
# rubocop: enable Graphql/AuthorizeTypes
|
||||||
end
|
end
|
||||||
|
|
|
@ -136,6 +136,10 @@ module Types
|
||||||
complexity: 5,
|
complexity: 5,
|
||||||
resolver: ::Resolvers::TimelogResolver
|
resolver: ::Resolvers::TimelogResolver
|
||||||
|
|
||||||
|
field :board_list, ::Types::BoardListType,
|
||||||
|
null: true,
|
||||||
|
resolver: Resolvers::BoardListResolver
|
||||||
|
|
||||||
def design_management
|
def design_management
|
||||||
DesignManagementObject.new(nil)
|
DesignManagementObject.new(nil)
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,8 +28,8 @@ class Environment < ApplicationRecord
|
||||||
|
|
||||||
has_one :last_deployment, -> { success.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
|
has_one :last_deployment, -> { success.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
|
||||||
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
|
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
|
||||||
has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: -> { ::Feature.enabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml) }
|
has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true
|
||||||
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: -> { ::Feature.enabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml) }
|
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true
|
||||||
|
|
||||||
has_one :upcoming_deployment, -> { running.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
|
has_one :upcoming_deployment, -> { running.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
|
||||||
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
|
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
|
||||||
|
@ -198,14 +198,14 @@ class Environment < ApplicationRecord
|
||||||
|
|
||||||
# Overriding association
|
# Overriding association
|
||||||
def last_visible_deployable
|
def last_visible_deployable
|
||||||
return super if association_cached?(:last_visible_deployable) || ::Feature.disabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml)
|
return super if association_cached?(:last_visible_deployable)
|
||||||
|
|
||||||
last_visible_deployment&.deployable
|
last_visible_deployment&.deployable
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overriding association
|
# Overriding association
|
||||||
def last_visible_pipeline
|
def last_visible_pipeline
|
||||||
return super if association_cached?(:last_visible_pipeline) || ::Feature.disabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml)
|
return super if association_cached?(:last_visible_pipeline)
|
||||||
|
|
||||||
last_visible_deployable&.pipeline
|
last_visible_deployable&.pipeline
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ListPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
|
||||||
|
delegate { @subject.board.resource_parent }
|
||||||
|
end
|
|
@ -3,10 +3,15 @@
|
||||||
module Ci
|
module Ci
|
||||||
class ArchiveTraceService
|
class ArchiveTraceService
|
||||||
def execute(job, worker_name:)
|
def execute(job, worker_name:)
|
||||||
|
unless job.trace.archival_attempts_available?
|
||||||
|
Sidekiq.logger.warn(class: worker_name, message: 'The job is out of archival attempts.', job_id: job.id)
|
||||||
|
|
||||||
|
job.trace.attempt_archive_cleanup!
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
unless job.trace.can_attempt_archival_now?
|
unless job.trace.can_attempt_archival_now?
|
||||||
Sidekiq.logger.warn(class: worker_name,
|
Sidekiq.logger.warn(class: worker_name, message: 'The job can not be archived right now.', job_id: job.id)
|
||||||
message: job.trace.archival_attempts_message,
|
|
||||||
job_id: job.id)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
%p
|
%p
|
||||||
= _('Configure the %{link} integration.').html_safe % { link: link_to(_('Mailgun events'), 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', target: '_blank') }
|
= _('Configure the %{link} integration.').html_safe % { link: link_to(_('Mailgun events'), 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', target: '_blank', rel: 'noopener noreferrer') }
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-mailgun-settings'), html: { class: 'fieldset-form', id: 'mailgun-settings' } do |f|
|
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-mailgun-settings'), html: { class: 'fieldset-form', id: 'mailgun-settings' } do |f|
|
||||||
= form_errors(@application_setting) if expanded
|
= form_errors(@application_setting) if expanded
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
= expanded ? _('Collapse') : _('Expand')
|
= expanded ? _('Collapse') : _('Expand')
|
||||||
%p
|
%p
|
||||||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('development/snowplow/index') }
|
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('development/snowplow/index') }
|
||||||
= html_escape(_('Configure %{link} to track events. %{link_start}Learn more.%{link_end}')) % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank').html_safe, link_start: link_start, link_end: '</a>'.html_safe }
|
= html_escape(_('Configure %{link} to track events. %{link_start}Learn more.%{link_end}')) % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank', rel: 'noopener noreferrer').html_safe, link_start: link_start, link_end: '</a>'.html_safe }
|
||||||
.settings-content
|
.settings-content
|
||||||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form', id: 'snowplow-settings' } do |f|
|
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form', id: 'snowplow-settings' } do |f|
|
||||||
= form_errors(@application_setting) if expanded
|
= form_errors(@application_setting) if expanded
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: environment_last_visible_pipeline_disable_joins
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68870
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340283
|
|
||||||
milestone: '14.3'
|
|
||||||
type: development
|
|
||||||
group: group::release
|
|
||||||
default_enabled: true
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CleanupDeleteOrphanedDeploymentsBackgroundMigration < Gitlab::Database::Migration[1.0]
|
||||||
|
MIGRATION = 'DeleteOrphanedDeployments'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
finalize_background_migration(MIGRATION)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# no-op
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
5701681a1006584149c88da520f780b186ca32ba1facb8b952252c6d426b6c0d
|
|
@ -34,7 +34,7 @@ GET /projects/:id/dependencies?package_manager=yarn,bundler
|
||||||
| Attribute | Type | Required | Description |
|
| Attribute | Type | Required | Description |
|
||||||
| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). |
|
||||||
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `maven`, `npm`, `pip` or `yarn`. |
|
| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `conan`, `go`, `maven`, `npm`, `nuget`, `pip`, `yarn`, or `sbt`. |
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/dependencies"
|
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/dependencies"
|
||||||
|
|
|
@ -36,6 +36,19 @@ in [Removed Items](../removed_items.md).
|
||||||
|
|
||||||
The `Query` type contains the API's top-level entry points for all executable queries.
|
The `Query` type contains the API's top-level entry points for all executable queries.
|
||||||
|
|
||||||
|
### `Query.boardList`
|
||||||
|
|
||||||
|
Find an issue board list.
|
||||||
|
|
||||||
|
Returns [`BoardList`](#boardlist).
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="queryboardlistid"></a>`id` | [`ListID!`](#listid) | Global ID of the list. |
|
||||||
|
| <a id="queryboardlistissuefilters"></a>`issueFilters` | [`BoardIssueInput`](#boardissueinput) | Filters applied when getting issue metadata in the board list. |
|
||||||
|
|
||||||
### `Query.ciApplicationSettings`
|
### `Query.ciApplicationSettings`
|
||||||
|
|
||||||
CI related settings that apply to the entire instance.
|
CI related settings that apply to the entire instance.
|
||||||
|
|
|
@ -275,7 +275,8 @@ You can verify if the MR was deployed to GitLab.com by executing
|
||||||
`/chatops run auto_deploy status <merge_sha>`. To verify existence of
|
`/chatops run auto_deploy status <merge_sha>`. To verify existence of
|
||||||
the index, you can:
|
the index, you can:
|
||||||
|
|
||||||
- Use a meta-command in #database-lab, such as: `\di <index_name>`
|
- Use a meta-command in #database-lab, such as: `\d <index_name>`
|
||||||
|
- Ensure that the index is not [`invalid`](https://www.postgresql.org/docs/12/sql-createindex.html#:~:text=The%20psql%20%5Cd%20command%20will%20report%20such%20an%20index%20as%20INVALID)
|
||||||
- Ask someone in #database to check if the index exists
|
- Ask someone in #database to check if the index exists
|
||||||
- With proper access, you can also verify directly on production or in a
|
- With proper access, you can also verify directly on production or in a
|
||||||
production clone
|
production clone
|
||||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
type: howto
|
type: howto
|
||||||
---
|
---
|
||||||
|
|
||||||
# Activating GitLab EE
|
# Activating GitLab EE **(PREMIUM SELF)**
|
||||||
|
|
||||||
To enable features of GitLab Enterprise Edition (EE), you need to activate your instance. Ensure you are running an enterprise edition. To verify, sign in to GitLab and browse to `/help`. The GitLab edition and version are listed at the top of the **Help** page.
|
To enable features of GitLab Enterprise Edition (EE), you need to activate your instance. Ensure you are running an enterprise edition. To verify, sign in to GitLab and browse to `/help`. The GitLab edition and version are listed at the top of the **Help** page.
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ As of GitLab Enterprise Edition 9.4.0, a newly-installed instance without an
|
||||||
uploaded license only has the Free features active. A trial license activates all Ultimate features, but after [the trial expires](#what-happens-when-your-license-expires), some functionality
|
uploaded license only has the Free features active. A trial license activates all Ultimate features, but after [the trial expires](#what-happens-when-your-license-expires), some functionality
|
||||||
is locked.
|
is locked.
|
||||||
|
|
||||||
## Activate GitLab EE with an Activation Code **(PREMIUM SELF)**
|
## Activate GitLab EE with an Activation Code
|
||||||
|
|
||||||
As of GitLab Enterprise Edition 14.1, you need an activation code to activate your instance. You can obtain an activation code by [purchasing a license](https://about.gitlab.com/pricing/) or by signing up for a [free trial](https://about.gitlab.com/free-trial/). This activation code is a 24-character alphanumeric string you receive in a confirmation email. You can also sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) to copy the activation code to your clipboard.
|
As of GitLab Enterprise Edition 14.1, you need an activation code to activate your instance. You can obtain an activation code by [purchasing a license](https://about.gitlab.com/pricing/) or by signing up for a [free trial](https://about.gitlab.com/free-trial/). This activation code is a 24-character alphanumeric string you receive in a confirmation email. You can also sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) to copy the activation code to your clipboard.
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ To begin the activation process with your activation code:
|
||||||
1. Read and accept the terms of service.
|
1. Read and accept the terms of service.
|
||||||
1. Select **Activate**.
|
1. Select **Activate**.
|
||||||
|
|
||||||
## Activate GitLab EE with a License File **(PREMIUM SELF)**
|
## Activate GitLab EE with a License File
|
||||||
|
|
||||||
If you receive a license file from GitLab (for example a new trial), you can upload it by signing into your GitLab instance as an administrator or adding it during installation. The license is a base64-encoded ASCII text file with a `.gitlab-license` extension.
|
If you receive a license file from GitLab (for example a new trial), you can upload it by signing into your GitLab instance as an administrator or adding it during installation. The license is a base64-encoded ASCII text file with a `.gitlab-license` extension.
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ module Gitlab
|
||||||
|
|
||||||
delegate :old_trace, to: :job
|
delegate :old_trace, to: :job
|
||||||
delegate :can_attempt_archival_now?, :increment_archival_attempts!,
|
delegate :can_attempt_archival_now?, :increment_archival_attempts!,
|
||||||
:archival_attempts_message, to: :trace_metadata
|
:archival_attempts_message, :archival_attempts_available?, to: :trace_metadata
|
||||||
|
|
||||||
def initialize(job)
|
def initialize(job)
|
||||||
@job = job
|
@job = job
|
||||||
|
@ -122,6 +122,10 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attempt_archive_cleanup!
|
||||||
|
destroy_any_orphan_trace_data!
|
||||||
|
end
|
||||||
|
|
||||||
def update_interval
|
def update_interval
|
||||||
if being_watched?
|
if being_watched?
|
||||||
UPDATE_FREQUENCY_WHEN_BEING_WATCHED
|
UPDATE_FREQUENCY_WHEN_BEING_WATCHED
|
||||||
|
@ -191,7 +195,10 @@ module Gitlab
|
||||||
def unsafe_archive!
|
def unsafe_archive!
|
||||||
raise ArchiveError, 'Job is not finished yet' unless job.complete?
|
raise ArchiveError, 'Job is not finished yet' unless job.complete?
|
||||||
|
|
||||||
unsafe_trace_conditionally_cleanup_before_retry!
|
already_archived?.tap do |archived|
|
||||||
|
destroy_any_orphan_trace_data!
|
||||||
|
raise AlreadyArchivedError, 'Could not archive again' if archived
|
||||||
|
end
|
||||||
|
|
||||||
if job.trace_chunks.any?
|
if job.trace_chunks.any?
|
||||||
Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream|
|
Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream|
|
||||||
|
@ -214,16 +221,15 @@ module Gitlab
|
||||||
def already_archived?
|
def already_archived?
|
||||||
# TODO check checksum to ensure archive completed successfully
|
# TODO check checksum to ensure archive completed successfully
|
||||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/259619
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/259619
|
||||||
trace_artifact.archived_trace_exists?
|
trace_artifact&.archived_trace_exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsafe_trace_conditionally_cleanup_before_retry!
|
def destroy_any_orphan_trace_data!
|
||||||
return unless trace_artifact
|
return unless trace_artifact
|
||||||
|
|
||||||
if already_archived?
|
if already_archived?
|
||||||
# An archive already exists, so make sure to remove the trace chunks
|
# An archive already exists, so make sure to remove the trace chunks
|
||||||
erase_trace_chunks!
|
erase_trace_chunks!
|
||||||
raise AlreadyArchivedError, 'Could not archive again'
|
|
||||||
else
|
else
|
||||||
# An archive already exists, but its associated file does not, so remove it
|
# An archive already exists, but its associated file does not, so remove it
|
||||||
trace_artifact.destroy!
|
trace_artifact.destroy!
|
||||||
|
|
|
@ -11,6 +11,12 @@ module Gitlab
|
||||||
# balancing is enabled, but no replicas have been configured (= the
|
# balancing is enabled, but no replicas have been configured (= the
|
||||||
# default case).
|
# default case).
|
||||||
class PrimaryHost
|
class PrimaryHost
|
||||||
|
WAL_ERROR_MESSAGE = <<~MSG.strip
|
||||||
|
Obtaining WAL information when not using any replicas results in
|
||||||
|
redundant queries, and may break installations that don't support
|
||||||
|
streaming replication (e.g. AWS' Aurora database).
|
||||||
|
MSG
|
||||||
|
|
||||||
def initialize(load_balancer)
|
def initialize(load_balancer)
|
||||||
@load_balancer = load_balancer
|
@load_balancer = load_balancer
|
||||||
end
|
end
|
||||||
|
@ -51,30 +57,16 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_write_location
|
def primary_write_location
|
||||||
@load_balancer.primary_write_location
|
raise NotImplementedError, WAL_ERROR_MESSAGE
|
||||||
end
|
end
|
||||||
|
|
||||||
def database_replica_location
|
def database_replica_location
|
||||||
row = query_and_release(<<-SQL.squish)
|
raise NotImplementedError, WAL_ERROR_MESSAGE
|
||||||
SELECT pg_last_wal_replay_lsn()::text AS location
|
|
||||||
SQL
|
|
||||||
|
|
||||||
row['location'] if row.any?
|
|
||||||
rescue *Host::CONNECTION_ERRORS
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def caught_up?(_location)
|
def caught_up?(_location)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def query_and_release(sql)
|
|
||||||
connection.select_all(sql).first || {}
|
|
||||||
rescue StandardError
|
|
||||||
{}
|
|
||||||
ensure
|
|
||||||
release_connection
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,6 +42,9 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def wal_location_for(load_balancer)
|
def wal_location_for(load_balancer)
|
||||||
|
# When only using the primary there's no need for any WAL queries.
|
||||||
|
return if load_balancer.primary_only?
|
||||||
|
|
||||||
if ::Gitlab::Database::LoadBalancing::Session.current.use_primary?
|
if ::Gitlab::Database::LoadBalancing::Session.current.use_primary?
|
||||||
load_balancer.primary_write_location
|
load_balancer.primary_write_location
|
||||||
else
|
else
|
||||||
|
|
|
@ -104,6 +104,10 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_primary_write_location
|
def with_primary_write_location
|
||||||
|
# When only using the primary, there's no point in getting write
|
||||||
|
# locations, as the primary is always in sync with itself.
|
||||||
|
return if @load_balancer.primary_only?
|
||||||
|
|
||||||
location = @load_balancer.primary_write_location
|
location = @load_balancer.primary_write_location
|
||||||
|
|
||||||
return if location.blank?
|
return if location.blank?
|
||||||
|
|
|
@ -26312,6 +26312,9 @@ msgstr ""
|
||||||
msgid "Profiles|You don't have access to delete this user."
|
msgid "Profiles|You don't have access to delete this user."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Profiles|You must accept the Terms of Service in order to perform this action."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account"
|
msgid "Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -28973,13 +28976,13 @@ msgstr ""
|
||||||
msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
|
msgid "Requirements can be based on users, stakeholders, system, software, or anything else you find important to capture."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Requires approval from %{names}."
|
msgid "Requires %d approval from eligible users."
|
||||||
msgid_plural "Requires %{count} more approvals from %{names}."
|
msgid_plural "Requires %d approvals from eligible users."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "Requires approval."
|
msgid "Requires %{count} approval from %{names}."
|
||||||
msgid_plural "Requires %d more approvals."
|
msgid_plural "Requires %{count} approvals from %{names}."
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
@ -40209,6 +40212,9 @@ msgstr ""
|
||||||
msgid "element is not a hierarchy"
|
msgid "element is not a hierarchy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "eligible users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "email '%{email}' is not a verified email."
|
msgid "email '%{email}' is not a verified email."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -55,9 +55,9 @@
|
||||||
"@babel/preset-env": "^7.10.1",
|
"@babel/preset-env": "^7.10.1",
|
||||||
"@gitlab/at.js": "1.5.7",
|
"@gitlab/at.js": "1.5.7",
|
||||||
"@gitlab/favicon-overlay": "2.0.0",
|
"@gitlab/favicon-overlay": "2.0.0",
|
||||||
"@gitlab/svgs": "1.215.0",
|
"@gitlab/svgs": "1.218.0",
|
||||||
"@gitlab/tributejs": "1.0.0",
|
"@gitlab/tributejs": "1.0.0",
|
||||||
"@gitlab/ui": "32.18.0",
|
"@gitlab/ui": "32.19.1",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
"@rails/actioncable": "6.1.4-1",
|
"@rails/actioncable": "6.1.4-1",
|
||||||
"@rails/ujs": "6.1.4-1",
|
"@rails/ujs": "6.1.4-1",
|
||||||
|
|
|
@ -20,11 +20,6 @@ module Gitlab
|
||||||
def additional_limits
|
def additional_limits
|
||||||
additional_minutes_usage[%r{([^/ ]+)$}]
|
additional_minutes_usage[%r{([^/ ]+)$}]
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Refactor/Remove this method once https://gitlab.com/gitlab-org/quality/chemlab/-/merge_requests/28 is merged
|
|
||||||
def additional_minutes_exist?
|
|
||||||
has_element?(:strong, :additional_minutes, text: 'Additional minutes')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -602,6 +602,22 @@ RSpec.describe RegistrationsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user did not accept app terms' do
|
||||||
|
let(:user) { create(:user, accepted_term: nil) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(password_authentication_enabled_for_web: false)
|
||||||
|
stub_application_setting(password_authentication_enabled_for_git: false)
|
||||||
|
stub_application_setting(enforce_terms: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails with message' do
|
||||||
|
post :destroy, params: { username: user.username }
|
||||||
|
|
||||||
|
expect_failure(s_('Profiles|You must accept the Terms of Service in order to perform this action.'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'sets the username and caller_id in the context' do
|
it 'sets the username and caller_id in the context' do
|
||||||
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
|
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
|
||||||
m.call(*args)
|
m.call(*args)
|
||||||
|
|
|
@ -61,9 +61,7 @@ describe('MRWidget approvals summary', () => {
|
||||||
it('render message', () => {
|
it('render message', () => {
|
||||||
const names = toNounSeriesText(testRulesLeft());
|
const names = toNounSeriesText(testRulesLeft());
|
||||||
|
|
||||||
expect(wrapper.text()).toContain(
|
expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} approvals from ${names}.`);
|
||||||
`Requires ${TEST_APPROVALS_LEFT} more approvals from ${names}.`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,7 +73,9 @@ describe('MRWidget approvals summary', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders message', () => {
|
it('renders message', () => {
|
||||||
expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} more approvals.`);
|
expect(wrapper.text()).toContain(
|
||||||
|
`Requires ${TEST_APPROVALS_LEFT} approvals from eligible users`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Resolvers::BoardListResolver do
|
||||||
|
include GraphqlHelpers
|
||||||
|
include Gitlab::Graphql::Laziness
|
||||||
|
|
||||||
|
let_it_be(:guest) { create(:user) }
|
||||||
|
let_it_be(:unauth_user) { create(:user) }
|
||||||
|
let_it_be(:group) { create(:group, :private) }
|
||||||
|
let_it_be(:group_label) { create(:group_label, group: group, name: 'Development') }
|
||||||
|
let_it_be(:board) { create(:board, resource_parent: group) }
|
||||||
|
let_it_be(:label_list) { create(:list, board: board, label: group_label) }
|
||||||
|
|
||||||
|
describe '#resolve' do
|
||||||
|
subject { resolve_board_list(args: { id: global_id_of(label_list) }, current_user: current_user) }
|
||||||
|
|
||||||
|
context 'with unauthorized user' do
|
||||||
|
let(:current_user) { unauth_user }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when authorized' do
|
||||||
|
let(:current_user) { guest }
|
||||||
|
|
||||||
|
before do
|
||||||
|
group.add_guest(guest)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq label_list }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_board_list(args: {}, current_user: user)
|
||||||
|
force(resolve(described_class, obj: nil, args: args, ctx: { current_user: current_user }))
|
||||||
|
end
|
||||||
|
end
|
|
@ -27,6 +27,7 @@ RSpec.describe GitlabSchema.types['Query'] do
|
||||||
runner
|
runner
|
||||||
runners
|
runners
|
||||||
timelogs
|
timelogs
|
||||||
|
board_list
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(described_class).to have_graphql_fields(*expected_fields).at_least
|
expect(described_class).to have_graphql_fields(*expected_fields).at_least
|
||||||
|
@ -136,4 +137,14 @@ RSpec.describe GitlabSchema.types['Query'] do
|
||||||
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
is_expected.to have_graphql_resolver(Resolvers::TimelogResolver)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'boardList field' do
|
||||||
|
subject { described_class.fields['boardList'] }
|
||||||
|
|
||||||
|
it 'finds a board list by its gid' do
|
||||||
|
is_expected.to have_graphql_arguments(:id, :issue_filters)
|
||||||
|
is_expected.to have_graphql_type(Types::BoardListType)
|
||||||
|
is_expected.to have_graphql_resolver(Resolvers::BoardListResolver)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,9 +63,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#primary_write_location' do
|
describe '#primary_write_location' do
|
||||||
it 'returns the write location of the primary' do
|
it 'raises NotImplementedError' do
|
||||||
expect(host.primary_write_location).to be_an_instance_of(String)
|
expect { host.primary_write_location }.to raise_error(NotImplementedError)
|
||||||
expect(host.primary_write_location).not_to be_empty
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,51 +75,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#database_replica_location' do
|
describe '#database_replica_location' do
|
||||||
let(:connection) { double(:connection) }
|
it 'raises NotImplementedError' do
|
||||||
|
expect { host.database_replica_location }.to raise_error(NotImplementedError)
|
||||||
it 'returns the write ahead location of the replica', :aggregate_failures do
|
|
||||||
expect(host)
|
|
||||||
.to receive(:query_and_release)
|
|
||||||
.and_return({ 'location' => '0/D525E3A8' })
|
|
||||||
|
|
||||||
expect(host.database_replica_location).to be_an_instance_of(String)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns nil when the database query returned no rows' do
|
|
||||||
expect(host).to receive(:query_and_release).and_return({})
|
|
||||||
|
|
||||||
expect(host.database_replica_location).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns nil when the database connection fails' do
|
|
||||||
allow(host).to receive(:connection).and_raise(PG::Error)
|
|
||||||
|
|
||||||
expect(host.database_replica_location).to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#query_and_release' do
|
|
||||||
it 'executes a SQL query' do
|
|
||||||
results = host.query_and_release('SELECT 10 AS number')
|
|
||||||
|
|
||||||
expect(results).to be_an_instance_of(Hash)
|
|
||||||
expect(results['number'].to_i).to eq(10)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'releases the connection after running the query' do
|
|
||||||
expect(host)
|
|
||||||
.to receive(:release_connection)
|
|
||||||
.once
|
|
||||||
|
|
||||||
host.query_and_release('SELECT 10 AS number')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns an empty Hash in the event of an error' do
|
|
||||||
expect(host.connection)
|
|
||||||
.to receive(:select_all)
|
|
||||||
.and_raise(RuntimeError, 'kittens')
|
|
||||||
|
|
||||||
expect(host.query_and_release('SELECT 10 AS number')).to eq({})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
|
||||||
middleware.call(worker_class, job, nil, nil) {}
|
middleware.call(worker_class, job, nil, nil) {}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call', :database_replica do
|
||||||
shared_context 'data consistency worker class' do |data_consistency, feature_flag|
|
shared_context 'data consistency worker class' do |data_consistency, feature_flag|
|
||||||
let(:expected_consistency) { data_consistency }
|
let(:expected_consistency) { data_consistency }
|
||||||
let(:worker_class) do
|
let(:worker_class) do
|
||||||
|
|
|
@ -188,6 +188,10 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sticks an entity to the primary', :aggregate_failures do
|
it 'sticks an entity to the primary', :aggregate_failures do
|
||||||
|
allow(ActiveRecord::Base.connection.load_balancer)
|
||||||
|
.to receive(:primary_only?)
|
||||||
|
.and_return(false)
|
||||||
|
|
||||||
ids.each do |id|
|
ids.each do |id|
|
||||||
expect(sticking)
|
expect(sticking)
|
||||||
.to receive(:set_write_location_for)
|
.to receive(:set_write_location_for)
|
||||||
|
@ -199,6 +203,12 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
|
||||||
|
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not update the write location when no replicas are used' do
|
||||||
|
expect(sticking).not_to receive(:set_write_location_for)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#stick' do
|
describe '#stick' do
|
||||||
|
@ -221,12 +231,22 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
|
||||||
.to receive(:primary_write_location)
|
.to receive(:primary_write_location)
|
||||||
.and_return('foo')
|
.and_return('foo')
|
||||||
|
|
||||||
|
allow(ActiveRecord::Base.connection.load_balancer)
|
||||||
|
.to receive(:primary_only?)
|
||||||
|
.and_return(false)
|
||||||
|
|
||||||
expect(sticking)
|
expect(sticking)
|
||||||
.to receive(:set_write_location_for)
|
.to receive(:set_write_location_for)
|
||||||
.with(:user, 42, 'foo')
|
.with(:user, 42, 'foo')
|
||||||
|
|
||||||
sticking.mark_primary_write_location(:user, 42)
|
sticking.mark_primary_write_location(:user, 42)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does nothing when no replicas are used' do
|
||||||
|
expect(sticking).not_to receive(:set_write_location_for)
|
||||||
|
|
||||||
|
sticking.mark_primary_write_location(:user, 42)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#unstick' do
|
describe '#unstick' do
|
||||||
|
|
|
@ -2791,17 +2791,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
||||||
extra_update_queries = 4 # transition ... => :canceled, queue pop
|
extra_update_queries = 4 # transition ... => :canceled, queue pop
|
||||||
extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
|
extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
|
||||||
|
|
||||||
# The number of extra load balancing queries depends on whether or not
|
expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
|
||||||
# we use a load balancer for CI. That in turn depends on the contents of
|
|
||||||
# database.yml, so here we support both cases.
|
|
||||||
extra_load_balancer_queries =
|
|
||||||
if Gitlab::Database.has_config?(:ci)
|
|
||||||
6
|
|
||||||
else
|
|
||||||
3
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries + extra_load_balancer_queries)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -801,38 +801,6 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
|
||||||
expect(query_count).to eq(0)
|
expect(query_count).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the feature for disable_join is disabled' do
|
|
||||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
|
||||||
let(:ci_build) { create(:ci_build, project: project, pipeline: pipeline) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_feature_flags(environment_last_visible_pipeline_disable_joins: false)
|
|
||||||
create(:deployment, :failed, project: project, environment: environment, deployable: ci_build)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for preload' do
|
|
||||||
it 'executes the original association instead of override' do
|
|
||||||
environment.reload
|
|
||||||
ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_deployable: []])
|
|
||||||
|
|
||||||
expect_any_instance_of(Deployment).not_to receive(:deployable)
|
|
||||||
|
|
||||||
query_count = ActiveRecord::QueryRecorder.new do
|
|
||||||
expect(subject.id).to eq(ci_build.id)
|
|
||||||
end.count
|
|
||||||
|
|
||||||
expect(query_count).to eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for direct call' do
|
|
||||||
it 'executes the original association instead of override' do
|
|
||||||
expect_any_instance_of(Deployment).not_to receive(:deployable)
|
|
||||||
expect(subject.id).to eq(ci_build.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#last_visible_pipeline' do
|
describe '#last_visible_pipeline' do
|
||||||
|
@ -963,40 +931,6 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
|
||||||
expect(query_count).to eq(0)
|
expect(query_count).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the feature for disable_join is disabled' do
|
|
||||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
|
||||||
let(:ci_build) { create(:ci_build, project: project, pipeline: pipeline) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_feature_flags(environment_last_visible_pipeline_disable_joins: false)
|
|
||||||
create(:deployment, :failed, project: project, environment: environment, deployable: ci_build)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { environment.last_visible_pipeline }
|
|
||||||
|
|
||||||
context 'for preload' do
|
|
||||||
it 'executes the original association instead of override' do
|
|
||||||
environment.reload
|
|
||||||
ActiveRecord::Associations::Preloader.new.preload(environment, [last_visible_pipeline: []])
|
|
||||||
|
|
||||||
expect_any_instance_of(Ci::Build).not_to receive(:pipeline)
|
|
||||||
|
|
||||||
query_count = ActiveRecord::QueryRecorder.new do
|
|
||||||
expect(subject.id).to eq(pipeline.id)
|
|
||||||
end.count
|
|
||||||
|
|
||||||
expect(query_count).to eq(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for direct call' do
|
|
||||||
it 'executes the original association instead of override' do
|
|
||||||
expect_any_instance_of(Ci::Build).not_to receive(:pipeline)
|
|
||||||
expect(subject.id).to eq(pipeline.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#upcoming_deployment' do
|
describe '#upcoming_deployment' do
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Querying a Board list' do
|
||||||
|
include GraphqlHelpers
|
||||||
|
|
||||||
|
let_it_be(:current_user) { create(:user) }
|
||||||
|
let_it_be(:project) { create(:project) }
|
||||||
|
let_it_be(:board) { create(:board, resource_parent: project) }
|
||||||
|
let_it_be(:label) { create(:label, project: project, name: 'foo') }
|
||||||
|
let_it_be(:list) { create(:list, board: board, label: label) }
|
||||||
|
let_it_be(:issue1) { create(:issue, project: project, labels: [label]) }
|
||||||
|
let_it_be(:issue2) { create(:issue, project: project, labels: [label], assignees: [current_user]) }
|
||||||
|
|
||||||
|
let(:filters) { {} }
|
||||||
|
let(:query) do
|
||||||
|
graphql_query_for(
|
||||||
|
:board_list,
|
||||||
|
{ id: list.to_global_id.to_s, issueFilters: filters },
|
||||||
|
%w[title issuesCount]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { graphql_data['boardList'] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
post_graphql(query, current_user: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has access to the list' do
|
||||||
|
before_all do
|
||||||
|
project.add_guest(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'a working graphql query'
|
||||||
|
|
||||||
|
it { is_expected.to include({ 'issuesCount' => 2, 'title' => list.title }) }
|
||||||
|
|
||||||
|
context 'with matching issue filters' do
|
||||||
|
let(:filters) { { assigneeUsername: current_user.username } }
|
||||||
|
|
||||||
|
it 'filters issues metadata' do
|
||||||
|
is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unmatching issue filters' do
|
||||||
|
let(:filters) { { assigneeUsername: 'foo' } }
|
||||||
|
|
||||||
|
it 'filters issues metadata' do
|
||||||
|
is_expected.to include({ 'issuesCount' => 0, 'title' => list.title })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user does not have access to the list' do
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ID argument is missing' do
|
||||||
|
let(:query) do
|
||||||
|
graphql_query_for('boardList', {}, 'title')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an exception' do
|
||||||
|
expect(graphql_errors).to include(a_hash_including('message' => "Field 'boardList' is missing required arguments: id"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when list ID is not found' do
|
||||||
|
let(:query) do
|
||||||
|
graphql_query_for('boardList', { id: "gid://gitlab/List/#{non_existing_record_id}" }, 'title')
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not have an N+1 performance issue' do
|
||||||
|
a, b = create_list(:list, 2, board: board)
|
||||||
|
ctx = { current_user: current_user }
|
||||||
|
project.add_guest(current_user)
|
||||||
|
|
||||||
|
baseline = graphql_query_for(:board_list, { id: global_id_of(a) }, 'title')
|
||||||
|
query = <<~GQL
|
||||||
|
query {
|
||||||
|
a: #{query_graphql_field(:board_list, { id: global_id_of(a) }, 'title')}
|
||||||
|
b: #{query_graphql_field(:board_list, { id: global_id_of(b) }, 'title')}
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
|
||||||
|
control = ActiveRecord::QueryRecorder.new do
|
||||||
|
run_with_clean_state(baseline, context: ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { run_with_clean_state(query, context: ctx) }.not_to exceed_query_limit(control)
|
||||||
|
end
|
||||||
|
end
|
|
@ -88,6 +88,32 @@ RSpec.describe Ci::ArchiveTraceService, '#execute' do
|
||||||
|
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'job has archive and chunks' do
|
||||||
|
let(:job) { create(:ci_build, :success, :trace_artifact) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:ci_build_trace_chunk, build: job, chunk_index: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'archive is not completed' do
|
||||||
|
before do
|
||||||
|
job.job_artifacts_trace.file.remove!
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'cleanups any stale archive data' do
|
||||||
|
expect(job.job_artifacts_trace).to be_present
|
||||||
|
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(job.reload.job_artifacts_trace).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes trace chunks' do
|
||||||
|
expect { subject }.to change { job.trace_chunks.count }.to(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the archival process is backed off' do
|
context 'when the archival process is backed off' do
|
||||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -904,23 +904,23 @@
|
||||||
stylelint-declaration-strict-value "1.7.7"
|
stylelint-declaration-strict-value "1.7.7"
|
||||||
stylelint-scss "3.18.0"
|
stylelint-scss "3.18.0"
|
||||||
|
|
||||||
"@gitlab/svgs@1.215.0":
|
"@gitlab/svgs@1.218.0":
|
||||||
version "1.215.0"
|
version "1.218.0"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.215.0.tgz#f2760bbb0a38b26346e1b755e63fb63eba005edd"
|
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.218.0.tgz#0715e2ef50b5cb83813e1a5e29d5919a96685734"
|
||||||
integrity sha512-/bc0+EOYPQlPCMbfyOkMLxDKBn+ewEBlmTRmFwf7mXvfIRszdJPY8XCx/fJIEQwDr8+k4E28ktFnLZGnaFhCnw==
|
integrity sha512-eckixyumeWogykEUZfP4pGjoRdhdWQIFwSTM0ks5tQqza+BikcL2xvxzicJs69T1IiCKwYtEpR1c3T/hSx39Mg==
|
||||||
|
|
||||||
"@gitlab/tributejs@1.0.0":
|
"@gitlab/tributejs@1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||||
|
|
||||||
"@gitlab/ui@32.18.0":
|
"@gitlab/ui@32.19.1":
|
||||||
version "32.18.0"
|
version "32.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.18.0.tgz#cd340f050fe0183218f6233328aca2369bd6e449"
|
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.19.1.tgz#ab54408272cb5ee695dc0a328892e047da3d41ac"
|
||||||
integrity sha512-bDMmsNB9VMBX2JbezyJWfk02t0aFfAT9Ez4ALTDUJLb5/Q9GKByfE5sLycms6L1aZxzP6r1jypnu5DD0eT92eg==
|
integrity sha512-ooc0TwCvREuWJfvn8EbOkEz1Mh4UKEu7x0MKhD+TBjG+JJwLKDClmD1cPPE05BXtWAvW5W9JUBkaeMCVQG2l3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/standalone" "^7.0.0"
|
"@babel/standalone" "^7.0.0"
|
||||||
bootstrap-vue "2.19.0"
|
bootstrap-vue "2.20.1"
|
||||||
copy-to-clipboard "^3.0.8"
|
copy-to-clipboard "^3.0.8"
|
||||||
dompurify "^2.3.3"
|
dompurify "^2.3.3"
|
||||||
echarts "^4.9.0"
|
echarts "^4.9.0"
|
||||||
|
@ -2758,10 +2758,10 @@ boolbase@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||||
|
|
||||||
bootstrap-vue@2.19.0:
|
bootstrap-vue@2.20.1:
|
||||||
version "2.19.0"
|
version "2.20.1"
|
||||||
resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.19.0.tgz#5019df48251e552a5c34da57fc97dabebd53b02f"
|
resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.20.1.tgz#1b6cd4368632c1a6dd4a5ed161242baa131c3cd5"
|
||||||
integrity sha512-IjAXUSrRU5Qu9x3uwUcoj6LtysKbCVeWoJOsODyI/WokStUr95M+tTIajXUjIrB/Nsk0fS+RNvZnm2sWeNFrhg==
|
integrity sha512-s+w83q0T2mo/RbFwTM8gExbLJMEOYpdTUqmyFaHv2Ir+TFprMvTWpeAzeNuawJ130W1gePZ3LW3cNp1t/tZbOw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nuxt/opencollective" "^0.3.2"
|
"@nuxt/opencollective" "^0.3.2"
|
||||||
bootstrap ">=4.5.3 <5.0.0"
|
bootstrap ">=4.5.3 <5.0.0"
|
||||||
|
|
Loading…
Reference in New Issue