diff options
32 files changed, 351 insertions, 75 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index beb05183b55..83f706a8903 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -7c2fcde23bd4a962409897adbbb71da11c6db99a +a31bd1be25d0ff03efaa7f756321ea9440122b24 diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue index d460f764c00..85a40b89b77 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue @@ -13,6 +13,7 @@ import { __ } from '~/locale'; import Tracking from '~/tracking'; import { NOT_ENOUGH_DATA_ERROR, + FIELD_KEY_TITLE, PAGINATION_SORT_FIELD_END_EVENT, PAGINATION_SORT_FIELD_DURATION, PAGINATION_SORT_DIRECTION_ASC, @@ -22,7 +23,8 @@ import TotalTime from './total_time.vue'; const DEFAULT_WORKFLOW_TITLE_PROPERTIES = { thClass: 'gl-w-half', - key: PAGINATION_SORT_FIELD_END_EVENT, + key: FIELD_KEY_TITLE, + sortable: false, }; const WORKFLOW_COLUMN_TITLES = { @@ -132,14 +134,16 @@ export default { return [ this.workflowTitle, { + key: PAGINATION_SORT_FIELD_END_EVENT, + label: __('Last event'), + sortable: this.sortable, + }, + { key: PAGINATION_SORT_FIELD_DURATION, - label: __('Time'), - thClass: 'gl-w-half', + label: __('Duration'), + sortable: this.sortable, }, - ].map((field) => ({ - ...field, - sortable: this.sortable, - })); + ]; }, prevPage() { return Math.max(this.pagination.page - 1, 0); @@ -201,7 +205,7 @@ export default { :empty-text="emptyStateMessage" @sort-changed="onSort" > - <template v-if="stageCount" #head(end_event)="data"> + <template v-if="stageCount" #head(title)="data"> <span>{{ data.label }}</span ><gl-badge class="gl-ml-2" size="sm" ><formatted-stage-count :stage-count="stageCount" @@ -210,7 +214,10 @@ export default { <template #head(duration)="data"> <span data-testid="vsa-stage-header-duration">{{ data.label }}</span> </template> - <template #cell(end_event)="{ item }"> + <template #head(end_event)="data"> + <span data-testid="vsa-stage-header-last-event">{{ data.label }}</span> + </template> + <template #cell(title)="{ item }"> <div data-testid="vsa-stage-event"> <div v-if="item.id" data-testid="vsa-stage-content"> <p class="gl-m-0"> @@ -282,6 +289,9 @@ export default { <template #cell(duration)="{ item }"> <total-time :time="item.totalTime" data-testid="vsa-stage-event-time" /> </template> + <template #cell(end_event)="{ item }"> + <span data-testid="vsa-stage-last-event">{{ item.endEventTimestamp }}</span> + </template> </gl-table> <gl-pagination v-if="pagination && !isLoading && !isEmptyStage" diff --git a/app/assets/javascripts/cycle_analytics/constants.js b/app/assets/javascripts/cycle_analytics/constants.js index f0b2bd9dc5b..2758d686fb1 100644 --- a/app/assets/javascripts/cycle_analytics/constants.js +++ b/app/assets/javascripts/cycle_analytics/constants.js @@ -22,6 +22,7 @@ export const PAGINATION_SORT_FIELD_END_EVENT = 'end_event'; export const PAGINATION_SORT_FIELD_DURATION = 'duration'; export const PAGINATION_SORT_DIRECTION_DESC = 'desc'; export const PAGINATION_SORT_DIRECTION_ASC = 'asc'; +export const FIELD_KEY_TITLE = 'title'; export const I18N_VSA_ERROR_STAGES = __( 'There was an error fetching value stream analytics stages.', diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue index d0594d1ad27..097ab3b4cf6 100644 --- a/app/assets/javascripts/jobs/components/job_container_item.vue +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -64,9 +64,10 @@ export default { v-if="isActive" name="arrow-right" class="icon-arrow-right gl-absolute gl-display-block" + :size="14" /> - <ci-icon :status="job.status" /> + <ci-icon :status="job.status" class="gl-mr-2" :size="14" /> <span class="gl-text-truncate gl-w-full">{{ jobName }}</span> diff --git a/app/assets/stylesheets/page_bundles/build.scss b/app/assets/stylesheets/page_bundles/build.scss index 9213754d419..d40c03b7fd1 100644 --- a/app/assets/stylesheets/page_bundles/build.scss +++ b/app/assets/stylesheets/page_bundles/build.scss @@ -170,12 +170,6 @@ width: 289px; overflow: auto; - svg { - margin-right: 3px; - height: 14px; - width: 14px; - } - a { padding: $gl-padding 10px $gl-padding 40px; width: 270px; diff --git a/app/models/container_registry/event.rb b/app/models/container_registry/event.rb index 5409bdf5af4..b9fa61b4edf 100644 --- a/app/models/container_registry/event.rb +++ b/app/models/container_registry/event.rb @@ -78,6 +78,7 @@ module ContainerRegistry return unless project return unless Feature.enabled?(:container_registry_project_statistics, project) + Rails.cache.delete(project.root_ancestor.container_repositories_size_cache_key) ProjectCacheWorker.perform_async(project.id, [], [:container_registry_size]) end end diff --git a/app/models/group.rb b/app/models/group.rb index 86f4b14cb6c..5369e873d17 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -863,6 +863,12 @@ class Group < Namespace end end + def gitlab_deploy_token + strong_memoize(:gitlab_deploy_token) do + deploy_tokens.gitlab_deploy_token + end + end + private def feature_flag_enabled_for_self_or_ancestor?(feature_flag) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index ec4b9786945..7ecf97ce4c2 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -427,14 +427,21 @@ class Namespace < ApplicationRecord aggregation_schedule.present? end + def container_repositories_size_cache_key + "namespaces:#{id}:container_repositories_size" + end + def container_repositories_size strong_memoize(:container_repositories_size) do next unless Gitlab.com? + next unless root? next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api? next 0 if all_container_repositories.empty? next unless all_container_repositories.all_migrated? - ContainerRegistry::GitlabApiClient.deduplicated_size(full_path) + Rails.cache.fetch(container_repositories_size_cache_key, expires_in: 7.days) do + ContainerRegistry::GitlabApiClient.deduplicated_size(full_path) + end end end diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 78cddaa1302..3461104ae35 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -81,7 +81,9 @@ class PoolRepository < ApplicationRecord object_pool.link(repository.raw) end - def mark_obsolete_if_last(repository) + def unlink_repository(repository) + repository.disconnect_alternates + if member_projects.where.not(id: repository.project.id).exists? true else diff --git a/app/models/project.rb b/app/models/project.rb index a215ff2a878..0f6a65be230 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2509,7 +2509,13 @@ class Project < ApplicationRecord end def gitlab_deploy_token - @gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token + strong_memoize(:gitlab_deploy_token) do + if Feature.enabled?(:ci_variable_for_group_gitlab_deploy_token, self) + deploy_tokens.gitlab_deploy_token || group&.gitlab_deploy_token + else + deploy_tokens.gitlab_deploy_token + end + end end def any_lfs_file_locks? @@ -2566,7 +2572,7 @@ class Project < ApplicationRecord end def leave_pool_repository - pool_repository&.mark_obsolete_if_last(repository) && update_column(:pool_repository_id, nil) + pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil) end def link_pool_repository diff --git a/config/feature_flags/development/ci_variable_for_group_gitlab_deploy_token.yml b/config/feature_flags/development/ci_variable_for_group_gitlab_deploy_token.yml new file mode 100644 index 00000000000..6474e8aa85e --- /dev/null +++ b/config/feature_flags/development/ci_variable_for_group_gitlab_deploy_token.yml @@ -0,0 +1,8 @@ +--- +name: ci_variable_for_group_gitlab_deploy_token +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88696 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363621 +milestone: '15.1' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/db/migrate/20220531100920_add_license_usage_data_exported_to_application_settings.rb b/db/migrate/20220531100920_add_license_usage_data_exported_to_application_settings.rb new file mode 100644 index 00000000000..825697d5387 --- /dev/null +++ b/db/migrate/20220531100920_add_license_usage_data_exported_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddLicenseUsageDataExportedToApplicationSettings < Gitlab::Database::Migration[2.0] + enable_lock_retries! + + def change + add_column :application_settings, :license_usage_data_exported, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20220531100920 b/db/schema_migrations/20220531100920 new file mode 100644 index 00000000000..8010729a4a1 --- /dev/null +++ b/db/schema_migrations/20220531100920 @@ -0,0 +1 @@ +55c13dd2cf8db2ca54d3fb1bd09d459e90a90e01b3c1f7ad950e4b618df241af
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 1183e7d329a..9eff36609e7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11319,6 +11319,7 @@ CREATE TABLE application_settings ( jira_connect_application_key text, globally_allowed_ips text DEFAULT ''::text NOT NULL, container_registry_pre_import_tags_rate numeric(6,2) DEFAULT 0.5 NOT NULL, + license_usage_data_exported boolean DEFAULT false NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 2359f0fc2d1..c9b59ba66b5 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -898,6 +898,44 @@ def down end ``` +## Dropping a sequence + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88387) in GitLab 15.1. + +Dropping a sequence is uncommon, but you can use the `drop_sequence` method provided by the database team. + +Under the hood, it works like this: + +Remove a sequence: + +- Remove the default value if the sequence is actually used. +- Execute `DROP SEQUENCE`. + +Re-add a sequence: + +- Create the sequence, with the possibility of specifying the current value. +- Change the default value of the column. + +A Rails migration example: + +```ruby +class DropSequenceTest < Gitlab::Database::Migration[2.0] + def up + drop_sequence(:ci_pipelines_config, :pipeline_id, :ci_pipelines_config_pipeline_id_seq) + end + + def down + default_value = Ci::Pipeline.maximum(:id) + 10_000 + + add_sequence(:ci_pipelines_config, :pipeline_id, :ci_pipelines_config_pipeline_id_seq, default_value) + end +end +``` + +NOTE: +`add_sequence` should be avoided for columns with foreign keys. +Adding sequence to these columns is **only allowed** in the down method (restore previous schema state). + ## Integer column type By default, an integer column can hold up to a 4-byte (32-bit) number. That is diff --git a/doc/user/analytics/value_stream_analytics.md b/doc/user/analytics/value_stream_analytics.md index a428db75a28..afefbee7ca0 100644 --- a/doc/user/analytics/value_stream_analytics.md +++ b/doc/user/analytics/value_stream_analytics.md @@ -43,8 +43,7 @@ To view value stream analytics for your project: - In the **From** field, select a start date. - In the **To** field, select an end date. 1. Optional. Sort results by ascending or descending: - - To sort by most recent or oldest workflow item, select the **Merge requests** or **Issues** - header. The header name differs based on the stage you select. + - To sort by most recent or oldest workflow item, select the **Last event** header. - To sort by most or least amount of time spent in each stage, select the **Time** header. The table shows a list of related workflow items for the selected stage. Based on the stage you choose, this can be: diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md index 8908052f4d7..c2b729ebcc2 100644 --- a/doc/user/group/value_stream_analytics/index.md +++ b/doc/user/group/value_stream_analytics/index.md @@ -50,8 +50,7 @@ To view value stream analytics for your group: - In the **To** field, select an end date. The charts and list show workflow items created during the date range. 1. Optional. Sort results by ascending or descending: - - To sort by most recent or oldest workflow item, select the **Merge requests** or **Issues** - header. The header name differs based on the stage you select. + - To sort by most recent or oldest workflow item, select the **Last event** header. - To sort by most or least amount of time spent in each stage, select the **Time** header. A badge next to the workflow items table header shows the number of workflow items that diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 64c18ab6f3b..0854b95275a 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -190,6 +190,8 @@ To pull images from the Dependency Proxy, you must: ### GitLab deploy token +> Support for `gitlab-deploy-token` at the group level [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214014) in GitLab 15.1 [with a flag](../../../administration/feature_flags.md) named `ci_variable_for_group_gitlab_deploy_token`. Disabled by default. + There's a special case when it comes to deploy tokens. If a user creates one named `gitlab-deploy-token`, the username and token of the deploy token is automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER` @@ -203,9 +205,10 @@ docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY ``` NOTE: -The special handling for the `gitlab-deploy-token` deploy token is not -implemented for group deploy tokens. To make the group-level deploy token available for -CI/CD jobs, the `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD` variables should be set under **Settings** to the name and token of the group deploy token respectively. +In GitLab 15.0 and earlier, the special handling for the `gitlab-deploy-token` deploy token +does not work for group deploy tokens. To make the group-level deploy token available +for CI/CD jobs, the `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD` CI/CD variables must be +set in **Settings > CI/CD > Variables** to the name and token of the group deploy token. ## Troubleshooting diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md index 87c08ce5d62..20c16ed64b2 100644 --- a/doc/user/project/merge_requests/test_coverage_visualization.md +++ b/doc/user/project/merge_requests/test_coverage_visualization.md @@ -394,3 +394,27 @@ run tests: coverage_format: cobertura path: coverage/coverage.xml ``` + +## Troubleshooting + +### Test coverage visualization not displayed + +If the test coverage visualization is not displayed in the diff view, you can check +the coverage report itself and verify that: + +- The file you are viewing in the diff view is mentioned in the coverage report. +- The `source` and `filename` nodes in the report follows the [expected structure](#automatic-class-path-correction) + to match the files in your repository. + +Report artifacts are not downloadable by default. If you want the report to be downloadable +from the job details page, add your coverage report to the artifact `paths`: + +```yaml +artifacts: + paths: + - coverage/cobertura-coverage.xml + reports: + coverage_report: + coverage_format: cobertura + path: coverage/cobertura-coverage.xml +``` diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 2bdb839875b..4bb1d71ce18 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1499,6 +1499,20 @@ into similar problems in the future (e.g. when new tables are created). SQL end + def drop_sequence(table_name, column_name, sequence_name) + execute <<~SQL + ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} DROP DEFAULT; + DROP SEQUENCE IF EXISTS #{quote_table_name(sequence_name)} + SQL + end + + def add_sequence(table_name, column_name, sequence_name, start_value) + execute <<~SQL + CREATE SEQUENCE #{quote_table_name(sequence_name)} START #{start_value}; + ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT nextval(#{quote(sequence_name)}) + SQL + end + private def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ce47193b965..1344728ec96 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22362,6 +22362,9 @@ msgstr "" msgid "Last edited by %{link_start}%{avatar} %{name}%{link_end}" msgstr "" +msgid "Last event" +msgstr "" + msgid "Last item before this page loaded in your browser:" msgstr "" @@ -33858,9 +33861,6 @@ msgstr "" msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}" msgstr "" -msgid "SecurityOrchestration|Group level policy" -msgstr "" - msgid "SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security." msgstr "" diff --git a/spec/frontend/cycle_analytics/stage_table_spec.js b/spec/frontend/cycle_analytics/stage_table_spec.js index 0d15d67866d..473e1d5b664 100644 --- a/spec/frontend/cycle_analytics/stage_table_spec.js +++ b/spec/frontend/cycle_analytics/stage_table_spec.js @@ -27,6 +27,7 @@ const findTableHeadColumns = () => findTableHead().findAll('th'); const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title'); const findStageEventLink = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-link'); const findStageTime = () => wrapper.findByTestId('vsa-stage-event-time'); +const findStageLastEvent = () => wrapper.findByTestId('vsa-stage-last-event'); const findIcon = (name) => wrapper.findByTestId(`${name}-icon`); function createComponent(props = {}, shallow = false) { @@ -128,6 +129,10 @@ describe('StageTable', () => { expect(findStageTime().text()).toBe(createdAt); }); + it('will render the end event', () => { + expect(findStageLastEvent().text()).toBe(firstIssueEvent.endEventTimestamp); + }); + it('will render the author', () => { expect(wrapper.findByTestId('vsa-stage-event-author').text()).toContain( firstIssueEvent.author.name, @@ -303,10 +308,20 @@ describe('StageTable', () => { wrapper.destroy(); }); - it('can sort the table by each column', () => { - findTableHeadColumns().wrappers.forEach((w) => { - expect(w.attributes('aria-sort')).toBe('none'); - }); + it('can sort the end event or duration', () => { + findTableHeadColumns() + .wrappers.slice(1) + .forEach((w) => { + expect(w.attributes('aria-sort')).toBe('none'); + }); + }); + + it('cannot be sorted by title', () => { + findTableHeadColumns() + .wrappers.slice(0, 1) + .forEach((w) => { + expect(w.attributes('aria-sort')).toBeUndefined(); + }); }); it('clicking a table column will send tracking information', () => { diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 1964fecbadd..e09016b2b2b 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -3281,4 +3281,20 @@ RSpec.describe Gitlab::Database::MigrationHelpers do model.rename_constraint(:test_table, :fk_old_name, :fk_new_name) end end + + describe '#drop_sequence' do + it "executes the statement to drop the sequence" do + expect(model).to receive(:execute).with /ALTER TABLE "test_table" ALTER COLUMN "test_column" DROP DEFAULT;\nDROP SEQUENCE IF EXISTS "test_table_id_seq"/ + + model.drop_sequence(:test_table, :test_column, :test_table_id_seq) + end + end + + describe '#add_sequence' do + it "executes the statement to add the sequence" do + expect(model).to receive(:execute).with "CREATE SEQUENCE \"test_table_id_seq\" START 1;\nALTER TABLE \"test_table\" ALTER COLUMN \"test_column\" SET DEFAULT nextval(\'test_table_id_seq\')\n" + + model.add_sequence(:test_table, :test_column, :test_table_id_seq, 1) + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 2aac4b5a3c8..9beeb9e8737 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3538,7 +3538,7 @@ RSpec.describe Ci::Build do ] end - context 'when gitlab-deploy-token exists' do + context 'when gitlab-deploy-token exists for project' do before do project.deploy_tokens << deploy_token end @@ -3548,11 +3548,32 @@ RSpec.describe Ci::Build do end end - context 'when gitlab-deploy-token does not exist' do + context 'when gitlab-deploy-token does not exist for project' do it 'does not include deploy token variables' do expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil end + + context 'when gitlab-deploy-token exists for group' do + before do + group.deploy_tokens << deploy_token + end + + it 'includes deploy token variables' do + is_expected.to include(*deploy_token_variables) + end + + context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do + before do + stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false) + end + + it 'does not include deploy token variables' do + expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil + expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil + end + end + end end end diff --git a/spec/models/container_registry/event_spec.rb b/spec/models/container_registry/event_spec.rb index 6b544c95cc8..13028c0e1da 100644 --- a/spec/models/container_registry/event_spec.rb +++ b/spec/models/container_registry/event_spec.rb @@ -46,6 +46,12 @@ RSpec.describe ContainerRegistry::Event do handle! end + it 'clears the cache for the namespace container repositories size' do + expect(Rails.cache).to receive(:delete).with(group.container_repositories_size_cache_key) + + handle! + end + shared_examples 'event without project statistics update' do it 'does not queue a project statistics update' do expect(ProjectCacheWorker).not_to receive(:perform_async) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index e7c830ce5e7..ab92606e6fc 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -3396,4 +3396,42 @@ RSpec.describe Group do end end end + + describe '#gitlab_deploy_token' do + subject(:gitlab_deploy_token) { group.gitlab_deploy_token } + + context 'when there is a gitlab deploy token associated' do + let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, groups: [group]) } + + it { is_expected.to eq(deploy_token) } + end + + context 'when there is no a gitlab deploy token associated' do + it { is_expected.to be_nil } + end + + context 'when there is a gitlab deploy token associated but is has been revoked' do + let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, :revoked, groups: [group]) } + + it { is_expected.to be_nil } + end + + context 'when there is a gitlab deploy token associated but it is expired' do + let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, :expired, groups: [group]) } + + it { is_expected.to be_nil } + end + + context 'when there is a deploy token associated with a different name' do + let!(:deploy_token) { create(:deploy_token, :group, groups: [group]) } + + it { is_expected.to be_nil } + end + + context 'when there is a gitlab deploy token associated to a different group' do + let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, groups: [create(:group)]) } + + it { is_expected.to be_nil } + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 6c1ceb335f7..eab82de0b01 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -583,7 +583,13 @@ RSpec.describe Namespace do end end - describe '#container_repositories_size' do + describe '#container_repositories_size_cache_key' do + it 'returns the correct cache key' do + expect(namespace.container_repositories_size_cache_key).to eq "namespaces:#{namespace.id}:container_repositories_size" + end + end + + describe '#container_repositories_size', :clean_gitlab_redis_cache do let(:project_namespace) { create(:namespace) } subject { project_namespace.container_repositories_size } @@ -611,12 +617,29 @@ RSpec.describe Namespace do end it { is_expected.to eq(expected_result) } + + it 'caches the result when all migrated' do + if all_migrated + expect(Rails.cache) + .to receive(:fetch) + .with(project_namespace.container_repositories_size_cache_key, expires_in: 7.days) + + subject + end + end end end context 'not on gitlab.com' do it { is_expected.to eq(nil) } end + + context 'for a sub-group' do + let(:parent_namespace) { create(:group) } + let(:project_namespace) { create(:group, parent: parent_namespace) } + + it { is_expected.to eq(nil) } + end end describe '#all_container_repositories' do diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb index 92b3e41cd18..447b7b2e0a2 100644 --- a/spec/models/pool_repository_spec.rb +++ b/spec/models/pool_repository_spec.rb @@ -24,23 +24,35 @@ RSpec.describe PoolRepository do end end - describe '#mark_obsolete_if_last' do + describe '#unlink_repository' do let(:pool) { create(:pool_repository, :ready) } + let(:repository_path) { File.join(TestEnv.repos_path, pool.source_project.repository.relative_path) } + let(:alternates_file) { File.join(repository_path, 'objects', 'info', 'alternates') } + + before do + pool.link_repository(pool.source_project.repository) + end context 'when the last member leaves' do it 'schedules pool removal' do expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original - pool.mark_obsolete_if_last(pool.source_project.repository) + pool.unlink_repository(pool.source_project.repository) + + expect(File).not_to exist(alternates_file) end end context 'when the second member leaves' do it 'does not schedule pool removal' do - create(:project, :repository, pool_repository: pool) + other_project = create(:project, :repository, pool_repository: pool) + pool.link_repository(other_project.repository) + expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id) - pool.mark_obsolete_if_last(pool.source_project.repository) + pool.unlink_repository(pool.source_project.repository) + + expect(File).not_to exist(alternates_file) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 44d4b99bc36..21978321a0f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6220,7 +6220,7 @@ RSpec.describe Project, factory_default: :keep do describe '#gitlab_deploy_token' do let(:project) { create(:project) } - subject { project.gitlab_deploy_token } + subject(:gitlab_deploy_token) { project.gitlab_deploy_token } context 'when there is a gitlab deploy token associated' do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) } @@ -6252,10 +6252,43 @@ RSpec.describe Project, factory_default: :keep do context 'when there is a deploy token associated to a different project' do let(:project_2) { create(:project) } - let!(:deploy_token) { create(:deploy_token, projects: [project_2]) } + let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project_2]) } it { is_expected.to be_nil } end + + context 'when the project group has a gitlab deploy token associated' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :group, groups: [group]) } + + it { is_expected.to eq(deploy_token) } + + context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do + before do + stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false) + end + + it { is_expected.to be_nil } + end + end + + context 'when the project and its group has a gitlab deploy token associated' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let!(:project_deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) } + let!(:group_deploy_token) { create(:deploy_token, :gitlab_deploy_token, :group, groups: [group]) } + + it { is_expected.to eq(project_deploy_token) } + + context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do + before do + stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false) + end + + it { is_expected.to eq(project_deploy_token) } + end + end end context 'with uploads' do diff --git a/spec/requests/projects/environments_controller_spec.rb b/spec/requests/projects/environments_controller_spec.rb index 1fd29f474ef..5cdf507abef 100644 --- a/spec/requests/projects/environments_controller_spec.rb +++ b/spec/requests/projects/environments_controller_spec.rb @@ -14,20 +14,7 @@ RSpec.describe Projects::EnvironmentsController do sign_in(project.owner) end - it 'avoids N+1 queries', :use_sql_query_cache do - create_deployment_with_associations(commit_depth: 19) - - control = ActiveRecord::QueryRecorder.new(skip_cached: false) do - get project_environment_path(project, environment), params: environment_params - end - - 18.downto(0).each { |n| create_deployment_with_associations(commit_depth: n) } - - # N+1s exist for loading commit emails and users - expect do - get project_environment_path(project, environment), params: environment_params - end.not_to exceed_all_query_limit(control).with_threshold(9) - end + include_examples 'avoids N+1 queries on environment detail page' end def environment_params(opts = {}) diff --git a/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb index a79b94209f3..c6e880635aa 100644 --- a/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/environments_controller_shared_examples.rb @@ -65,20 +65,3 @@ RSpec.shared_examples 'failed response for #cancel_auto_stop' do end end end - -RSpec.shared_examples 'avoids N+1 queries on environment detail page' do - render_views - - before do - create_deployment_with_associations(sequence: 0) - end - - it 'avoids N+1 queries' do - control = ActiveRecord::QueryRecorder.new { get :show, params: environment_params } - - create_deployment_with_associations(sequence: 1) - create_deployment_with_associations(sequence: 2) - - expect { get :show, params: environment_params }.not_to exceed_query_limit(control.count).with_threshold(34) - end -end diff --git a/spec/support/shared_examples/requests/projects/environments_controller_spec_shared_examples.rb b/spec/support/shared_examples/requests/projects/environments_controller_spec_shared_examples.rb new file mode 100644 index 00000000000..31218b104bd --- /dev/null +++ b/spec/support/shared_examples/requests/projects/environments_controller_spec_shared_examples.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'avoids N+1 queries on environment detail page' do + it 'avoids N+1 queries', :use_sql_query_cache do + create_deployment_with_associations(commit_depth: 19) + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get project_environment_path(project, environment), params: environment_params + end + + 18.downto(0).each { |n| create_deployment_with_associations(commit_depth: n) } + + # N+1s exist for loading commit emails and users + expect do + get project_environment_path(project, environment), params: environment_params + end.not_to exceed_all_query_limit(control).with_threshold(9) + end +end |