Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-20 18:09:10 +00:00
parent 049d16d168
commit a3764262c0
50 changed files with 518 additions and 441 deletions

View File

@ -127,7 +127,7 @@ variables:
# Run with decomposed databases by default
DECOMPOSED_DB: "true"
DOCS_REVIEW_APPS_DOMAIN: "35.193.151.162.nip.io"
DOCS_REVIEW_APPS_DOMAIN: "docs.gitlab-review-app"
DOCS_GITLAB_REPO_SUFFIX: "ee"
REVIEW_APPS_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ruby-3.0:gcloud-383-kubectl-1.23-helm-3.5"

View File

@ -1,5 +1,5 @@
---
# Cop supports --auto-correct.
# Cop supports --autocorrect.
Gitlab/Json:
Details: grace period
Exclude:
@ -247,6 +247,7 @@ Gitlab/Json:
- 'lib/gitlab/database/background_migration/batched_migration.rb'
- 'lib/gitlab/database/background_migration_job.rb'
- 'lib/gitlab/database/migration_helpers.rb'
- 'lib/gitlab/database/migrations/batched_background_migration_helpers.rb'
- 'lib/gitlab/database/migrations/instrumentation.rb'
- 'lib/gitlab/database/migrations/runner.rb'
- 'lib/gitlab/database/postgres_hll/buckets.rb'
@ -284,6 +285,7 @@ Gitlab/Json:
- 'lib/microsoft_teams/notifier.rb'
- 'lib/tasks/gitlab/background_migrations.rake'
- 'lib/version_check.rb'
- 'spec/commands/diagnostic_reports/uploader_smoke_spec.rb'
- 'spec/controllers/admin/integrations_controller_spec.rb'
- 'spec/controllers/concerns/product_analytics_tracking_spec.rb'
- 'spec/controllers/groups/settings/integrations_controller_spec.rb'
@ -417,7 +419,9 @@ Gitlab/Json:
- 'spec/requests/api/merge_requests_spec.rb'
- 'spec/requests/api/namespaces_spec.rb'
- 'spec/requests/api/project_snapshots_spec.rb'
- 'spec/requests/groups/settings/access_tokens_controller_spec.rb'
- 'spec/requests/projects/incident_management/pagerduty_incidents_spec.rb'
- 'spec/requests/projects/settings/access_tokens_controller_spec.rb'
- 'spec/requests/users_controller_spec.rb'
- 'spec/requests/whats_new_controller_spec.rb'
- 'spec/scripts/pipeline_test_report_builder_spec.rb'

View File

@ -2855,7 +2855,6 @@ Layout/LineLength:
- 'ee/spec/views/registrations/welcome/show.html.haml_spec.rb'
- 'ee/spec/views/shared/_mirror_status.html.haml_spec.rb'
- 'ee/spec/views/shared/_namespace_user_cap_reached_alert.html.haml_spec.rb'
- 'ee/spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- 'ee/spec/views/shared/billings/_eoa_bronze_plan_banner.html.haml_spec.rb'
- 'ee/spec/views/shared/billings/_trial_status.html.haml_spec.rb'
- 'ee/spec/views/shared/credentials_inventory/personal_access_tokens/_personal_access_token.html.haml_spec.rb'
@ -5968,7 +5967,6 @@ Layout/LineLength:
- 'spec/views/projects/tags/index.html.haml_spec.rb'
- 'spec/views/projects/tree/show.html.haml_spec.rb'
- 'spec/views/search/_results.html.haml_spec.rb'
- 'spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- 'spec/views/shared/milestones/_issuable.html.haml_spec.rb'
- 'spec/views/shared/projects/_project.html.haml_spec.rb'
- 'spec/views/shared/snippets/_snippet.html.haml_spec.rb'

View File

@ -743,7 +743,6 @@ Style/IfUnlessModifier:
- 'ee/spec/support/http_io/http_io_helpers.rb'
- 'ee/spec/support/shared_examples/requests/api/graphql/geo/registries_shared_examples.rb'
- 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb'
- 'ee/spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- 'ee/spec/workers/elastic/migration_worker_spec.rb'
- 'lib/api/api_guard.rb'
- 'lib/api/boards_responses.rb'
@ -1202,7 +1201,6 @@ Style/IfUnlessModifier:
- 'spec/views/groups/edit.html.haml_spec.rb'
- 'spec/views/profiles/keys/_key.html.haml_spec.rb'
- 'spec/views/projects/edit.html.haml_spec.rb'
- 'spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- 'spec/workers/analytics/usage_trends/counter_job_worker_spec.rb'
- 'tooling/danger/product_intelligence.rb'
- 'tooling/lib/tooling/find_codeowners.rb'

View File

@ -428,7 +428,7 @@ group :development, :test do
end
group :development, :test, :danger do
gem 'gitlab-dangerfiles', '~> 3.5.2', require: false
gem 'gitlab-dangerfiles', '~> 3.6.1', require: false
end
group :development, :test, :coverage do

View File

