diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-20 18:09:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-20 18:09:10 +0000 |
commit | a3764262c04bafcd6a54aff635541d73a8a630fd (patch) | |
tree | ea54444857967f08b7601886b47d15819990b6cf | |
parent | 049d16d168fdee408b78f5f38619c092fd3b2265 (diff) | |
download | gitlab-ce-a3764262c04bafcd6a54aff635541d73a8a630fd.tar.gz |
Add latest changes from gitlab-org/gitlab@master
50 files changed, 517 insertions, 440 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 689c05f4874..3d2f32a0390 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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" diff --git a/.rubocop_todo/gitlab/json.yml b/.rubocop_todo/gitlab/json.yml index 9a946f11f84..ec48c04f3ed 100644 --- a/.rubocop_todo/gitlab/json.yml +++ b/.rubocop_todo/gitlab/json.yml @@ -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' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index fe9c0593f7d..eaa8e6485be 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -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' diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml index 8e295c68623..16d163ebdfc 100644 --- a/.rubocop_todo/style/if_unless_modifier.yml +++ b/.rubocop_todo/style/if_unless_modifier.yml @@ -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' @@ -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 diff --git a/Gemfile.checksum b/Gemfile.checksum index c145415591b..74500a24d73 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -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"}, diff --git a/Gemfile.lock b/Gemfile.lock index d2b6d367ae4..8d2b983cf00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue index 8894e8f63b8..254248ef1d4 100644 --- a/app/assets/javascripts/issuable/components/related_issuable_item.vue +++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue @@ -141,6 +141,7 @@ export default { <gl-link :href="computedPath" class="sortable-link gl-font-weight-normal" + target="_blank" @click="handleTitleClick" > {{ title }} diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue index 4a08a82275a..b055b89c528 100644 --- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue +++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue @@ -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" /> + <template v-else> + <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> - <!-- Tabs will be addressed in #371989 --> + <template v-if="tab.showBadge"> + <gl-loading-icon v-if="tab.scope === scope && isLoading" class="gl-ml-2" /> - <template v-else> - <pipeline-schedules-table :schedules="schedules" @showDeleteModal="showDeleteModal" /> + <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" diff --git a/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue b/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue index d54008b81b2..da2157a8851 100644 --- a/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue +++ b/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue @@ -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', }, ], diff --git a/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql b/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql index 7d9d658b1b6..9f6cb429cca 100644 --- a/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql +++ b/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql @@ -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 diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb index 9305f46c39e..751481f78e2 100644 --- a/app/controllers/jira_connect/subscriptions_controller.rb +++ b/app/controllers/jira_connect/subscriptions_controller.rb @@ -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? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 42f362876bb..12b96f34316 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -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 diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index f0ed1822da6..3126dba9d6d 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -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') diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index ec5d1ef4a34..db407ae35c4 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -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) diff --git a/app/views/ci/variables/_url_query_variable_row.html.haml b/app/views/ci/variables/_url_query_variable_row.html.haml deleted file mode 100644 index 77bcacdb94b..00000000000 --- a/app/views/ci/variables/_url_query_variable_row.html.haml +++ /dev/null @@ -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') diff --git a/app/views/shared/access_tokens/_created_container.html.haml b/app/views/shared/access_tokens/_created_container.html.haml deleted file mode 100644 index c0aaa46e761..00000000000 --- a/app/views/shared/access_tokens/_created_container.html.haml +++ /dev/null @@ -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 diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml deleted file mode 100644 index 53c6800f93d..00000000000 --- a/app/views/shared/access_tokens/_table.html.haml +++ /dev/null @@ -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 diff --git a/config/feature_flags/development/jira_connect_oauth_self_managed_setting.yml b/config/feature_flags/development/jira_connect_oauth_self_managed_setting.yml new file mode 100644 index 00000000000..05232d0f80a --- /dev/null +++ b/config/feature_flags/development/jira_connect_oauth_self_managed_setting.yml @@ -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 diff --git a/db/docs/software_license_policies.yml b/db/docs/software_license_policies.yml index 478c68d8c59..615ae644985 100644 --- a/db/docs/software_license_policies.yml +++ b/db/docs/software_license_policies.yml @@ -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' diff --git a/db/docs/software_licenses.yml b/db/docs/software_licenses.yml index 48e78c8ca02..67ebd697fa8 100644 --- a/db/docs/software_licenses.yml +++ b/db/docs/software_licenses.yml @@ -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' diff --git a/db/post_migrate/20221013215832_cleanup_vulnerability_state_transitions_with_same_from_state_to_state.rb b/db/post_migrate/20221013215832_cleanup_vulnerability_state_transitions_with_same_from_state_to_state.rb new file mode 100644 index 00000000000..a81a80deb25 --- /dev/null +++ b/db/post_migrate/20221013215832_cleanup_vulnerability_state_transitions_with_same_from_state_to_state.rb @@ -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 diff --git a/db/schema_migrations/20221013215832 b/db/schema_migrations/20221013215832 new file mode 100644 index 00000000000..106cb540d6e --- /dev/null +++ b/db/schema_migrations/20221013215832 @@ -0,0 +1 @@ +2ab913b0b479fc29d939d03b5df95dc2a8c5a155f1b35a606e300802cb3aa9d3
\ No newline at end of file diff --git a/doc/api/remote_mirrors.md b/doc/api/remote_mirrors.md index 1013ffb49fb..bd59aa64e45 100644 --- a/doc/api/remote_mirrors.md +++ b/doc/api/remote_mirrors.md @@ -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. -## List a project's remote mirrors +For security reasons, the `url` attribute in the API response is always scrubbed of username +and password information. -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/38121) in GitLab 12.9. +## List a project's remote mirrors -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: diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md index 115f6909d2d..6e5ab8040ae 100644 --- a/doc/architecture/blueprints/ci_pipeline_components/index.md +++ b/doc/architecture/blueprints/ci_pipeline_components/index.md @@ -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 diff --git a/doc/ci/pipelines/downstream_pipelines.md b/doc/ci/pipelines/downstream_pipelines.md index 0b1963e1874..c6fffa0ec59 100644 --- a/doc/ci/pipelines/downstream_pipelines.md +++ b/doc/ci/pipelines/downstream_pipelines.md @@ -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. diff --git a/doc/ci/testing/unit_test_report_examples.md b/doc/ci/testing/unit_test_report_examples.md index c14e4eedd7c..5d4cfa88d88 100644 --- a/doc/ci/testing/unit_test_report_examples.md +++ b/doc/ci/testing/unit_test_report_examples.md @@ -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 diff --git a/doc/development/sidekiq/worker_attributes.md b/doc/development/sidekiq/worker_attributes.md index 48a222d65a0..4fcd8e33d5c 100644 --- a/doc/development/sidekiq/worker_attributes.md +++ b/doc/development/sidekiq/worker_attributes.md @@ -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. | diff --git a/lib/api/integrations/jira_connect/subscriptions.rb b/lib/api/integrations/jira_connect/subscriptions.rb index a6e931ba7bb..87d6985cead 100644 --- a/lib/api/integrations/jira_connect/subscriptions.rb +++ b/lib/api/integrations/jira_connect/subscriptions.rb @@ -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) diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb index b6d6e1a3e5f..1dbe5481807 100644 --- a/lib/gitlab/ci/variables/collection.rb +++ b/lib/gitlab/ci/variables/collection.rb @@ -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 diff --git a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb index 15b542cf089..eb7f62395d0 100644 --- a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb +++ b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb @@ -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 diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake index 960d0e51a47..e814d59aaf9 100644 --- a/lib/tasks/gitlab/gitaly.rake +++ b/lib/tasks/gitlab/gitaly.rake @@ -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 diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake index 2d06792d656..19337f50f1b 100644 --- a/lib/tasks/gitlab/tw/codeowners.rake +++ b/lib/tasks/gitlab/tw/codeowners.rake @@ -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'), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f5fb7306a9d..7b4956c8e45 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb index 3c8a608cdc2..8723f6619d9 100644 --- a/qa/qa/page/component/access_tokens.rb +++ b/qa/qa/page/component/access_tokens.rb @@ -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 diff --git a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb index c4be90d3759..756bbe2164a 100644 --- a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb +++ b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb @@ -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 diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index 7add6c782f7..2716d742be3 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -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' diff --git a/spec/frontend/pipeline_schedules/components/pipeline_schedules_spec.js b/spec/frontend/pipeline_schedules/components/pipeline_schedules_spec.js index cce8f480928..2a7ccce1092 100644 --- a/spec/frontend/pipeline_schedules/components/pipeline_schedules_spec.js +++ b/spec/frontend/pipeline_schedules/components/pipeline_schedules_spec.js @@ -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); + }); }); diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 7d4a1eef70b..4710698fd95 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -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 diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb index 7465f69b87c..5d02266e8a2 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb @@ -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 diff --git a/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb b/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb new file mode 100644 index 00000000000..92ece81ffc8 --- /dev/null +++ b/spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb @@ -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 diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index c6d028af22d..838e5d700c1 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -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 diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index 67e7d444d25..73ec51ea801 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -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) } diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index 396fe7843ba..8d6278ab1d7 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -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 diff --git a/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb b/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb index 8a222a99b34..6b26eaad2f3 100644 --- a/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb +++ b/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb @@ -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' } diff --git a/spec/requests/jira_connect/subscriptions_controller_spec.rb b/spec/requests/jira_connect/subscriptions_controller_spec.rb index f407ea09250..ee5c26295fd 100644 --- a/spec/requests/jira_connect/subscriptions_controller_spec.rb +++ b/spec/requests/jira_connect/subscriptions_controller_spec.rb @@ -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/') } diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 0558bfd51c3..b8fe8412d55 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -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' diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index e57021f749b..d2f4fa0b8ef 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -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') diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb index c7d156cde39..a9c470e49df 100644 --- a/spec/views/admin/application_settings/general.html.haml_spec.rb +++ b/spec/views/admin/application_settings/general.html.haml_spec.rb @@ -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 diff --git a/spec/views/shared/access_tokens/_table.html.haml_spec.rb b/spec/views/shared/access_tokens/_table.html.haml_spec.rb deleted file mode 100644 index 74de9e12d04..00000000000 --- a/spec/views/shared/access_tokens/_table.html.haml_spec.rb +++ /dev/null @@ -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 |