Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0024c2f444
commit
aed0a60015
|
@ -303,12 +303,6 @@ That's all of the required database changes.
|
|||
git_access_class.error_message(:no_repo)
|
||||
end
|
||||
|
||||
# The feature flag follows the format `geo_#{replicable_name}_replication`,
|
||||
# so here it would be `geo_cool_widget_replication`
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
|
||||
override :verification_feature_flag_enabled?
|
||||
def self.verification_feature_flag_enabled?
|
||||
# We are adding verification at the same time as replication, so we
|
||||
|
@ -715,27 +709,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
|
|||
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
|
||||
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
|
||||
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
|
||||
|
||||
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
|
||||
|
||||
```ruby
|
||||
module Geo
|
||||
class CoolWidgetReplicator < Gitlab::Geo::Replicator
|
||||
...
|
||||
# REMOVE THIS LINE IF IT IS NO LONGER NEEDED
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
# REMOVE THIS METHOD
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
# REMOVE THIS METHOD
|
||||
|
||||
...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
|
||||
|
||||
```ruby
|
||||
|
|
|
@ -291,12 +291,6 @@ That's all of the required database changes.
|
|||
model_record.file
|
||||
end
|
||||
|
||||
# The feature flag follows the format `geo_#{replicable_name}_replication`,
|
||||
# so here it would be `geo_cool_widget_replication`
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
|
||||
override :verification_feature_flag_enabled?
|
||||
def self.verification_feature_flag_enabled?
|
||||
# We are adding verification at the same time as replication, so we
|
||||
|
@ -680,28 +674,6 @@ As illustrated by the above two examples, batch destroy logic cannot be handled
|
|||
- [ ] Add a step to `Test replication and verification of Cool Widgets on a non-GDK-deployment. For example, using GitLab Environment Toolkit`.
|
||||
- [ ] Add a step to `Ping the Geo PM and EM to coordinate testing`. For example, you might add steps to generate Cool Widgets, and then a Geo engineer may take it from there.
|
||||
- [ ] In `ee/config/feature_flags/development/geo_cool_widget_replication.yml`, set `default_enabled: true`
|
||||
|
||||
- [ ] In `ee/app/replicators/geo/cool_widget_replicator.rb`, delete the `self.replication_enabled_by_default?` method:
|
||||
|
||||
```ruby
|
||||
module Geo
|
||||
class CoolWidgetReplicator < Gitlab::Geo::Replicator
|
||||
...
|
||||
# REMOVE THIS LINE IF IT IS NO LONGER NEEDED
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
...
|
||||
# REMOVE THIS METHOD
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
# REMOVE THIS METHOD
|
||||
|
||||
...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
- [ ] In `ee/app/graphql/types/geo/geo_node_type.rb`, remove the `feature_flag` option for the released type:
|
||||
|
||||
```ruby
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
## Author's checklist
|
||||
|
||||
- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4).
|
||||
- [ ] Optional. Consider taking [the GitLab Technical Writing Fundamentals course](https://about.gitlab.com/handbook/engineering/ux/technical-writing/fundamentals/).
|
||||
- [ ] Follow the:
|
||||
- [Documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html).
|
||||
- [Documentation guidelines](https://docs.gitlab.com/ee/development/documentation/).
|
||||
|
|
|
@ -39,7 +39,7 @@ export default {
|
|||
configHelpLink: helpPagePath('user/clusters/agent/install/index', {
|
||||
anchor: 'create-an-agent-configuration-file',
|
||||
}),
|
||||
inject: ['gitlabVersion'],
|
||||
inject: ['gitlabVersion', 'kasVersion'],
|
||||
props: {
|
||||
agents: {
|
||||
required: true,
|
||||
|
@ -102,6 +102,9 @@ export default {
|
|||
return { ...agent, versions };
|
||||
});
|
||||
},
|
||||
serverVersion() {
|
||||
return this.kasVersion || this.gitlabVersion;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getStatusCellId(item) {
|
||||
|
@ -135,12 +138,12 @@ export default {
|
|||
if (!agent.versions.length) return false;
|
||||
|
||||
const [agentMajorVersion, agentMinorVersion] = this.getAgentVersionString(agent).split('.');
|
||||
const [gitlabMajorVersion, gitlabMinorVersion] = this.gitlabVersion.split('.');
|
||||
const [serverMajorVersion, serverMinorVersion] = this.serverVersion.split('.');
|
||||
|
||||
const majorVersionMismatch = agentMajorVersion !== gitlabMajorVersion;
|
||||
const majorVersionMismatch = agentMajorVersion !== serverMajorVersion;
|
||||
|
||||
// We should warn user if their current GitLab and agent versions are more than 1 minor version apart:
|
||||
const minorVersionMismatch = Math.abs(agentMinorVersion - gitlabMinorVersion) > 1;
|
||||
const minorVersionMismatch = Math.abs(agentMinorVersion - serverMinorVersion) > 1;
|
||||
|
||||
return majorVersionMismatch || minorVersionMismatch;
|
||||
},
|
||||
|
@ -240,7 +243,7 @@ export default {
|
|||
|
||||
<p class="gl-mb-0">
|
||||
<gl-sprintf :message="$options.i18n.versionOutdatedText">
|
||||
<template #version>{{ gitlabVersion }}</template>
|
||||
<template #version>{{ serverVersion }}</template>
|
||||
</gl-sprintf>
|
||||
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
|
||||
{{ $options.i18n.viewDocsText }}</gl-link
|
||||
|
@ -253,7 +256,7 @@ export default {
|
|||
|
||||
<p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
|
||||
<gl-sprintf :message="$options.i18n.versionOutdatedText">
|
||||
<template #version>{{ gitlabVersion }}</template>
|
||||
<template #version>{{ serverVersion }}</template>
|
||||
</gl-sprintf>
|
||||
<gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
|
||||
{{ $options.i18n.viewDocsText }}</gl-link
|
||||
|
|
|
@ -30,6 +30,7 @@ export default () => {
|
|||
canAddCluster,
|
||||
canAdminCluster,
|
||||
gitlabVersion,
|
||||
kasVersion,
|
||||
displayClusterAgents,
|
||||
certificateBasedClustersEnabled,
|
||||
} = el.dataset;
|
||||
|
@ -48,6 +49,7 @@ export default () => {
|
|||
canAddCluster: parseBoolean(canAddCluster),
|
||||
canAdminCluster: parseBoolean(canAdminCluster),
|
||||
gitlabVersion,
|
||||
kasVersion,
|
||||
displayClusterAgents: parseBoolean(displayClusterAgents),
|
||||
certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import { s__, __ } from '~/locale';
|
||||
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
@ -179,7 +179,10 @@ export default {
|
|||
if (shouldConfirm && isDirty) {
|
||||
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
|
||||
|
||||
const confirmed = await confirmAction(msg);
|
||||
const confirmed = await confirmAction(msg, {
|
||||
primaryBtnText: __('Discard changes'),
|
||||
cancelBtnText: __('Continue editing'),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { GlModal, GlSafeHtmlDirective } from '@gitlab/ui';
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
cancelAction: { text: __('Cancel') },
|
||||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
},
|
||||
|
@ -36,6 +35,16 @@ export default {
|
|||
required: false,
|
||||
default: 'confirm',
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('Cancel'),
|
||||
},
|
||||
cancelVariant: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'default',
|
||||
},
|
||||
modalHtmlMessage: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -71,7 +80,14 @@ export default {
|
|||
};
|
||||
},
|
||||
cancelAction() {
|
||||
return this.hideCancel ? null : this.$options.cancelAction;
|
||||
return this.hideCancel
|
||||
? null
|
||||
: {
|
||||
text: this.cancelText,
|
||||
attributes: {
|
||||
variant: this.cancelVariant,
|
||||
},
|
||||
};
|
||||
},
|
||||
shouldShowHeader() {
|
||||
return Boolean(this.title?.length);
|
||||
|
|
|
@ -7,6 +7,8 @@ export function confirmAction(
|
|||
primaryBtnText,
|
||||
secondaryBtnVariant,
|
||||
secondaryBtnText,
|
||||
cancelBtnVariant,
|
||||
cancelBtnText,
|
||||
modalHtmlMessage,
|
||||
title,
|
||||
hideCancel,
|
||||
|
@ -28,6 +30,8 @@ export function confirmAction(
|
|||
secondaryVariant: secondaryBtnVariant,
|
||||
primaryVariant: primaryBtnVariant,
|
||||
primaryText: primaryBtnText,
|
||||
cancelVariant: cancelBtnVariant,
|
||||
cancelText: cancelBtnText,
|
||||
title,
|
||||
modalHtmlMessage,
|
||||
hideCancel,
|
||||
|
|
|
@ -176,7 +176,10 @@ export default {
|
|||
if (shouldConfirm && isDirty) {
|
||||
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
|
||||
|
||||
const confirmed = await confirmAction(msg);
|
||||
const confirmed = await confirmAction(msg, {
|
||||
primaryBtnText: __('Discard changes'),
|
||||
cancelBtnText: __('Continue editing'),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
|
|
|
@ -15,5 +15,8 @@ export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
|
|||
export const SINGLE_JOB = 'single_job';
|
||||
export const JOB_DROPDOWN = 'job_dropdown';
|
||||
|
||||
export const BUILD_KIND = 'BUILD';
|
||||
export const BRIDGE_KIND = 'BRIDGE';
|
||||
|
||||
export const ACTION_FAILURE = 'action_failure';
|
||||
export const IID_FAILURE = 'missing_iid';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
|
||||
import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
|
||||
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
|
@ -7,7 +7,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
|||
import { reportToSentry } from '../../utils';
|
||||
import ActionComponent from '../jobs_shared/action_component.vue';
|
||||
import JobNameComponent from '../jobs_shared/job_name_component.vue';
|
||||
import { SINGLE_JOB } from './constants';
|
||||
import { BRIDGE_KIND, SINGLE_JOB } from './constants';
|
||||
|
||||
/**
|
||||
* Renders the badge for the pipeline graph and the job's dropdown.
|
||||
|
@ -35,11 +35,16 @@ import { SINGLE_JOB } from './constants';
|
|||
*/
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
bridgeBadgeText: __('Trigger job'),
|
||||
unauthorizedTooltip: __('You are not authorized to run this manual job'),
|
||||
},
|
||||
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
|
||||
components: {
|
||||
ActionComponent,
|
||||
CiIcon,
|
||||
JobNameComponent,
|
||||
GlBadge,
|
||||
GlLink,
|
||||
},
|
||||
directives: {
|
||||
|
@ -113,6 +118,12 @@ export default {
|
|||
isSingleItem() {
|
||||
return this.type === SINGLE_JOB;
|
||||
},
|
||||
isBridge() {
|
||||
return this.kind === BRIDGE_KIND;
|
||||
},
|
||||
kind() {
|
||||
return this.job?.kind || '';
|
||||
},
|
||||
nameComponent() {
|
||||
return this.hasDetails ? 'gl-link' : 'div';
|
||||
},
|
||||
|
@ -187,6 +198,7 @@ export default {
|
|||
[this.$options.hoverClass]:
|
||||
this.relatedDownstreamHovered || this.relatedDownstreamExpanded,
|
||||
},
|
||||
{ 'gl-rounded-lg': this.isBridge },
|
||||
this.cssClassJobName,
|
||||
];
|
||||
},
|
||||
|
@ -213,9 +225,6 @@ export default {
|
|||
this.$emit('pipelineActionRequestComplete');
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
unauthorizedTooltip: __('You are not authorized to run this manual job'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -253,6 +262,9 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<gl-badge v-if="isBridge" class="gl-mt-3" variant="info" size="sm">
|
||||
{{ $options.i18n.bridgeBadgeText }}
|
||||
</gl-badge>
|
||||
</component>
|
||||
|
||||
<action-component
|
||||
|
|
|
@ -111,7 +111,7 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
flash_type = result.error? ? :alert : :notice
|
||||
flash[flash_type] = result.message
|
||||
|
||||
redirect_to project_branches_path(@project), status: :see_other
|
||||
redirect_back_or_default(default: project_branches_path(@project), options: { status: :see_other })
|
||||
end
|
||||
|
||||
format.js { head result.http_status }
|
||||
|
|
|
@ -102,6 +102,7 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
|
|||
__typename
|
||||
id
|
||||
name
|
||||
kind
|
||||
scheduledAt
|
||||
needs {
|
||||
__typename
|
||||
|
|
|
@ -26,7 +26,8 @@ module ClustersHelper
|
|||
default_branch_name: default_branch_name(clusterable),
|
||||
project_path: clusterable_project_path(clusterable),
|
||||
kas_address: Gitlab::Kas.external_url,
|
||||
gitlab_version: Gitlab.version_info
|
||||
gitlab_version: Gitlab.version_info,
|
||||
kas_version: Gitlab::Kas.version_info
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -5,25 +5,50 @@ module Preloaders
|
|||
# stores the values in requests store via the ProjectTeam class.
|
||||
class UserMaxAccessLevelInProjectsPreloader
|
||||
def initialize(projects, user)
|
||||
@projects = projects
|
||||
@projects = if projects.is_a?(Array)
|
||||
Project.where(id: projects)
|
||||
else
|
||||
# Push projects base query in to a sub-select to avoid
|
||||
# table name clashes. Performs better than aliasing.
|
||||
Project.where(id: projects.reselect(:id))
|
||||
end
|
||||
|
||||
@user = user
|
||||
end
|
||||
|
||||
def execute
|
||||
# Use reselect to override the existing select to prevent
|
||||
# the error `subquery has too many columns`
|
||||
# NotificationsController passes in an Array so we need to check the type
|
||||
project_ids = @projects.is_a?(ActiveRecord::Relation) ? @projects.reselect(:id) : @projects
|
||||
access_levels = @user
|
||||
.project_authorizations
|
||||
.where(project_id: project_ids)
|
||||
.group(:project_id)
|
||||
.maximum(:access_level)
|
||||
project_authorizations = ProjectAuthorization.arel_table
|
||||
|
||||
@projects.each do |project|
|
||||
access_level = access_levels[project.id] || Gitlab::Access::NO_ACCESS
|
||||
auths = @projects
|
||||
.select(
|
||||
Project.default_select_columns,
|
||||
project_authorizations[:user_id],
|
||||
project_authorizations[:access_level]
|
||||
)
|
||||
.joins(project_auth_join)
|
||||
|
||||
auths.each do |project|
|
||||
access_level = project.access_level || Gitlab::Access::NO_ACCESS
|
||||
ProjectTeam.new(project).write_member_access_for_user_id(@user.id, access_level)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_auth_join
|
||||
project_authorizations = ProjectAuthorization.arel_table
|
||||
projects = Project.arel_table
|
||||
|
||||
projects
|
||||
.join(
|
||||
project_authorizations.as(project_authorizations.name),
|
||||
Arel::Nodes::OuterJoin
|
||||
)
|
||||
.on(
|
||||
project_authorizations[:project_id].eq(projects[:id])
|
||||
.and(project_authorizations[:user_id].eq(@user.id))
|
||||
)
|
||||
.join_sources
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -239,6 +239,8 @@ class User < ApplicationRecord
|
|||
|
||||
has_many :timelogs
|
||||
|
||||
has_many :resource_label_events, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
|
|
@ -63,10 +63,7 @@ module Users
|
|||
# destroying: https://github.com/rails/rails/issues/22510
|
||||
# This ensures we delete records in batches.
|
||||
user.destroy_dependent_associations_in_batches(exclude: [:snippets])
|
||||
|
||||
if Feature.enabled?(:nullify_in_batches_on_user_deletion)
|
||||
user.nullify_dependent_associations_in_batches
|
||||
end
|
||||
user.nullify_dependent_associations_in_batches
|
||||
|
||||
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
|
||||
user_data = user.destroy
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
|
||||
.devise-errors
|
||||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
.form-group.gl-px-5.gl-pt-5
|
||||
= f.label :email
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, autocomplete: 'off', value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
||||
.form-text.text-muted
|
||||
= _('Requires your primary GitLab email address.')
|
||||
|
||||
%div
|
||||
- if recaptcha_enabled?
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
.gl-px-5
|
||||
= recaptcha_tags nonce: content_security_policy_nonce
|
||||
|
||||
.gl-mt-5
|
||||
.gl-p-5
|
||||
= f.submit _("Reset password"), class: "gl-button btn-confirm btn"
|
||||
|
||||
.clearfix.prepend-top-20
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
= gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors js-sign-in-form', aria: { live: 'assertive' }, data: { testid: 'sign-in-form' }}) do |f|
|
||||
.form-group.gl-px-5.gl-pt-5
|
||||
= f.label _('Username or email'), for: 'user_login', class: 'label-bold'
|
||||
= render_if_exists 'devise/sessions/new_base_user_login_label', form: f
|
||||
= f.text_field :login, value: @invite_email, class: 'form-control gl-form-input top js-username-field', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field', testid: 'username-field' }
|
||||
.form-group.gl-px-5
|
||||
= f.label :password, class: 'label-bold'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
= local_assigns[:form].label _('Username or email'), for: 'user_login', class: 'label-bold'
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: 'nullify_in_batches_on_user_deletion'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84709
|
||||
rollout_issue_url:
|
||||
milestone: '14.10'
|
||||
type: development
|
||||
group: group::optimize
|
||||
default_enabled: true
|
|
@ -0,0 +1,16 @@
|
|||
- name: "Elasticsearch 6.8.x in GitLab 15.0"
|
||||
announcement_milestone: "14.8"
|
||||
announcement_date: "2022-02-22"
|
||||
removal_milestone: "15.0"
|
||||
removal_date: "2022-05-22"
|
||||
breaking_change: true
|
||||
reporter: JohnMcGuire
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
Elasticsearch 6.8 support has been removed in GitLab 15.0. Elasticsearch 6.8 has reached [end of life](https://www.elastic.co/support/eol).
|
||||
If you use Elasticsearch 6.8, **you must upgrade your Elasticsearch version to 7.x** prior to upgrading to GitLab 15.0.
|
||||
You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15.0 upgrade.
|
||||
|
||||
View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
|
||||
# The following items are not published on the docs page, but may be used in the future.
|
||||
stage: "Enablement"
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350275
|
|
@ -144,7 +144,7 @@ You can optionally track progress and verify that all packages migrated successf
|
|||
Verify `objectstg` below (where `file_store=2`) has count of all states:
|
||||
|
||||
```shell
|
||||
gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM terraform_states;
|
||||
gitlabhq_production=# SELECT count(*) AS total, sum(case when file_store = '1' then 1 else 0 end) AS filesystem, sum(case when file_store = '2' then 1 else 0 end) AS objectstg FROM terraform_state_versions;
|
||||
|
||||
total | filesystem | objectstg
|
||||
------+------------+-----------
|
||||
|
@ -154,7 +154,7 @@ total | filesystem | objectstg
|
|||
Verify that there are no files on disk in the `terraform_state` folder:
|
||||
|
||||
```shell
|
||||
sudo find /var/opt/gitlab/gitlab-rails/shared/terraform_state -type f | wc -l
|
||||
sudo find /var/opt/gitlab/gitlab-rails/shared/terraform_state -type f | grep -v tmp | wc -l
|
||||
```
|
||||
|
||||
### S3-compatible connection settings
|
||||
|
|
|
@ -93,12 +93,6 @@ module Geo
|
|||
def self.model
|
||||
::Packages::PackageFile
|
||||
end
|
||||
|
||||
# The feature flag follows the format `geo_#{replicable_name}_replication`,
|
||||
# so here it would be `geo_package_file_replication`
|
||||
def self.replication_enabled_by_default?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
|
@ -247,12 +247,18 @@ The `* as-if-jh` jobs are run in addition to the regular EE-context jobs. The `j
|
|||
|
||||
The intent is to ensure that a change doesn't introduce a failure after `gitlab-org/gitlab` is synced to [GitLab JH](https://jihulab.com/gitlab-cn/gitlab).
|
||||
|
||||
### When to consider applying `pipeline:run-as-if-jh` label
|
||||
|
||||
If a Ruby file is renamed and there's a corresponding [`prepend_mod` line](jh_features_review.md#jh-features-based-on-ce-or-ee-features),
|
||||
it's likely that GitLab JH is relying on it and requires a corresponding
|
||||
change to rename the module or class it's prepending.
|
||||
|
||||
### Corresponding JH branch
|
||||
|
||||
You can create a corresponding JH branch on [GitLab JH](https://jihulab.com/gitlab-cn/gitlab) by
|
||||
appending `-jh` to the branch name. If a corresponding JH branch is found,
|
||||
`* as-if-jh` jobs grab the `jh` folder from the respective branch,
|
||||
rather than from the default branch.
|
||||
rather than from the default branch `main-jh`.
|
||||
|
||||
NOTE:
|
||||
For now, CI will try to fetch the branch on the [GitLab JH mirror](https://gitlab.com/gitlab-org/gitlab-jh-mirrors/gitlab), so it might take some time for the new JH branch to propagate to the mirror.
|
||||
|
|
|
@ -116,6 +116,20 @@ The following `geo:db:*` tasks have been removed from GitLab 15.0 and have been
|
|||
- `geo:db:test:load` -> `db:test:load:geo`
|
||||
- `geo:db:test:purge` -> `db:test:purge:geo`
|
||||
|
||||
### Elasticsearch 6.8.x in GitLab 15.0
|
||||
|
||||
WARNING:
|
||||
This feature was changed or removed in 15.0
|
||||
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
|
||||
Before updating GitLab, review the details carefully to determine if you need to make any
|
||||
changes to your code, settings, or workflow.
|
||||
|
||||
Elasticsearch 6.8 support has been removed in GitLab 15.0. Elasticsearch 6.8 has reached [end of life](https://www.elastic.co/support/eol).
|
||||
If you use Elasticsearch 6.8, **you must upgrade your Elasticsearch version to 7.x** prior to upgrading to GitLab 15.0.
|
||||
You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15.0 upgrade.
|
||||
|
||||
View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
|
||||
|
||||
### GitLab Serverless
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -292,14 +292,12 @@ scan images from within your Kubernetes cluster and record the vulnerabilities i
|
|||
|
||||
### Prerequisites
|
||||
|
||||
- [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/)
|
||||
installed and configured in your cluster.
|
||||
- [GitLab agent](../../clusters/agent/install/index.md)
|
||||
set up in GitLab, installed in your cluster, and configured using a configuration repository.
|
||||
|
||||
### Configuration
|
||||
|
||||
The agent runs the cluster image scanning once the `cluster_image_scanning`
|
||||
The agent runs the cluster image scanning once the `starboard`
|
||||
directive is added to your [agent's configuration repository](../../clusters/agent/vulnerabilities.md).
|
||||
|
||||
## Security Dashboard
|
||||
|
|
|
@ -244,7 +244,7 @@ table.supported-languages ul {
|
|||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">Python</td>
|
||||
<td rowspan="3">3.6</td>
|
||||
<td rowspan="3">3.9</td>
|
||||
<td><a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a></td>
|
||||
<td><code>setup.py</code></td>
|
||||
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
|
||||
|
@ -915,9 +915,9 @@ import the following default dependency scanning analyzer images from `registry.
|
|||
your [local Docker container registry](../../packages/container_registry/index.md):
|
||||
|
||||
```plaintext
|
||||
registry.gitlab.com/security-products/gemnasium:2
|
||||
registry.gitlab.com/security-products/gemnasium-maven:2
|
||||
registry.gitlab.com/security-products/gemnasium-python:2
|
||||
registry.gitlab.com/security-products/gemnasium:3
|
||||
registry.gitlab.com/security-products/gemnasium-maven:3
|
||||
registry.gitlab.com/security-products/gemnasium-python:3
|
||||
```
|
||||
|
||||
The process for importing Docker images into a local offline Docker registry depends on
|
||||
|
@ -1219,5 +1219,4 @@ To work around this error, downgrade the analyzer's version of `setuptools` (e.g
|
|||
gemnasium-python-dependency_scanning:
|
||||
before_script:
|
||||
- pip install setuptools==57.5.0
|
||||
image: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python:2-python-3.9
|
||||
```
|
||||
|
|
|
@ -87,6 +87,13 @@ This rule enforces the defined actions and schedules a scan on the provided date
|
|||
| `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. |
|
||||
| `clusters` | `object` | | The cluster where the given policy enforces running selected scans (only for `container_scanning`/`cluster_image_scanning` scans). The key of the object is the name of the Kubernetes cluster configured for your project in GitLab. In the optionally provided value of the object, you can precisely select Kubernetes resources that are scanned. |
|
||||
|
||||
GitLab supports the following types of CRON syntax for the `cadence` field:
|
||||
|
||||
- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
|
||||
- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
|
||||
|
||||
It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
|
||||
|
||||
### `cluster` schema
|
||||
|
||||
Use this schema to define `clusters` objects in the [`schedule` rule type](#schedule-rule-type).
|
||||
|
|
|
@ -11,7 +11,7 @@ When you are using the GitLab agent for Kubernetes, you might experience issues
|
|||
You can start by viewing the service logs:
|
||||
|
||||
```shell
|
||||
kubectl logs -f -l=app=gitlab-agent -n gitlab-kubernetes-agent
|
||||
kubectl logs -f -l=app=gitlab-agent -n gitlab-agent
|
||||
```
|
||||
|
||||
If you are a GitLab administrator, you can also view the [GitLab agent server logs](../../../administration/clusters/kas.md#troubleshooting).
|
||||
|
@ -113,14 +113,14 @@ will be picked up automatically.
|
|||
For example, if your internal CA certificate is `myCA.pem`:
|
||||
|
||||
```plaintext
|
||||
kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
|
||||
kubectl -n gitlab-agent create configmap ca-pemstore --from-file=myCA.pem
|
||||
```
|
||||
|
||||
Then in `resources.yml`:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
serviceAccountName: gitlab-kubernetes-agent
|
||||
serviceAccountName: gitlab-agent
|
||||
containers:
|
||||
- name: agent
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
|
||||
|
@ -140,7 +140,7 @@ Then in `resources.yml`:
|
|||
volumes:
|
||||
- name: token-volume
|
||||
secret:
|
||||
secretName: gitlab-kubernetes-agent-token
|
||||
secretName: gitlab-agent-token
|
||||
- name: ca-pemstore-volume
|
||||
configMap:
|
||||
name: ca-pemstore
|
||||
|
|
|
@ -34,80 +34,34 @@ You can use [cluster image scanning](../../application_security/cluster_image_sc
|
|||
to scan container images in your cluster for security vulnerabilities.
|
||||
|
||||
To begin scanning all resources in your cluster, add a `starboard`
|
||||
configuration block to your agent configuration file with no `filters`:
|
||||
configuration block to your agent configuration with a `cadence` field
|
||||
containing a CRON expression for when the scans will be run.
|
||||
|
||||
```yaml
|
||||
starboard:
|
||||
vulnerability_report:
|
||||
filters: []
|
||||
cadence: '0 0 * * *' # Daily at 00:00 (Kubernetes cluster time)
|
||||
```
|
||||
|
||||
The namespaces that are able to be scanned depend on the [Starboard Operator install mode](https://aquasecurity.github.io/starboard/latest/operator/configuration/#install-modes).
|
||||
By default, the Starboard Operator only scans resources in the `default` namespace. To change this
|
||||
behavior, edit the `STARBOARD_OPERATOR` environment variable in the `starboard-operator` deployment
|
||||
definition.
|
||||
The `cadence` field is required. GitLab supports the following types of CRON syntax for the cadence field:
|
||||
|
||||
By adding filters, you can limit scans by:
|
||||
- A daily cadence of once per hour at a specified hour, for example: `0 18 * * *`
|
||||
- A weekly cadence of once per week on a specified day and at a specified hour, for example: `0 13 * * 0`
|
||||
|
||||
- Resource name
|
||||
- Kind
|
||||
- Container name
|
||||
- Namespace
|
||||
|
||||
```yaml
|
||||
starboard:
|
||||
vulnerability_report:
|
||||
filters:
|
||||
- namespaces:
|
||||
- staging
|
||||
- production
|
||||
kinds:
|
||||
- Deployment
|
||||
- DaemonSet
|
||||
containers:
|
||||
- ruby
|
||||
- postgres
|
||||
- nginx
|
||||
resources:
|
||||
- my-app-name
|
||||
- postgres
|
||||
- ingress-nginx
|
||||
```
|
||||
|
||||
A resource is scanned if the resource matches any of the given names and all of the given filter
|
||||
types (`namespaces`, `kinds`, `containers`, `resources`). If a filter type is omitted, then all
|
||||
names are scanned. In this example, a resource isn't scanned unless it has a container named `ruby`,
|
||||
`postgres`, or `nginx`, and it's a `Deployment`:
|
||||
|
||||
```yaml
|
||||
starboard:
|
||||
vulnerability_report:
|
||||
filters:
|
||||
- kinds:
|
||||
- Deployment
|
||||
containers:
|
||||
- ruby
|
||||
- postgres
|
||||
- nginx
|
||||
```
|
||||
|
||||
There is also a global `namespaces` field that applies to all filters:
|
||||
It is possible that other elements of the CRON syntax will work in the cadence field, however, GitLab does not officially test or support them.
|
||||
|
||||
By default, cluster image scanning will attempt to scan the workloads in all
|
||||
namespaces for vulnerabilities. The `vulnerability_report` block has a `namespaces`
|
||||
field which can be used to restrict which namespaces are scanned. For example,
|
||||
if you would like to scan only the `development`, `staging`, and `production`
|
||||
namespaces, you can use this configuration:
|
||||
|
||||
```yaml
|
||||
starboard:
|
||||
vulnerability_report:
|
||||
cadence: '0 0 * * *'
|
||||
namespaces:
|
||||
- production
|
||||
filters:
|
||||
- kinds:
|
||||
- Deployment
|
||||
- kinds:
|
||||
- DaemonSet
|
||||
resources:
|
||||
- log-collector
|
||||
- development
|
||||
- staging
|
||||
- production
|
||||
```
|
||||
|
||||
In this example, the following resources are scanned:
|
||||
|
||||
- All deployments (`Deployment`) in the `production` namespace.
|
||||
- All daemon sets (`DaemonSet`) named `log-collector` in the `production` namespace.
|
||||
|
|
|
@ -31,7 +31,7 @@ pre-push:
|
|||
rubocop:
|
||||
tags: backend style
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
glob: '*.rb'
|
||||
glob: '*.{rb,rake}'
|
||||
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --force-exclusion {files}
|
||||
graphql_docs:
|
||||
tags: documentation
|
||||
|
|
|
@ -14,7 +14,7 @@ variables:
|
|||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"
|
||||
DS_EXCLUDED_ANALYZERS: ""
|
||||
DS_EXCLUDED_PATHS: "spec, test, tests, tmp"
|
||||
DS_MAJOR_VERSION: 2
|
||||
DS_MAJOR_VERSION: 3
|
||||
|
||||
dependency_scanning:
|
||||
stage: test
|
||||
|
@ -82,9 +82,6 @@ gemnasium-maven-dependency_scanning:
|
|||
- .cyclone-dx-reports
|
||||
variables:
|
||||
DS_ANALYZER_NAME: "gemnasium-maven"
|
||||
# Stop reporting Gradle as "maven".
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
|
||||
DS_REPORT_PACKAGE_MANAGER_MAVEN_WHEN_JAVA: "false"
|
||||
rules:
|
||||
- if: $DEPENDENCY_SCANNING_DISABLED
|
||||
when: never
|
||||
|
@ -104,9 +101,6 @@ gemnasium-python-dependency_scanning:
|
|||
- .cyclone-dx-reports
|
||||
variables:
|
||||
DS_ANALYZER_NAME: "gemnasium-python"
|
||||
# Stop reporting Pipenv and Setuptools as "pip".
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/338252
|
||||
DS_REPORT_PACKAGE_MANAGER_PIP_WHEN_PYTHON: "false"
|
||||
rules:
|
||||
- if: $DEPENDENCY_SCANNING_DISABLED
|
||||
when: never
|
||||
|
|
|
@ -33,6 +33,10 @@ module Gitlab
|
|||
@_version ||= Rails.root.join(VERSION_FILE).read.chomp
|
||||
end
|
||||
|
||||
def version_info
|
||||
Gitlab::VersionInfo.parse(version)
|
||||
end
|
||||
|
||||
# Return GitLab KAS external_url
|
||||
#
|
||||
# @return [String] external_url
|
||||
|
|
|
@ -3258,6 +3258,9 @@ msgstr ""
|
|||
msgid "Advanced export options"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdvancedSearch|Elasticsearch version not compatible"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdvancedSearch|Reindex required"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27187,6 +27190,9 @@ msgstr ""
|
|||
msgid "Pause"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pause indexing and upgrade Elasticsearch to a supported version."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pause time (ms)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39757,6 +39763,9 @@ msgstr ""
|
|||
msgid "Trigger cluster reindexing. Only use this with an index that was created in GitLab 13.0 or later."
|
||||
msgstr ""
|
||||
|
||||
msgid "Trigger job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Trigger manual job"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -307,17 +307,36 @@ RSpec.describe Projects::BranchesController do
|
|||
sign_in(developer)
|
||||
end
|
||||
|
||||
it 'returns 303' do
|
||||
post :destroy,
|
||||
format: :html,
|
||||
params: {
|
||||
id: 'foo/bar/baz',
|
||||
namespace_id: project.namespace,
|
||||
project_id: project
|
||||
}
|
||||
subject(:post_request) do
|
||||
post :destroy, format: :html, params: {
|
||||
id: 'foo/bar/baz',
|
||||
namespace_id: project.namespace,
|
||||
project_id: project
|
||||
}
|
||||
end
|
||||
|
||||
it "returns response code 303" do
|
||||
post_request
|
||||
expect(response).to have_gitlab_http_status(:see_other)
|
||||
end
|
||||
|
||||
context 'with http referer' do
|
||||
before do
|
||||
request.env['HTTP_REFERER'] = '/'
|
||||
end
|
||||
|
||||
it "redirects to the referer path" do
|
||||
post_request
|
||||
expect(response).to redirect_to('/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without http referer' do
|
||||
it "redirects to the project branches path" do
|
||||
post_request
|
||||
expect(response).to redirect_to(project_branches_path(project))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST destroy" do
|
||||
|
|
|
@ -445,7 +445,7 @@ RSpec.describe 'GFM autocomplete', :js do
|
|||
click_button('Cancel')
|
||||
|
||||
page.within('.modal') do
|
||||
click_button('OK', match: :first)
|
||||
click_button('Discard changes', match: :first)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
|
|
@ -13,6 +13,7 @@ const defaultConfigHelpUrl =
|
|||
|
||||
const provideData = {
|
||||
gitlabVersion: '14.8',
|
||||
kasVersion: '14.8',
|
||||
};
|
||||
const propsData = {
|
||||
agents: clusterAgents,
|
||||
|
@ -26,7 +27,7 @@ const outdatedTitle = I18N_AGENT_TABLE.versionOutdatedTitle;
|
|||
const mismatchTitle = I18N_AGENT_TABLE.versionMismatchTitle;
|
||||
const mismatchOutdatedTitle = I18N_AGENT_TABLE.versionMismatchOutdatedTitle;
|
||||
const outdatedText = sprintf(I18N_AGENT_TABLE.versionOutdatedText, {
|
||||
version: provideData.gitlabVersion,
|
||||
version: provideData.kasVersion,
|
||||
});
|
||||
const mismatchText = I18N_AGENT_TABLE.versionMismatchText;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "6",
|
||||
"kind": "BUILD",
|
||||
"name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -53,6 +54,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "11",
|
||||
"kind": "BUILD",
|
||||
"name": "build_b",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -95,6 +97,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "16",
|
||||
"kind": "BUILD",
|
||||
"name": "build_c",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -137,6 +140,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "21",
|
||||
"kind": "BUILD",
|
||||
"name": "build_d 1/3",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -163,6 +167,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "24",
|
||||
"kind": "BUILD",
|
||||
"name": "build_d 2/3",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -189,6 +194,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "27",
|
||||
"kind": "BUILD",
|
||||
"name": "build_d 3/3",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -231,6 +237,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "59",
|
||||
"kind": "BUILD",
|
||||
"name": "test_c",
|
||||
"needs": Array [],
|
||||
"previousStageJobsOrNeeds": Array [],
|
||||
|
@ -275,6 +282,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "34",
|
||||
"kind": "BUILD",
|
||||
"name": "test_a",
|
||||
"needs": Array [
|
||||
"build_c",
|
||||
|
@ -325,6 +333,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "42",
|
||||
"kind": "BUILD",
|
||||
"name": "test_b 1/2",
|
||||
"needs": Array [
|
||||
"build_d 3/3",
|
||||
|
@ -363,6 +372,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "67",
|
||||
"kind": "BUILD",
|
||||
"name": "test_b 2/2",
|
||||
"needs": Array [
|
||||
"build_d 3/3",
|
||||
|
@ -417,6 +427,7 @@ Array [
|
|||
Object {
|
||||
"__typename": "CiJob",
|
||||
"id": "53",
|
||||
"kind": "BUILD",
|
||||
"name": "test_d",
|
||||
"needs": Array [
|
||||
"build_b",
|
||||
|
|
|
@ -1,89 +1,34 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { GlBadge } from '@gitlab/ui';
|
||||
import JobItem from '~/pipelines/components/graph/job_item.vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import {
|
||||
delayedJob,
|
||||
mockJob,
|
||||
mockJobWithoutDetails,
|
||||
mockJobWithUnauthorizedAction,
|
||||
triggerJob,
|
||||
} from './mock_data';
|
||||
|
||||
describe('pipeline graph job item', () => {
|
||||
let wrapper;
|
||||
|
||||
const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
|
||||
const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]');
|
||||
const findActionComponent = () => wrapper.find('[data-testid="ci-action-component"]');
|
||||
const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
|
||||
const findJobWithLink = () => wrapper.findByTestId('job-with-link');
|
||||
const findActionComponent = () => wrapper.findByTestId('ci-action-component');
|
||||
const findBadge = () => wrapper.findComponent(GlBadge);
|
||||
|
||||
const createWrapper = (propsData) => {
|
||||
wrapper = mount(JobItem, {
|
||||
propsData,
|
||||
});
|
||||
wrapper = extendedWrapper(
|
||||
mount(JobItem, {
|
||||
propsData,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
|
||||
|
||||
const delayedJob = {
|
||||
__typename: 'CiJob',
|
||||
name: 'delayed job',
|
||||
scheduledAt: '2015-07-03T10:01:00.000Z',
|
||||
needs: [],
|
||||
status: {
|
||||
__typename: 'DetailedStatus',
|
||||
icon: 'status_scheduled',
|
||||
tooltip: 'delayed manual action (%{remainingTime})',
|
||||
hasDetails: true,
|
||||
detailsPath: '/root/kinder-pipe/-/jobs/5339',
|
||||
group: 'scheduled',
|
||||
action: {
|
||||
__typename: 'StatusAction',
|
||||
icon: 'time-out',
|
||||
title: 'Unschedule',
|
||||
path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
|
||||
buttonTitle: 'Unschedule job',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockJob = {
|
||||
id: 4256,
|
||||
name: 'test',
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
tooltip: 'passed',
|
||||
group: 'success',
|
||||
detailsPath: '/root/ci-mock/builds/4256',
|
||||
hasDetails: true,
|
||||
action: {
|
||||
icon: 'retry',
|
||||
title: 'Retry',
|
||||
path: '/root/ci-mock/builds/4256/retry',
|
||||
method: 'post',
|
||||
},
|
||||
},
|
||||
};
|
||||
const mockJobWithoutDetails = {
|
||||
id: 4257,
|
||||
name: 'job_without_details',
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
group: 'success',
|
||||
detailsPath: '/root/ci-mock/builds/4257',
|
||||
hasDetails: false,
|
||||
},
|
||||
};
|
||||
const mockJobWithUnauthorizedAction = {
|
||||
id: 4258,
|
||||
name: 'stop-environment',
|
||||
status: {
|
||||
icon: 'status_manual',
|
||||
label: 'manual stop action (not allowed)',
|
||||
tooltip: 'manual action',
|
||||
group: 'manual',
|
||||
detailsPath: '/root/ci-mock/builds/4258',
|
||||
hasDetails: true,
|
||||
action: null,
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
@ -148,13 +93,25 @@ describe('pipeline graph job item', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should render provided class name', () => {
|
||||
createWrapper({
|
||||
job: mockJob,
|
||||
cssClassJobName: 'css-class-job-name',
|
||||
describe('job style', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
job: mockJob,
|
||||
cssClassJobName: 'css-class-job-name',
|
||||
});
|
||||
});
|
||||
|
||||
expect(wrapper.find('a').classes()).toContain('css-class-job-name');
|
||||
it('should render provided class name', () => {
|
||||
expect(wrapper.find('a').classes()).toContain('css-class-job-name');
|
||||
});
|
||||
|
||||
it('does not show a badge on the job item', () => {
|
||||
expect(findBadge().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not apply the trigger job class', () => {
|
||||
expect(findJobWithLink().classes()).not.toContain('gl-rounded-lg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('status label', () => {
|
||||
|
@ -201,34 +158,51 @@ describe('pipeline graph job item', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('trigger job highlighting', () => {
|
||||
it.each`
|
||||
job | jobName | expanded | link
|
||||
${mockJob} | ${mockJob.name} | ${true} | ${true}
|
||||
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
|
||||
`(
|
||||
`trigger job should stay highlighted when downstream is expanded`,
|
||||
({ job, jobName, expanded, link }) => {
|
||||
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
|
||||
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
|
||||
describe('trigger job', () => {
|
||||
describe('card', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({ job: triggerJob });
|
||||
});
|
||||
|
||||
expect(findJobEl().classes()).toContain(triggerActiveClass);
|
||||
},
|
||||
);
|
||||
it('shows a badge on the job item', () => {
|
||||
expect(findBadge().exists()).toBe(true);
|
||||
expect(findBadge().text()).toBe('Trigger job');
|
||||
});
|
||||
|
||||
it.each`
|
||||
job | jobName | expanded | link
|
||||
${mockJob} | ${mockJob.name} | ${false} | ${true}
|
||||
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
|
||||
`(
|
||||
`trigger job should not be highlighted when downstream is not expanded`,
|
||||
({ job, jobName, expanded, link }) => {
|
||||
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
|
||||
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
|
||||
it('applies a rounded corner style instead of the usual pill shape', () => {
|
||||
expect(findJobWithoutLink().classes()).toContain('gl-rounded-lg');
|
||||
});
|
||||
});
|
||||
|
||||
expect(findJobEl().classes()).not.toContain(triggerActiveClass);
|
||||
},
|
||||
);
|
||||
describe('highlighting', () => {
|
||||
it.each`
|
||||
job | jobName | expanded | link
|
||||
${mockJob} | ${mockJob.name} | ${true} | ${true}
|
||||
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
|
||||
`(
|
||||
`trigger job should stay highlighted when downstream is expanded`,
|
||||
({ job, jobName, expanded, link }) => {
|
||||
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
|
||||
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
|
||||
|
||||
expect(findJobEl().classes()).toContain(triggerActiveClass);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
job | jobName | expanded | link
|
||||
${mockJob} | ${mockJob.name} | ${false} | ${true}
|
||||
${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
|
||||
`(
|
||||
`trigger job should not be highlighted when downstream is not expanded`,
|
||||
({ job, jobName, expanded, link }) => {
|
||||
createWrapper({ job, pipelineExpanded: { jobName, expanded } });
|
||||
const findJobEl = link ? findJobWithLink : findJobWithoutLink;
|
||||
|
||||
expect(findJobEl().classes()).not.toContain(triggerActiveClass);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('job classes', () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
|
||||
import { BUILD_KIND, BRIDGE_KIND } from '~/pipelines/components/graph/constants';
|
||||
|
||||
export const mockPipelineResponse = {
|
||||
data: {
|
||||
|
@ -50,6 +51,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '6',
|
||||
kind: BUILD_KIND,
|
||||
name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -101,6 +103,7 @@ export const mockPipelineResponse = {
|
|||
__typename: 'CiJob',
|
||||
id: '11',
|
||||
name: 'build_b',
|
||||
kind: BUILD_KIND,
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
__typename: 'DetailedStatus',
|
||||
|
@ -151,6 +154,7 @@ export const mockPipelineResponse = {
|
|||
__typename: 'CiJob',
|
||||
id: '16',
|
||||
name: 'build_c',
|
||||
kind: BUILD_KIND,
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
__typename: 'DetailedStatus',
|
||||
|
@ -200,6 +204,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '21',
|
||||
kind: BUILD_KIND,
|
||||
name: 'build_d 1/3',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -232,6 +237,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '24',
|
||||
kind: BUILD_KIND,
|
||||
name: 'build_d 2/3',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -264,6 +270,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '27',
|
||||
kind: BUILD_KIND,
|
||||
name: 'build_d 3/3',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -329,6 +336,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '34',
|
||||
kind: BUILD_KIND,
|
||||
name: 'test_a',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -413,6 +421,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '42',
|
||||
kind: BUILD_KIND,
|
||||
name: 'test_b 1/2',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -499,6 +508,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '67',
|
||||
kind: BUILD_KIND,
|
||||
name: 'test_b 2/2',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -603,6 +613,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '59',
|
||||
kind: BUILD_KIND,
|
||||
name: 'test_c',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -646,6 +657,7 @@ export const mockPipelineResponse = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '53',
|
||||
kind: BUILD_KIND,
|
||||
name: 'test_d',
|
||||
scheduledAt: null,
|
||||
status: {
|
||||
|
@ -871,6 +883,7 @@ export const wrappedPipelineReturn = {
|
|||
{
|
||||
__typename: 'CiJob',
|
||||
id: '83',
|
||||
kind: BUILD_KIND,
|
||||
name: 'build_n',
|
||||
scheduledAt: null,
|
||||
needs: {
|
||||
|
@ -941,3 +954,87 @@ export const mockCalloutsResponse = (mappedCallouts) => ({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const delayedJob = {
|
||||
__typename: 'CiJob',
|
||||
kind: BUILD_KIND,
|
||||
name: 'delayed job',
|
||||
scheduledAt: '2015-07-03T10:01:00.000Z',
|
||||
needs: [],
|
||||
status: {
|
||||
__typename: 'DetailedStatus',
|
||||
icon: 'status_scheduled',
|
||||
tooltip: 'delayed manual action (%{remainingTime})',
|
||||
hasDetails: true,
|
||||
detailsPath: '/root/kinder-pipe/-/jobs/5339',
|
||||
group: 'scheduled',
|
||||
action: {
|
||||
__typename: 'StatusAction',
|
||||
icon: 'time-out',
|
||||
title: 'Unschedule',
|
||||
path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
|
||||
buttonTitle: 'Unschedule job',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockJob = {
|
||||
id: 4256,
|
||||
name: 'test',
|
||||
kind: BUILD_KIND,
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
tooltip: 'passed',
|
||||
group: 'success',
|
||||
detailsPath: '/root/ci-mock/builds/4256',
|
||||
hasDetails: true,
|
||||
action: {
|
||||
icon: 'retry',
|
||||
title: 'Retry',
|
||||
path: '/root/ci-mock/builds/4256/retry',
|
||||
method: 'post',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockJobWithoutDetails = {
|
||||
id: 4257,
|
||||
name: 'job_without_details',
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
group: 'success',
|
||||
detailsPath: '/root/ci-mock/builds/4257',
|
||||
hasDetails: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const mockJobWithUnauthorizedAction = {
|
||||
id: 4258,
|
||||
name: 'stop-environment',
|
||||
status: {
|
||||
icon: 'status_manual',
|
||||
label: 'manual stop action (not allowed)',
|
||||
tooltip: 'manual action',
|
||||
group: 'manual',
|
||||
detailsPath: '/root/ci-mock/builds/4258',
|
||||
hasDetails: true,
|
||||
action: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const triggerJob = {
|
||||
id: 4259,
|
||||
name: 'trigger',
|
||||
kind: BRIDGE_KIND,
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'passed',
|
||||
label: 'passed',
|
||||
group: 'success',
|
||||
action: null,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -90,6 +90,10 @@ RSpec.describe ClustersHelper do
|
|||
expect(subject[:gitlab_version]).to eq(Gitlab.version_info)
|
||||
end
|
||||
|
||||
it 'displays KAS version' do
|
||||
expect(subject[:kas_version]).to eq(Gitlab::Kas.version_info)
|
||||
end
|
||||
|
||||
context 'user has no permissions to create a cluster' do
|
||||
it 'displays that user can\'t add cluster' do
|
||||
expect(subject[:can_add_cluster]).to eq("false")
|
||||
|
|
|
@ -9,29 +9,47 @@ RSpec.describe Preloaders::UserMaxAccessLevelInProjectsPreloader do
|
|||
let_it_be(:project_3) { create(:project) }
|
||||
|
||||
let(:projects) { [project_1, project_2, project_3] }
|
||||
let(:query) { projects.each { |project| user.can?(:read_project, project) } }
|
||||
|
||||
before do
|
||||
project_1.add_developer(user)
|
||||
project_2.add_developer(user)
|
||||
end
|
||||
|
||||
context 'preload maximum access level to avoid querying project_authorizations', :request_store do
|
||||
it 'avoids N+1 queries', :request_store do
|
||||
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, user).execute
|
||||
context 'without preloader' do
|
||||
it 'runs N queries' do
|
||||
expect { query }.to make_queries(projects.size)
|
||||
end
|
||||
end
|
||||
|
||||
query_count = ActiveRecord::QueryRecorder.new do
|
||||
projects.each { |project| user.can?(:read_project, project) }
|
||||
end.count
|
||||
describe '#execute', :request_store do
|
||||
let(:projects_arg) { projects }
|
||||
|
||||
expect(query_count).to eq(0)
|
||||
before do
|
||||
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_arg, user).execute
|
||||
end
|
||||
|
||||
it 'runs N queries without preloading' do
|
||||
query_count = ActiveRecord::QueryRecorder.new do
|
||||
projects.each { |project| user.can?(:read_project, project) }
|
||||
end.count
|
||||
it 'avoids N+1 queries' do
|
||||
expect { query }.not_to make_queries
|
||||
end
|
||||
|
||||
expect(query_count).to eq(projects.size)
|
||||
context 'when projects is an array of IDs' do
|
||||
let(:projects_arg) { [project_1.id, project_2.id, project_3.id] }
|
||||
|
||||
it 'avoids N+1 queries' do
|
||||
expect { query }.not_to make_queries
|
||||
end
|
||||
end
|
||||
|
||||
# Test for handling of SQL table name clashes.
|
||||
context 'when projects is a relation including project_authorizations' do
|
||||
let(:projects_arg) do
|
||||
Project.where(id: ProjectAuthorization.where(project_id: projects).select(:project_id))
|
||||
end
|
||||
|
||||
it 'avoids N+1 queries' do
|
||||
expect { query }.not_to make_queries
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -336,35 +336,24 @@ RSpec.describe Users::DestroyService do
|
|||
context 'batched nullify' do
|
||||
let(:other_user) { create(:user) }
|
||||
|
||||
context 'when :nullify_in_batches_on_user_deletion feature flag is enabled' do
|
||||
it 'nullifies related associations in batches' do
|
||||
expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
|
||||
it 'nullifies related associations in batches' do
|
||||
expect(other_user).to receive(:nullify_dependent_associations_in_batches).and_call_original
|
||||
|
||||
described_class.new(user).execute(other_user, skip_authorization: true)
|
||||
end
|
||||
|
||||
it 'nullifies last_updated_issues and closed_issues' do
|
||||
issue = create(:issue, closed_by: other_user, updated_by: other_user)
|
||||
|
||||
described_class.new(user).execute(other_user, skip_authorization: true)
|
||||
|
||||
issue.reload
|
||||
|
||||
expect(issue.closed_by).to be_nil
|
||||
expect(issue.updated_by).to be_nil
|
||||
end
|
||||
described_class.new(user).execute(other_user, skip_authorization: true)
|
||||
end
|
||||
|
||||
context 'when :nullify_in_batches_on_user_deletion feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(nullify_in_batches_on_user_deletion: false)
|
||||
end
|
||||
it 'nullifies last_updated_issues, closed_issues, resource_label_events' do
|
||||
issue = create(:issue, closed_by: other_user, updated_by: other_user)
|
||||
resource_label_event = create(:resource_label_event, user: other_user)
|
||||
|
||||
it 'does not use batching' do
|
||||
expect(other_user).not_to receive(:nullify_dependent_associations_in_batches)
|
||||
described_class.new(user).execute(other_user, skip_authorization: true)
|
||||
|
||||
described_class.new(user).execute(other_user, skip_authorization: true)
|
||||
end
|
||||
issue.reload
|
||||
resource_label_event.reload
|
||||
|
||||
expect(issue.closed_by).to be_nil
|
||||
expect(issue.updated_by).to be_nil
|
||||
expect(resource_label_event.user).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec::Matchers.define :make_queries do |expected_count = nil|
|
||||
supports_block_expectations
|
||||
|
||||
match do |block|
|
||||
@recorder = ActiveRecord::QueryRecorder.new(&block)
|
||||
@counter = @recorder.count
|
||||
if expected_count
|
||||
@counter == expected_count
|
||||
else
|
||||
@counter > 0
|
||||
end
|
||||
end
|
||||
|
||||
failure_message do |_|
|
||||
if expected_count
|
||||
"expected to make #{expected_count} queries but made #{@counter} queries"
|
||||
else
|
||||
"expected to make queries but did not make any"
|
||||
end
|
||||
end
|
||||
|
||||
failure_message_when_negated do |_|
|
||||
if expected_count
|
||||
"expected not to make #{expected_count} queries but received #{@counter} queries"
|
||||
else
|
||||
"expected not to make queries but received #{@counter} queries"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,6 +28,20 @@ RSpec.describe 'devise/shared/_signin_box' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Base form' do
|
||||
before do
|
||||
stub_devise
|
||||
allow(view).to receive(:captcha_enabled?).and_return(false)
|
||||
allow(view).to receive(:captcha_on_login_required?).and_return(false)
|
||||
end
|
||||
|
||||
it 'renders user_login label' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_content(_('Username or email'))
|
||||
end
|
||||
end
|
||||
|
||||
def stub_devise
|
||||
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
|
||||
allow(view).to receive(:resource).and_return(spy)
|
||||
|
|
|
@ -35,7 +35,6 @@ var zipSubcommandsErrorsCounter = promauto.NewCounterVec(
|
|||
}, []string{"error"})
|
||||
|
||||
type artifactsUploadProcessor struct {
|
||||
opts *destination.UploadOpts
|
||||
format string
|
||||
|
||||
SavedFileTracker
|
||||
|
@ -52,7 +51,7 @@ func Artifacts(myAPI *api.API, h http.Handler, p Preparer) http.Handler {
|
|||
|
||||
format := r.URL.Query().Get(ArtifactFormatKey)
|
||||
|
||||
mg := &artifactsUploadProcessor{opts: opts, format: format, SavedFileTracker: SavedFileTracker{Request: r}}
|
||||
mg := &artifactsUploadProcessor{format: format, SavedFileTracker: SavedFileTracker{Request: r}}
|
||||
interceptMultipartFiles(w, r, h, a, mg, opts)
|
||||
}, "/authorize")
|
||||
}
|
||||
|
@ -62,12 +61,9 @@ func (a *artifactsUploadProcessor) generateMetadataFromZip(ctx context.Context,
|
|||
defer metaWriter.Close()
|
||||
|
||||
metaOpts := &destination.UploadOpts{
|
||||
LocalTempPath: a.opts.LocalTempPath,
|
||||
LocalTempPath: os.TempDir(),
|
||||
TempFilePrefix: "metadata.gz",
|
||||
}
|
||||
if metaOpts.LocalTempPath == "" {
|
||||
metaOpts.LocalTempPath = os.TempDir()
|
||||
}
|
||||
|
||||
fileName := file.LocalPath
|
||||
if fileName == "" {
|
||||
|
|
Loading…
Reference in New Issue