@ -151,7 +151,7 @@
{"name":"faraday-em_http","version":"1.0.0","platform":"ruby","checksum":"7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689"},
{"name":"faraday-em_synchrony","version":"1.0.0","platform":"ruby","checksum":"460dad1c30cc692d6e77d4c391ccadb4eca4854b315632cd7e560f74275cf9ed"},
{"name":"faraday-excon","version":"1.1.0","platform":"ruby","checksum":"b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940"},
{"name":"faraday-http-cache","version":"2.4.0","platform":"ruby","checksum":"388f901d63bd5903b470c5696bc886ed94fab0c4206b25c3761e7b9bdbbf6c90"},
{"name":"faraday-http-cache","version":"2.4.1","platform":"ruby","checksum":"fb51b2e9ee72f89e81cc277ee574dbc5940f3db95431b3533de9882f92635ee3"},
{"name":"faraday-httpclient","version":"1.0.1","platform":"ruby","checksum":"4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b"},
{"name":"faraday-multipart","version":"1.0.4","platform":"ruby","checksum":"9012021ab57790f7d712f590b48d5f948b19b43cfa11ca83e6459f06090b0725"},
{"name":"faraday-net_http","version":"1.0.1","platform":"ruby","checksum":"3245ce406ebb77b40e17a77bfa66191dda04be2fd4e13a78d8a4305854d328ba"},
@ -202,7 +202,7 @@
{"name":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"},
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
{"name":"gitlab-dangerfiles","version":"3.5.2","platform":"ruby","checksum":"fae28a55b83b6c7f8298b9b1d90354ae73636729fd829ad58326bef46bd2f01f"},
{"name":"gitlab-dangerfiles","version":"3.6.1","platform":"ruby","checksum":"f7b69b093d52acb89095d411cb7b8849f5f3b9e76f8baa4c99b5671f1564865f"},
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
{"name":"gitlab-fog-azure-rm","version":"1.3.0","platform":"ruby","checksum":"2fef5317d6515f95f803099afa860fe3019ce6e1907bf49f66b5e06468a617b5"},
{"name":"gitlab-labkit","version":"0.24.0","platform":"ruby","checksum":"8f16e5aa4e0a05be58958fe880bdd53c84b659a081ea9981d2b510922a4a0548"},

View File

@ -448,7 +448,7 @@ GEM
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-http-cache (2.4.0)
faraday-http-cache (2.4.1)
faraday (>= 0.8)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
@ -555,7 +555,7 @@ GEM
terminal-table (~> 1.5, >= 1.5.1)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
gitlab-dangerfiles (3.5.2)
gitlab-dangerfiles (3.6.1)
danger (>= 8.4.5)
danger-gitlab (>= 8.0.0)
rake
@ -1627,7 +1627,7 @@ DEPENDENCIES
gitaly (~> 15.4.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.5.2)
gitlab-dangerfiles (~> 3.6.1)
gitlab-experiment (~> 0.7.1)
gitlab-fog-azure-rm (~> 1.3.0)
gitlab-labkit (~> 0.24.0)

View File

@ -141,6 +141,7 @@ export default {
<gl-link
:href="computedPath"
class="sortable-link gl-font-weight-normal"
target="_blank"
@click="handleTitleClick"
>
{{ title }}

View File

@ -1,6 +1,8 @@
<script>
import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
import { GlAlert, GlBadge, GlButton, GlLoadingIcon, GlModal, GlTabs, GlTab } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { limitedCounterWithDelimiter } from '~/lib/utils/text_utility';
import { queryToObject } from '~/lib/utils/url_utility';
import deletePipelineScheduleMutation from '../graphql/mutations/delete_pipeline_schedule.mutation.graphql';
import getPipelineSchedulesQuery from '../graphql/queries/get_pipeline_schedules.query.graphql';
import PipelineSchedulesTable from './table/pipeline_schedules_table.vue';
@ -11,6 +13,7 @@ export default {
scheduleDeleteError: s__(
'PipelineSchedules|There was a problem deleting the pipeline schedule.',
),
newSchedule: s__('PipelineSchedules|New schedule'),
},
modal: {
id: 'delete-pipeline-schedule-modal',
@ -28,8 +31,12 @@ export default {
},
components: {
GlAlert,
GlBadge,
GlButton,
GlLoadingIcon,
GlModal,
GlTabs,
GlTab,
PipelineSchedulesTable,
},
inject: {
@ -43,10 +50,16 @@ export default {
variables() {
return {
projectPath: this.fullPath,
status: this.scope,
};
},
update({ project }) {
return project?.pipelineSchedules?.nodes || [];
update(data) {
const { pipelineSchedules: { nodes: list = [], count } = {} } = data.project || {};
return {
list,
count,
};
},
error() {
this.reportError(this.$options.i18n.schedulesFetchError);
@ -54,18 +67,58 @@ export default {
},
},
data() {
const { scope } = queryToObject(window.location.search);
return {
schedules: [],
schedules: {
list: [],
},
scope,
hasError: false,
errorMessage: '',
scheduleToDeleteId: null,
showModal: false,
count: 0,
};
},
computed: {
isLoading() {
return this.$apollo.queries.schedules.loading;
},
schedulesCount() {
return this.schedules.count;
},
tabs() {
return [
{
text: s__('PipelineSchedules|All'),
count: limitedCounterWithDelimiter(this.count),
scope: null,
showBadge: true,
attrs: { 'data-testid': 'pipeline-schedules-all-tab' },
},
{
text: s__('PipelineSchedules|Active'),
scope: 'ACTIVE',
showBadge: false,
attrs: { 'data-testid': 'pipeline-schedules-active-tab' },
},
{
text: s__('PipelineSchedules|Inactive'),
scope: 'INACTIVE',
showBadge: false,
attrs: { 'data-testid': 'pipeline-schedules-inactive-tab' },
},
];
},
},
watch: {
// this watcher ensures that the count on the all tab
// is not updated when switching to other tabs
schedulesCount(newCount) {
if (!this.scope) {
this.count = newCount;
}
},
},
methods: {
reportError(error) {
@ -100,6 +153,10 @@ export default {
this.reportError(this.$options.i18n.scheduleDeleteError);
}
},
fetchPipelineSchedulesByStatus(scope) {
this.scope = scope;
this.$apollo.queries.schedules.refetch();
},
},
};
</script>
@ -110,12 +167,45 @@ export default {
{{ errorMessage }}
</gl-alert>
<gl-loading-icon v-if="isLoading" size="lg" />
<!-- Tabs will be addressed in #371989 -->
<template v-else>
<pipeline-schedules-table :schedules="schedules" @showDeleteModal="showDeleteModal" />
<gl-tabs
sync-active-tab-with-query-params
query-param-name="scope"
nav-class="gl-flex-grow-1 gl-align-items-center"
>
<gl-tab
v-for="tab in tabs"
:key="tab.text"
:title-link-attributes="tab.attrs"
:query-param-value="tab.scope"
@click="fetchPipelineSchedulesByStatus(tab.scope)"
>
<template #title>
<span>{{ tab.text }}</span>
<template v-if="tab.showBadge">
<gl-loading-icon v-if="tab.scope === scope && isLoading" class="gl-ml-2" />
<gl-badge v-else-if="tab.count" size="sm" class="gl-tab-counter-badge">
{{ tab.count }}
</gl-badge>
</template>
</template>
<gl-loading-icon v-if="isLoading" size="lg" />
<pipeline-schedules-table
v-else
:schedules="schedules.list"
@showDeleteModal="showDeleteModal"
/>
</gl-tab>
<template #tabs-end>
<gl-button variant="confirm" class="gl-ml-auto" data-testid="new-schedule-button">
{{ $options.i18n.newSchedule }}
</gl-button>
</template>
</gl-tabs>
<gl-modal
:visible="showModal"

View File

@ -12,31 +12,37 @@ export default {
{
key: 'description',
label: s__('PipelineSchedules|Description'),
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-40p',
},
{
key: 'target',
label: s__('PipelineSchedules|Target'),
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-10p',
},
{
key: 'pipeline',
label: s__('PipelineSchedules|Last Pipeline'),
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-10p',
},
{
key: 'next',
label: s__('PipelineSchedules|Next Run'),
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-15p',
},
{
key: 'owner',
label: s__('PipelineSchedules|Owner'),
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-10p',
},
{
key: 'actions',
label: '',
thClass: 'gl-border-t-none!',
columnClass: 'gl-w-15p',
},
],

View File

@ -1,7 +1,8 @@
query getPipelineSchedulesQuery($projectPath: ID!) {
query getPipelineSchedulesQuery($projectPath: ID!, $status: PipelineScheduleStatus) {
project(fullPath: $projectPath) {
id
pipelineSchedules {
pipelineSchedules(status: $status) {
count
nodes {
id
description

View File

@ -64,7 +64,7 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
private
def allow_self_managed_content_security_policy
return unless Feature.enabled?(:jira_connect_oauth_self_managed)
return unless Feature.enabled?(:jira_connect_oauth_self_managed_setting)
return unless current_jira_installation.instance_url?

View File

@ -40,9 +40,9 @@ class Namespace < ApplicationRecord
PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze
# The first date in https://docs.gitlab.com/ee/user/usage_quotas.html#namespace-storage-limit-enforcement-schedule
# Determines when we start enforcing namespace storage
MIN_STORAGE_ENFORCEMENT_DATE = Date.new(2022, 10, 19)
# This date is just a placeholder until namespace storage enforcement timeline is confirmed at which point
# this should be replaced, see https://about.gitlab.com/pricing/faq-efficient-free-tier/#user-limits-on-gitlab-saas-free-tier
MIN_STORAGE_ENFORCEMENT_DATE = 3.months.from_now.to_date
# https://gitlab.com/gitlab-org/gitlab/-/issues/367531
MIN_STORAGE_ENFORCEMENT_USAGE = 5.gigabytes

View File

@ -11,8 +11,6 @@ class PersonalAccessToken < ApplicationRecord
add_authentication_token_field :token, digest: true
REDIS_EXPIRY_TIME = 3.minutes
# PATs are 20 characters + optional configurable settings prefix (0..20)
TOKEN_LENGTH_RANGE = (20..40).freeze
@ -34,8 +32,6 @@ class PersonalAccessToken < ApplicationRecord
scope :for_user, -> (user) { where(user: user) }
scope :for_users, -> (users) { where(user: users) }
scope :preload_users, -> { preload(:user) }
scope :order_expires_at_asc, -> { reorder(expires_at: :asc) }
scope :order_expires_at_desc, -> { reorder(expires_at: :desc) }
scope :order_expires_at_asc_id_desc, -> { reorder(expires_at: :asc, id: :desc) }
scope :project_access_token, -> { includes(:user).where(user: { user_type: :project_bot }) }
scope :owner_is_human, -> { includes(:user).where(user: { user_type: :human }) }
@ -55,35 +51,10 @@ class PersonalAccessToken < ApplicationRecord
!revoked? && !expired?
end
def self.redis_getdel(user_id)
Gitlab::Redis::SharedState.with do |redis|
redis_key = redis_shared_state_key(user_id)
encrypted_token = redis.get(redis_key)
redis.del(redis_key)
begin
Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token)
rescue StandardError => e
logger.warn "Failed to decrypt #{self.name} value stored in Redis for key ##{redis_key}: #{e.class}"
encrypted_token
end
end
end
def self.redis_store!(user_id, token)
encrypted_token = Gitlab::CryptoHelper.aes256_gcm_encrypt(token)
Gitlab::Redis::SharedState.with do |redis|
redis.set(redis_shared_state_key(user_id), encrypted_token, ex: REDIS_EXPIRY_TIME)
end
end
override :simple_sorts
def self.simple_sorts
super.merge(
{
'expires_at_asc' => -> { order_expires_at_asc },
'expires_at_desc' => -> { order_expires_at_desc },
'expires_at_asc_id_desc' => -> { order_expires_at_asc_id_desc }
}
)
@ -121,10 +92,6 @@ class PersonalAccessToken < ApplicationRecord
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
def self.redis_shared_state_key(user_id)
"gitlab:personal_access_token:#{user_id}"
end
end
PersonalAccessToken.prepend_mod_with('PersonalAccessToken')

View File

@ -123,4 +123,4 @@
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'
= render_if_exists 'admin/application_settings/add_license'
= render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth, current_user)
= render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth_self_managed_setting, current_user)

View File

@ -1,28 +0,0 @@
- form_field = local_assigns.fetch(:form_field, nil)
- variable = local_assigns.fetch(:variable, nil)
- key = variable[0]
- value = variable[1]
- variable_type = variable[2] || "env_var"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
- variable_type_input_name = "#{form_field}[variables_attributes][][variable_type]"
- key_input_name = "#{form_field}[variables_attributes][][key]"
- value_input_name = "#{form_field}[variables_attributes][][secret_value]"
%li.js-row.ci-variable-row
.ci-variable-row-body.border-bottom
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
= options_for_select(ci_variable_type_options, variable_type)
%input.js-ci-variable-input-key.ci-variable-body-item.form-control.table-section.section-15{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
%textarea.js-ci-variable-input-value.js-secret-value.form-control{ rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
%button.gl-button.btn.btn-default.btn-icon.btn-item-remove.js-row-remove-button.ci-variable-row-remove-button.table-section{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= sprite_icon('close')

View File

@ -1,12 +0,0 @@
.created-personal-access-token-container
%h5.gl-mt-0
= _('Your new %{type}') % { type: type }
.form-group
.input-group
= text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: 'form-control js-select-on-focus', data: { qa_selector: 'created_access_token_field' }, 'aria-describedby' => 'created-token-help-block'
%span.input-group-append
= clipboard_button(text: new_token_value, title: _('Copy %{type}') % { type: type }, placement: 'left', class: 'input-group-text btn-default btn-clipboard')
%span#created-token-help-block.form-text.text-muted.text-danger
= _("Make sure you save it - you won't be able to access it again.")
%hr

View File

@ -1,51 +0,0 @@
- no_active_tokens_message = local_assigns.fetch(:no_active_tokens_message, _('This user has no active %{type}.') % { type: type_plural })
- impersonation = local_assigns.fetch(:impersonation, false)
- resource = local_assigns.fetch(:resource, false)
%hr
%h5
= _('Active %{type} (%{token_length})') % { type: type_plural, token_length: active_tokens.length }
- if impersonation
%p.profile-settings-content
= _("To see all the user's personal access tokens you must impersonate them first.")
- if active_tokens.present?
.table-responsive
%table.table.active-tokens
%thead
%tr
%th= _('Token name')
%th= _('Scopes')
%th= s_('AccessTokens|Created')
%th
= _('Last Used')
= link_to sprite_icon('question-o'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'view-the-last-time-a-token-was-used'), target: '_blank', rel: 'noopener noreferrer'
%th= _('Expires')
- if resource
%th= _('Role')
%th
%tbody
- active_tokens.each do |token|
%tr
%td= token.name
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
%td= token.created_at.to_date.to_s(:medium)
%td
- if token.last_used_at?
%span.token-last-used-label= _(time_ago_with_tooltip(token.last_used_at))
- else
%span.token-never-used-label= _('Never')
%td
- if token.expires?
%span{ class: ('text-warning' if token.expires_soon?) }
= time_ago_with_tooltip(token.expires_at)
- else
%span.token-never-expires-label= _('Never')
- if resource
%td= resource.member(token.user).human_access
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right #{'btn-danger-secondary' unless token.expires?}", aria: { label: _('Revoke') }, data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type }, 'confirm-btn-variant': 'danger', qa_selector: 'revoke_button' }
- else
.settings-message.text-center
= no_active_tokens_message

View File

@ -0,0 +1,8 @@
---
name: jira_connect_oauth_self_managed_setting
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100725
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/377679
milestone: '15.6'
type: development
group: group::integrations
default_enabled: false

View File

@ -3,7 +3,7 @@ table_name: software_license_policies
classes:
- SoftwareLicensePolicy
feature_categories:
- license_compliance
- security_policy_management
description: Allows user to approve or deny the use certain software licenses in their project.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6246
milestone: '11.2'

View File

@ -3,7 +3,7 @@ table_name: software_licenses
classes:
- SoftwareLicense
feature_categories:
- license_compliance
- security_policy_management
description: Normalized software licenses to use in conjunction with License Compliance features (like software license policies)
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6246
milestone: '11.2'

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class CleanupVulnerabilityStateTransitionsWithSameFromStateToState < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
class VulnerabilityStateTransition < MigrationRecord
self.table_name = 'vulnerability_state_transitions'
end
def up
VulnerabilityStateTransition.where('from_state = to_state').delete_all
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
2ab913b0b479fc29d939d03b5df95dc2a8c5a155f1b35a606e300802cb3aa9d3

View File

@ -8,15 +8,15 @@ type: reference, api
# Project remote mirrors API **(FREE)**
[Push mirrors](../user/project/repository/mirror/push.md)
defined on a project's repository settings are called "remote mirrors", and the
state of these mirrors can be queried and modified via the remote mirror API
outlined below.
defined on a project's repository settings are called "remote mirrors". You
can query and modify the state of these mirrors with the remote mirror API.
For security reasons, the `url` attribute in the API response is always scrubbed of username
and password information.
## List a project's remote mirrors
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38121) in GitLab 12.9.
Returns an Array of remote mirrors and their statuses:
Returns an array of remote mirrors and their statuses:
```plaintext
GET /projects/:id/remote_mirrors
@ -47,10 +47,6 @@ Example response:
]
```
NOTE:
For security reasons, the `url` attribute is always scrubbed of username
and password information.
## Get a single project's remote mirror
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82770) in GitLab 14.10.
@ -84,19 +80,14 @@ Example response:
}
```
NOTE:
For security reasons, the `url` attribute is always scrubbed of username
and password information.
## Create a pull mirror
Learn how to [configure a pull mirror](projects.md#configure-pull-mirroring-for-a-project) using the Projects API.
## Create a push mirror
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24189) in GitLab 12.9.
Push mirroring is disabled by default. You can enable it by including the optional parameter `enabled` when creating it:
Push mirroring is disabled by default. To enable it, include the optional parameter
`enabled` when you create the mirror:
```plaintext
POST /projects/:id/remote_mirrors
@ -106,8 +97,8 @@ POST /projects/:id/remote_mirrors
| :---------- | :----- | :--------- | :------------ |
| `url` | String | yes | The target URL to which the repository is mirrored. |
| `enabled` | Boolean | no | Determines if the mirror is enabled. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
| `keep_divergent_refs` | Boolean | no | Determines if divergent refs are skipped. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
Example request:
@ -135,8 +126,6 @@ Example response:
## Update a remote mirror's attributes
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38121) in GitLab 12.9.
Toggle a remote mirror on or off, or change which types of branches are
mirrored:
@ -148,8 +137,8 @@ PUT /projects/:id/remote_mirrors/:mirror_id
| :---------- | :----- | :--------- | :------------ |
| `mirror_id` | Integer | yes | The remote mirror ID. |
| `enabled` | Boolean | no | Determines if the mirror is enabled. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
| `keep_divergent_refs` | Boolean | no | Determines if divergent refs are skipped. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
Example request:

View File

@ -115,21 +115,22 @@ while encapsulating and isolating implementation details.
Components allow a pipeline to be assembled by using abstractions instead of having all the details defined in one place.
When using a component in a pipeline, a user shouldn't need to know the implementation details of the component and should
only rely on the provided interface. The interface will have a version / revision, so that users understand which revision they are interfacing with.
only rely on the provided interface.
A pipeline component defines its type which indicates in which context of the pipeline configuration the component can be used.
For example, a component of type X can only be used according to the type X use-case.
For best experience with any systems made of components it's fundamental that components are single purpose,
isolated, reusable and resolvable.
For best experience with any systems made of components it's fundamental that components:
- **Single purpose**: a component must focus on a single goal and the scope be as small as possible.
- **Isolation**: when a component is used in a pipeline, its implementation details should not leak outside the
- **Isolated**: when a component is used in a pipeline, its implementation details should not leak outside the
component itself and into the main pipeline.
- **Reusability:** a component is designed to be used in different pipelines.
- **Reusable**: a component is designed to be used in different pipelines.
Depending on the assumptions it's built on a component can be more or less generic.
Generic components are more reusable but may require more customization.
- **Resolvable:** When a component depends on another component, this dependency must be explicit and trackable.
- **Versioned**: when using a component we must specify the version we are interested in.
The version identifies the exact interface and behavior of the component.
- **Resolvable**: when a component depends on another component, this dependency must be explicit and trackable.
## Proposal

View File

@ -170,7 +170,10 @@ Use:
- The `project` keyword to specify the full path to a downstream project.
In [GitLab 15.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/367660), variable expansion is
supported.
- The `branch` keyword to specify the name of a branch in the project specified by `project`.
- The `branch` keyword to specify the name of a branch or [tag](../../topics/git/tags.md)
in the project specified by `project`. If you use a tag when a branch exists with the same
name, the downstream pipeline fails to create with the error: `downstream pipeline can not be created, Ref is ambiguous`.
In [GitLab 12.4 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/10126), variable expansion is
supported.

View File

@ -18,7 +18,10 @@ Use the following job in `.gitlab-ci.yml`. This includes the `artifacts:paths` k
```yaml
## Use https://github.com/sj26/rspec_junit_formatter to generate a JUnit report format XML file with rspec
ruby:
image: ruby:3.0.4
stage: test
before_script:
- apt-get update -y && apt-get install -y bundler
script:
- bundle install
- bundle exec rspec --format progress --format RspecJunitFormatter --out rspec.xml

View File

@ -277,7 +277,7 @@ they prefer read replicas and will wait for replicas to catch up:
| **Data Consistency** | **Description** |
|--------------|-----------------------------|
| `:always` | The job is required to use the primary database (default). It should be used for workers that primarily perform writes or that have strict requirements around data consistency when reading their own writes. |
| `:always` | The job is required to use the primary database (default). It should be used for workers that primarily perform writes, have strict requirements around data consistency when reading their own writes, or are cron jobs. |
| `:sticky` | The job prefers replicas, but switches to the primary for writes or when encountering replication lag. It should be used for jobs that require to be executed as fast as possible but can sustain a small initial queuing delay. |
| `:delayed` | The job prefers replicas, but switches to the primary for writes. When encountering replication lag before the job starts, the job is retried once. If the replica is still not up to date on the next retry, it switches to the primary. It should be used for jobs where delaying execution further typically does not matter, such as cache expiration or web hooks execution. |

View File

@ -17,7 +17,7 @@ module API
requires :namespace_path, type: String, desc: 'Path for the namespace that should be subscribed'
end
post do
not_found! unless Feature.enabled?(:jira_connect_oauth, current_user)
not_found! unless Feature.enabled?(:jira_connect_oauth_self_managed, current_user)
jwt = Atlassian::JiraConnect::Jwt::Symmetric.new(params[:jwt])
installation = JiraConnectInstallation.find_by_client_key(jwt.iss_claim)

View File

@ -94,7 +94,8 @@ module Gitlab
# when the variables are sent to Runner.
Gitlab::AppJsonLogger.info(
event: 'file_variable_is_referenced_in_another_variable',
project_id: project.id
project_id: project.id,
variable: variable_name
)
end

View File

@ -7,6 +7,8 @@ module Gitlab
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::SchemaHelpers
DuplicatedIndexesError = Class.new(StandardError)
ERROR_SCOPE = 'index'
# Concurrently creates a new index on a partitioned table. In concept this works similarly to
@ -92,6 +94,42 @@ module Gitlab
.map { |_, indexes| indexes.map { |index| index['index_name'] } }
end
# Retrieves a hash of index names for a given table and schema, by index
# definition.
#
# Example:
#
# indexes_by_definition_for_table('table_name_goes_here')
#
# Returns:
#
# {
# "CREATE _ btree (created_at)" => "index_on_created_at"
# }
def indexes_by_definition_for_table(table_name, schema_name: connection.current_schema)
duplicate_indexes = find_duplicate_indexes(table_name, schema_name: schema_name)
unless duplicate_indexes.empty?
raise DuplicatedIndexesError, "#{table_name} has duplicate indexes: #{duplicate_indexes}"
end
find_indexes(table_name, schema_name: schema_name)
.each_with_object({}) { |row, hash| hash[row['index_id']] = row['index_name'] }
end
# Renames indexes for a given table and schema, mapping by index
# definition, to a hash of new index names.
#
# Example:
#
# index_names = indexes_by_definition_for_table('source_table_name_goes_here')
# drop_table('source_table_name_goes_here')
# rename_indexes_for_table('destination_table_name_goes_here', index_names)
def rename_indexes_for_table(table_name, new_index_names, schema_name: connection.current_schema)
current_index_names = indexes_by_definition_for_table(table_name, schema_name: schema_name)
rename_indexes(current_index_names, new_index_names, schema_name: schema_name)
end
private
def find_indexes(table_name, schema_name: connection.current_schema)
@ -124,6 +162,18 @@ module Gitlab
def generated_index_name(partition_name, index_name)
object_name("#{partition_name}_#{index_name}", 'index')
end
def rename_indexes(from, to, schema_name: connection.current_schema)
indexes_to_rename = from.select { |index_id, _| to.has_key?(index_id) }
statements = indexes_to_rename.map do |index_id, index_name|
<<~SQL
ALTER INDEX #{connection.quote_table_name("#{schema_name}.#{connection.quote_column_name(index_name)}")}
RENAME TO #{connection.quote_column_name(to[index_id])}
SQL
end
connection.execute(statements.join(';'))
end
end
end
end

View File

@ -34,7 +34,7 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
env["BUNDLE_DEPLOYMENT"] = 'false'
end
output, status = Gitlab::Popen.popen([make_cmd, 'clean-build', 'all', 'git'], nil, env)
output, status = Gitlab::Popen.popen([make_cmd, 'clean-build', 'all'], nil, env)
raise "Gitaly failed to compile: #{output}" unless status&.zero?
end
end

View File

@ -48,7 +48,7 @@ namespace :tw do
CodeOwnerRule.new('Infrastructure', '@sselhorn'),
CodeOwnerRule.new('Integrations', '@ashrafkhamis'),
CodeOwnerRule.new('Knowledge', '@aqualls'),
CodeOwnerRule.new('Application Performance', '@sselhorn'),
CodeOwnerRule.new('Application Performance', '@jglassman1'),
CodeOwnerRule.new('Monitor', '@msedlakjakubowski'),
CodeOwnerRule.new('Observability', 'msedlakjakubowski'),
CodeOwnerRule.new('Optimize', '@lciutacu'),
@ -67,7 +67,7 @@ namespace :tw do
CodeOwnerRule.new('Release', '@rdickenson'),
CodeOwnerRule.new('Respond', '@msedlakjakubowski'),
CodeOwnerRule.new('Runner', '@sselhorn'),
CodeOwnerRule.new('Pods', '@sselhorn'),
CodeOwnerRule.new('Pods', '@jglassman1'),
CodeOwnerRule.new('Security Policies', '@claytoncornell'),
CodeOwnerRule.new('Source Code', '@aqualls'),
CodeOwnerRule.new('Static Analysis', '@rdickenson'),

View File

@ -2125,9 +2125,6 @@ msgstr ""
msgid "Active %{accessTokenTypePlural} (%{totalAccessTokens})"
msgstr ""
msgid "Active %{type} (%{token_length})"
msgstr ""
msgid "Active Sessions"
msgstr ""
@ -5215,9 +5212,6 @@ msgstr ""
msgid "Are you sure you want to revoke this %{accessTokenType}? This action cannot be undone."
msgstr ""
msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
msgstr ""
msgid "Are you sure you want to revoke this personal access token? This action cannot be undone."
msgstr ""
@ -10782,9 +10776,6 @@ msgstr ""
msgid "Copy %{protocol} clone URL"
msgstr ""
msgid "Copy %{type}"
msgstr ""
msgid "Copy ID"
msgstr ""
@ -29693,6 +29684,9 @@ msgstr ""
msgid "PipelineSchedules|Last Pipeline"
msgstr ""
msgid "PipelineSchedules|New schedule"
msgstr ""
msgid "PipelineSchedules|Next Run"
msgstr ""
@ -41750,9 +41744,6 @@ msgstr ""
msgid "This user has no active %{accessTokenTypePlural}."
msgstr ""
msgid "This user has no active %{type}."
msgstr ""
msgid "This user has no identities"
msgstr ""
@ -47024,9 +47015,6 @@ msgstr ""
msgid "Your new %{accessTokenType} has been created."
msgstr ""
msgid "Your new %{type}"
msgstr ""
msgid "Your new comment"
msgstr ""

View File

@ -18,19 +18,11 @@ module QA
element :expiry_date_field
end
base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
element :created_access_token_field
end
base.view 'app/views/shared/access_tokens/_form.html.haml' do
element :access_token_name_field
element :create_token_button
end
base.view 'app/views/shared/access_tokens/_table.html.haml' do
element :revoke_button
end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Project access token', :reliable do
describe 'Project access token' do
before(:all) do
@project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! do |pat|
pat.project = Resource::ReusableProject.fabricate_via_api!
@ -12,7 +12,7 @@ module QA
end
context 'for the same project' do
it 'can be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347858' do
it 'can be used to create a file via the project API', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347858' do
expect do
Resource::File.fabricate_via_api! do |file|
file.api_client = @user_api_client
@ -44,7 +44,7 @@ module QA
@different_project = Resource::Project.fabricate!
end
it 'cannot be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347860' do
it 'cannot be used to create a file via the project API', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347860' do
expect do
Resource::File.fabricate_via_api! do |file|
file.api_client = @user_api_client
@ -57,7 +57,7 @@ module QA
end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden/)
end
it 'cannot be used to commit via the API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347861' do
it 'cannot be used to commit via the API', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347861' do
expect do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.api_client = @user_api_client

View File

@ -24,7 +24,7 @@ RSpec.describe "User sorts issues" do
sign_in(user)
end
it 'keeps the sort option', :js do
it 'keeps the sort option', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/378184' do
visit(project_issues_path(project))
click_button 'Created date'

View File

@ -1,9 +1,10 @@
import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlLoadingIcon, GlModal, GlTabs } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { trimText } from 'helpers/text_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import PipelineSchedules from '~/pipeline_schedules/components/pipeline_schedules.vue';
import PipelineSchedulesTable from '~/pipeline_schedules/components/table/pipeline_schedules_table.vue';
import deletePipelineScheduleMutation from '~/pipeline_schedules/graphql/mutations/delete_pipeline_schedule.mutation.graphql';
@ -32,7 +33,7 @@ describe('Pipeline schedules app', () => {
};
const createComponent = (requestHandlers) => {
wrapper = shallowMount(PipelineSchedules, {
wrapper = mountExtended(PipelineSchedules, {
provide: {
fullPath: 'gitlab-org/gitlab',
},
@ -44,17 +45,24 @@ describe('Pipeline schedules app', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findModal = () => wrapper.findComponent(GlModal);
const findTabs = () => wrapper.findComponent(GlTabs);
const findNewButton = () => wrapper.findByTestId('new-schedule-button');
const findAllTab = () => wrapper.findByTestId('pipeline-schedules-all-tab');
const findActiveTab = () => wrapper.findByTestId('pipeline-schedules-active-tab');
const findInactiveTab = () => wrapper.findByTestId('pipeline-schedules-inactive-tab');
afterEach(() => {
wrapper.destroy();
});
it('displays table', async () => {
it('displays table, tabs and new button', async () => {
createComponent();
await waitForPromises();
expect(findTable().exists()).toBe(true);
expect(findNewButton().exists()).toBe(true);
expect(findTabs().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
});
@ -158,4 +166,38 @@ describe('Pipeline schedules app', () => {
expect(findModal().props('visible')).toBe(false);
});
describe('tabs', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('displays All tab with count', () => {
expect(trimText(findAllTab().text())).toBe(`All ${mockPipelineScheduleNodes.length}`);
});
it('displays Active tab with no count', () => {
expect(findActiveTab().text()).toBe('Active');
});
it('displays Inactive tab with no count', () => {
expect(findInactiveTab().text()).toBe('Inactive');
});
});
it('should refetch the schedules query on a tab click', async () => {
createComponent();
await waitForPromises();
jest.spyOn(wrapper.vm.$apollo.queries.schedules, 'refetch').mockImplementation(jest.fn());
expect(wrapper.vm.$apollo.queries.schedules.refetch).toHaveBeenCalledTimes(0);
await findAllTab().trigger('click');
expect(wrapper.vm.$apollo.queries.schedules.refetch).toHaveBeenCalledTimes(1);
});
});

View File

@ -601,7 +601,8 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
it 'logs file_variable_is_referenced_in_another_variable once for VAR5' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
event: 'file_variable_is_referenced_in_another_variable',
project_id: project.id
project_id: project.id,
variable: 'FILEVAR4'
).once
sort_and_expand_all

View File

@ -231,4 +231,165 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
end
end
end
describe '#indexes_by_definition_for_table' do
context 'when a partitioned table has indexes' do
subject do
migration.indexes_by_definition_for_table(table_name)
end
before do
connection.execute(<<~SQL)
CREATE INDEX #{index_name} ON #{table_name} (#{column_name});
SQL
end
it 'captures partitioned index names by index definition' do
expect(subject).to match(a_hash_including({ "CREATE _ btree (#{column_name})" => index_name }))
end
end
context 'when a non-partitioned table has indexes' do
let(:regular_table_name) { '_test_regular_table' }
let(:regular_index_name) { '_test_regular_index_name' }
subject do
migration.indexes_by_definition_for_table(regular_table_name)
end
before do
connection.execute(<<~SQL)
CREATE TABLE #{regular_table_name} (
#{column_name} timestamptz NOT NULL
);
CREATE INDEX #{regular_index_name} ON #{regular_table_name} (#{column_name});
SQL
end
it 'captures index names by index definition' do
expect(subject).to match(a_hash_including({ "CREATE _ btree (#{column_name})" => regular_index_name }))
end
end
context 'when a non-partitioned table has duplicate indexes' do
let(:regular_table_name) { '_test_regular_table' }
let(:regular_index_name) { '_test_regular_index_name' }
let(:duplicate_index_name) { '_test_duplicate_index_name' }
subject do
migration.indexes_by_definition_for_table(regular_table_name)
end
before do
connection.execute(<<~SQL)
CREATE TABLE #{regular_table_name} (
#{column_name} timestamptz NOT NULL
);
CREATE INDEX #{regular_index_name} ON #{regular_table_name} (#{column_name});
CREATE INDEX #{duplicate_index_name} ON #{regular_table_name} (#{column_name});
SQL
end
it 'raises an error' do
expect { subject }.to raise_error { described_class::DuplicatedIndexesError }
end
end
end
describe '#rename_indexes_for_table' do
let(:original_table_name) { '_test_rename_indexes_table' }
let(:first_partition_name) { '_test_rename_indexes_table_1' }
let(:transient_table_name) { '_test_rename_indexes_table_child' }
let(:custom_column_name) { 'created_at' }
let(:generated_column_name) { 'updated_at' }
let(:custom_index_name) { 'index_test_rename_indexes_table_on_created_at' }
let(:custom_index_name_regenerated) { '_test_rename_indexes_table_created_at_idx' }
let(:generated_index_name) { '_test_rename_indexes_table_updated_at_idx' }
let(:generated_index_name_collided) { '_test_rename_indexes_table_updated_at_idx1' }
before do
connection.execute(<<~SQL)
CREATE TABLE #{original_table_name} (
#{custom_column_name} timestamptz NOT NULL,
#{generated_column_name} timestamptz NOT NULL
);
CREATE INDEX #{custom_index_name} ON #{original_table_name} (#{custom_column_name});
CREATE INDEX ON #{original_table_name} (#{generated_column_name});
SQL
end
context 'when changing a table within the current schema' do
let!(:identifiers) { migration.indexes_by_definition_for_table(original_table_name) }
before do
connection.execute(<<~SQL)
ALTER TABLE #{original_table_name} RENAME TO #{first_partition_name};
CREATE TABLE #{original_table_name} (LIKE #{first_partition_name} INCLUDING ALL);
DROP TABLE #{first_partition_name};
SQL
end
it 'maps index names after they are changed' do
migration.rename_indexes_for_table(original_table_name, identifiers)
expect_index_to_exist(custom_index_name)
expect_index_to_exist(generated_index_name)
end
it 'does not rename an index which does not exist in the to_hash' do
partial_identifiers = identifiers.reject { |_, name| name == custom_index_name }
migration.rename_indexes_for_table(original_table_name, partial_identifiers)
expect_index_not_to_exist(custom_index_name)
expect_index_to_exist(generated_index_name)
end
end
context 'when partitioning an existing table' do
before do
connection.execute(<<~SQL)
/* Create new parent table */
CREATE TABLE #{first_partition_name} (LIKE #{original_table_name} INCLUDING ALL);
SQL
end
it 'renames indexes across schemas' do
# Capture index names generated by postgres
generated_index_names = migration.indexes_by_definition_for_table(first_partition_name)
# Capture index names from original table
original_index_names = migration.indexes_by_definition_for_table(original_table_name)
connection.execute(<<~SQL)
/* Rename original table out of the way */
ALTER TABLE #{original_table_name} RENAME TO #{transient_table_name};
/* Rename new parent table to original name */
ALTER TABLE #{first_partition_name} RENAME TO #{original_table_name};
/* Move original table to gitlab_partitions_dynamic schema */
ALTER TABLE #{transient_table_name} SET SCHEMA #{partition_schema};
/* Rename original table to be the first partition */
ALTER TABLE #{partition_schema}.#{transient_table_name} RENAME TO #{first_partition_name};
SQL
# Apply index names generated by postgres to first partition
migration.rename_indexes_for_table(first_partition_name, generated_index_names, schema_name: partition_schema)
expect_index_to_exist('_test_rename_indexes_table_1_created_at_idx')
expect_index_to_exist('_test_rename_indexes_table_1_updated_at_idx')
# Apply index names from original table to new parent table
migration.rename_indexes_for_table(original_table_name, original_index_names)
expect_index_to_exist(custom_index_name)
expect_index_to_exist(generated_index_name)
end
end
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe CleanupVulnerabilityStateTransitionsWithSameFromStateToState, :migration do
let_it_be(:namespace) { table(:namespaces).create!(name: 'namespace', type: 'Group', path: 'namespace') }
let_it_be(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) }
let_it_be(:project) do
table(:projects).create!(
path: 'project',
namespace_id: namespace.id,
project_namespace_id: namespace.id
)
end
let_it_be(:vulnerability) do
table(:vulnerabilities).create!(
project_id: project.id,
author_id: user.id,
title: 'test',
severity: 7,
confidence: 7,
report_type: 0
)
end
let_it_be(:state_transitions) { table(:vulnerability_state_transitions) }
let!(:state_transition_with_no_state_change) do
state_transitions.create!(
vulnerability_id: vulnerability.id,
from_state: 2,
to_state: 2
)
end
let!(:state_transition_with_state_change) do
state_transitions.create!(
vulnerability_id: vulnerability.id,
from_state: 1,
to_state: 2
)
end
it 'deletes state transitions with no state change' do
expect { migrate! }.to change(state_transitions, :count).from(2).to(1)
end
end

View File

@ -2356,7 +2356,7 @@ RSpec.describe Namespace do
end
end
describe 'storage_enforcement_date' do
describe 'storage_enforcement_date', :freeze_time do
let_it_be(:namespace) { create(:group) }
before do
@ -2364,7 +2364,7 @@ RSpec.describe Namespace do
end
it 'returns correct date' do
expect(namespace.storage_enforcement_date).to eql(Date.new(2022, 10, 19))
expect(namespace.storage_enforcement_date).to eql(3.months.from_now.to_date)
end
context 'when :storage_banner_bypass_date_check is enabled' do
@ -2372,7 +2372,7 @@ RSpec.describe Namespace do
stub_feature_flags(namespace_storage_limit_bypass_date_check: true)
end
it 'returns the current date', :freeze_time do
it 'returns the current date' do
expect(namespace.storage_enforcement_date).to eq(Date.current)
end
end

View File

@ -194,47 +194,6 @@ RSpec.describe PersonalAccessToken do
end
end
describe 'Redis storage' do
let(:user_id) { 123 }
let(:token) { 'KS3wegQYXBLYhQsciwsj' }
context 'reading encrypted data' do
before do
subject.redis_store!(user_id, token)
end
it 'returns stored data' do
expect(subject.redis_getdel(user_id)).to eq(token)
end
end
context 'reading unencrypted data' do
before do
Gitlab::Redis::SharedState.with do |redis|
redis.set(described_class.redis_shared_state_key(user_id),
token,
ex: PersonalAccessToken::REDIS_EXPIRY_TIME)
end
end
it 'returns stored data unmodified' do
expect(subject.redis_getdel(user_id)).to eq(token)
end
end
context 'after deletion' do
before do
subject.redis_store!(user_id, token)
expect(subject.redis_getdel(user_id)).to eq(token)
end
it 'token is removed' do
expect(subject.redis_getdel(user_id)).to be_nil
end
end
end
context "validations" do
let(:personal_access_token) { build(:personal_access_token) }
@ -365,7 +324,7 @@ RSpec.describe PersonalAccessToken do
describe '.simple_sorts' do
it 'includes overridden keys' do
expect(described_class.simple_sorts.keys).to include(*%w(expires_at_asc expires_at_desc expires_at_asc_id_desc))
expect(described_class.simple_sorts.keys).to include(*%w(expires_at_asc_id_desc))
end
end
@ -373,18 +332,6 @@ RSpec.describe PersonalAccessToken do
let_it_be(:earlier_token) { create(:personal_access_token, expires_at: 2.days.ago) }
let_it_be(:later_token) { create(:personal_access_token, expires_at: 1.day.ago) }
describe '.order_expires_at_asc' do
it 'returns ordered list in asc order of expiry date' do
expect(described_class.order_expires_at_asc).to match [earlier_token, later_token]
end
end
describe '.order_expires_at_desc' do
it 'returns ordered list in desc order of expiry date' do
expect(described_class.order_expires_at_desc).to match [later_token, earlier_token]
end
end
describe '.order_expires_at_asc_id_desc' do
let_it_be(:earlier_token_2) { create(:personal_access_token, expires_at: 2.days.ago) }

View File

@ -353,7 +353,8 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'logs file_variable_is_referenced_in_another_variable' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
event: 'file_variable_is_referenced_in_another_variable',
project_id: project.id
project_id: project.id,
variable: 'file_var'
).once
runner_variables

View File

@ -22,7 +22,7 @@ RSpec.describe API::Integrations::JiraConnect::Subscriptions do
context 'with feature flag disabled' do
before do
stub_feature_flags(jira_connect_oauth: false)
stub_feature_flags(jira_connect_oauth_self_managed: false)
end
let(:jwt) { '123' }

View File

@ -28,9 +28,9 @@ RSpec.describe JiraConnect::SubscriptionsController do
it { is_expected.not_to include('http://self-managed-gitlab.com/api/') }
end
context 'with jira_connect_oauth_self_managed feature disabled' do
context 'with jira_connect_oauth_self_managed_setting feature disabled' do
before do
stub_feature_flags(jira_connect_oauth_self_managed: false)
stub_feature_flags(jira_connect_oauth_self_managed_setting: false)
end
it { is_expected.not_to include('http://self-managed-gitlab.com/-/jira_connect/') }

View File

@ -3379,7 +3379,6 @@
- './ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb'
- './ee/spec/views/registrations/welcome/show.html.haml_spec.rb'
- './ee/spec/views/search/_category.html.haml_spec.rb'
- './ee/spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plan.html.haml_spec.rb'
- './ee/spec/views/shared/billings/_billing_plans.html.haml_spec.rb'
@ -10767,7 +10766,6 @@
- './spec/views/registrations/welcome/show.html.haml_spec.rb'
- './spec/views/search/_results.html.haml_spec.rb'
- './spec/views/search/show.html.haml_spec.rb'
- './spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- './spec/views/shared/deploy_tokens/_form.html.haml_spec.rb'
- './spec/views/shared/groups/_dropdown.html.haml_spec.rb'
- './spec/views/shared/issuable/_sidebar.html.haml_spec.rb'

View File

@ -66,7 +66,7 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
.with(%w[which gmake])
.and_return(['/usr/bin/gmake', 0])
expect(Gitlab::Popen).to receive(:popen)
.with(%w[gmake clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[gmake clean-build all], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['ok', 0])
subject
@ -78,7 +78,7 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
.with(%w[which gmake])
.and_return(['/usr/bin/gmake', 0])
expect(Gitlab::Popen).to receive(:popen)
.with(%w[gmake clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[gmake clean-build all], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['output', 1])
expect { subject }.to raise_error /Gitaly failed to compile: output/
@ -95,14 +95,14 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
it 'calls make in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen)
.with(%w[make clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[make clean-build all], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['output', 0])
subject
end
context 'when Rails.env is test' do
let(:command) { %w[make clean-build all git] }
let(:command) { %w[make clean-build all] }
before do
stub_rails_env('test')

View File

@ -76,9 +76,9 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
expect(rendered).to have_css('#js-jira_connect-settings')
end
context 'when the jira_connect_oauth feature flag is disabled' do
context 'when the jira_connect_oauth_self_managed_setting feature flag is disabled' do
before do
stub_feature_flags(jira_connect_oauth: false)
stub_feature_flags(jira_connect_oauth_self_managed_setting: false)
end
it 'does not show the jira connect application key section' do

View File

@ -1,151 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'shared/access_tokens/_table.html.haml' do
let(:type) { 'token' }
let(:type_plural) { 'tokens' }
let(:empty_message) { nil }
let(:impersonation) { false }
let_it_be(:user) { create(:user) }
let_it_be(:tokens) { [create(:personal_access_token, user: user)] }
let_it_be(:resource) { false }
before do
if resource
resource.add_maintainer(user)
end
# Forcibly removing scopes from one token as it's not possible to do with the current modal on creation
# But the check exists in the template (it may be there for legacy reasons), so we should test the outcome
if tokens.size > 1
tokens[1].scopes = []
end
locals = {
type: type,
type_plural: type_plural,
active_tokens: tokens,
resource: resource,
impersonation: impersonation,
revoke_route_helper: ->(token) { 'path/' }
}
if empty_message
locals[:no_active_tokens_message] = empty_message
end
render partial: 'shared/access_tokens/table', locals: locals
end
context 'if personal' do
it 'does not show non-personal content', :aggregate_failures do
expect(rendered).not_to have_content 'To see all the user\'s personal access tokens you must impersonate them first.'
expect(rendered).not_to have_selector 'th', text: 'Role'
end
end
context 'if impersonation' do
let(:impersonation) { true }
it 'shows the impersonation content', :aggregate_failures do
expect(rendered).to have_content 'To see all the user\'s personal access tokens you must impersonate them first.'
expect(rendered).not_to have_content 'Personal access tokens are not revoked upon expiration.'
expect(rendered).not_to have_selector 'th', text: 'Role'
end
end
context 'if resource is project' do
let_it_be(:resource) { create(:project) }
it 'shows the project content', :aggregate_failures do
expect(rendered).to have_selector 'th', text: 'Role'
expect(rendered).to have_selector 'td', text: 'Maintainer'
expect(rendered).not_to have_content 'Personal access tokens are not revoked upon expiration.'
expect(rendered).not_to have_content 'To see all the user\'s personal access tokens you must impersonate them first.'
end
end
context 'if resource is group' do
let_it_be(:resource) { create(:group) }
it 'shows the group content', :aggregate_failures do
expect(rendered).to have_selector 'th', text: 'Role'
expect(rendered).to have_selector 'td', text: 'Maintainer'
expect(rendered).not_to have_content 'Personal access tokens are not revoked upon expiration.'
expect(rendered).not_to have_content 'To see all the user\'s personal access tokens you must impersonate them first.'
end
end
context 'without tokens' do
let_it_be(:tokens) { [] }
it 'has the correct content', :aggregate_failures do
expect(rendered).to have_content 'Active tokens (0)'
expect(rendered).to have_content 'This user has no active tokens.'
end
context 'with a custom empty text' do
let(:empty_message) { 'Custom empty message' }
it 'shows the custom empty text' do
expect(rendered).to have_content empty_message
end
end
end
context 'with tokens' do
let_it_be(:tokens) do
[
create(:personal_access_token, user: user, name: 'Access token', last_used_at: 4.days.from_now, expires_at: nil, scopes: [:read_api, :read_user]),
create(:personal_access_token, user: user, expires_at: 1.day.from_now, scopes: [:read_api, :read_user])
]
end
let_it_be(:expired_token) { build(:personal_access_token, name: "Expired token", expires_at: 2.days.ago).tap { |t| t.save!(validate: false) } }
it 'has the correct content', :aggregate_failures do
# Heading content
expect(rendered).to have_content 'Active tokens (2)'
# Table headers
expect(rendered).to have_selector 'th', text: 'Token name'
expect(rendered).to have_selector 'th', text: 'Scopes'
expect(rendered).to have_selector 'th', text: 'Created'
expect(rendered).to have_selector 'th', text: 'Last Used'
expect(rendered).to have_selector 'th', text: 'Expires'
# Table contents
expect(rendered).to have_content 'Access token'
expect(rendered).not_to have_content 'Expired token'
expect(rendered).to have_content 'read_api, read_user'
expect(rendered).to have_content 'no scopes selected'
expect(rendered).to have_content Time.now.to_date.to_s(:medium)
expect(rendered).to have_content l(4.days.from_now, format: "%b %d, %Y")
# Revoke buttons
expect(rendered).to have_link 'Revoke', href: 'path/', class: 'btn-danger-secondary', count: 1
expect(rendered).to have_link 'Revoke', href: 'path/', count: 2
end
context 'without the last used time' do
let_it_be(:tokens) { [create(:personal_access_token, user: user, expires_at: 5.days.ago)] }
it 'shows the last used empty text' do
expect(rendered).to have_content 'Never'
end
end
context 'without expired at' do
let_it_be(:tokens) { [create(:personal_access_token, user: user, expires_at: nil, last_used_at: 1.day.ago)] }
it 'shows the expired at empty text' do
expect(rendered).to have_content 'Never'
end
end
end
end