summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-06 13:14:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-06 13:14:47 +0000
commit7ab0cadbbdf42fdd316941b3260e294577d649f4 (patch)
tree26ed9d750eb7706174afddb43a9e6fab210f2176
parent3aad3a0b6ffb1a0fe36db41f81e8bbd3728e5f80 (diff)
downloadgitlab-ce-7ab0cadbbdf42fdd316941b3260e294577d649f4.tar.gz
Add latest changes from gitlab-org/gitlab@14-0-stable-ee
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue1
-rw-r--r--app/assets/javascripts/frequent_items/utils.js10
-rw-r--r--app/assets/javascripts/nav/components/top_nav_container_view.vue12
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue5
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue36
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js3
-rw-r--r--app/models/deploy_key.rb1
-rw-r--r--app/models/namespace.rb9
-rw-r--r--app/policies/project_policy.rb18
-rw-r--r--app/views/shared/boards/components/sidebar/_time_tracker.html.haml1
-rw-r--r--config/feature_flags/development/recursive_approach_for_all_projects.yml8
-rw-r--r--config/initializers/1_settings.rb9
-rw-r--r--doc/api/deployments.md7
-rw-r--r--lib/gitlab/local_and_remote_storage_migration/base_migrater.rb6
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js2
-rw-r--r--spec/frontend/frequent_items/utils_spec.js47
-rw-r--r--spec/frontend/nav/components/responsive_app_spec.js2
-rw-r--r--spec/frontend/nav/components/top_nav_container_view_spec.js5
-rw-r--r--spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js1
-rw-r--r--spec/lib/gitlab/local_and_remote_storage_migration/artifact_migrater_spec.rb14
-rw-r--r--spec/lib/gitlab/local_and_remote_storage_migration/pages_deployment_migrater_spec.rb14
-rw-r--r--spec/models/deploy_key_spec.rb42
-rw-r--r--spec/models/namespace_spec.rb8
-rw-r--r--spec/policies/project_policy_spec.rb31
-rw-r--r--spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb53
25 files changed, 286 insertions, 59 deletions
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
index 5d61f7b2887..a35b3f14be4 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
@@ -29,6 +29,7 @@ export default {
<template>
<issuable-time-tracker
+ :issuable-id="activeBoardItem.id.toString()"
:issuable-iid="activeBoardItem.iid.toString()"
:limit-to-hours="timeTrackingLimitToHours"
:initial-time-tracking="initialTimeTracking"
diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js
index 88519d934cb..27ef47df8c8 100644
--- a/app/assets/javascripts/frequent_items/utils.js
+++ b/app/assets/javascripts/frequent_items/utils.js
@@ -35,13 +35,15 @@ export const getTopFrequentItems = (items) => {
};
export const updateExistingFrequentItem = (frequentItem, item) => {
- const accessedOverHourAgo =
- Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / HOUR_IN_MS > 1;
+ // `frequentItem` comes from localStorage and it's possible it doesn't have a `lastAccessedOn`
+ const neverAccessed = !frequentItem.lastAccessedOn;
+ const shouldUpdate =
+ neverAccessed || Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / HOUR_IN_MS > 1;
return {
...item,
- frequency: accessedOverHourAgo ? frequentItem.frequency + 1 : frequentItem.frequency,
- lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn,
+ frequency: shouldUpdate ? frequentItem.frequency + 1 : frequentItem.frequency,
+ lastAccessedOn: shouldUpdate ? Date.now() : frequentItem.lastAccessedOn,
};
};
diff --git a/app/assets/javascripts/nav/components/top_nav_container_view.vue b/app/assets/javascripts/nav/components/top_nav_container_view.vue
index 6f98f85ff90..36e4a278da9 100644
--- a/app/assets/javascripts/nav/components/top_nav_container_view.vue
+++ b/app/assets/javascripts/nav/components/top_nav_container_view.vue
@@ -20,6 +20,10 @@ export default {
type: String,
required: true,
},
+ currentItem: {
+ type: Object,
+ required: true,
+ },
containerClass: {
type: String,
required: false,
@@ -43,6 +47,12 @@ export default {
{ id: 'secondary', menuItems: this.linksSecondary },
].filter((x) => x.menuItems?.length);
},
+ currentItemTimestamped() {
+ return {
+ ...this.currentItem,
+ lastAccessedOn: Date.now(),
+ };
+ },
},
mounted() {
// For historic reasons, the frequent-items-app component requires this too start up.
@@ -62,7 +72,7 @@ export default {
>
<div class="frequent-items-dropdown-content gl-w-full! gl-pt-0!">
<vuex-module-provider :vuex-module="frequentItemsVuexModule">
- <frequent-items-app v-bind="$attrs" />
+ <frequent-items-app :current-item="currentItemTimestamped" v-bind="$attrs" />
</vuex-module-provider>
</div>
</div>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
index 58167b3934a..d472b67d976 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue
@@ -17,6 +17,10 @@ export default {
required: false,
default: '',
},
+ issuableId: {
+ type: String,
+ required: true,
+ },
issuableIid: {
type: String,
required: true,
@@ -60,6 +64,7 @@ export default {
<div class="block">
<issuable-time-tracker
:full-path="fullPath"
+ :issuable-id="issuableId"
:issuable-iid="issuableIid"
:limit-to-hours="limitToHours"
/>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 3feff8639a1..87ddbbf256a 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -31,7 +31,11 @@ export default {
directives: {
GlModal: GlModalDirective,
},
- inject: ['issuableType'],
+ inject: {
+ issuableType: {
+ default: null,
+ },
+ },
props: {
limitToHours: {
type: Boolean,
@@ -43,6 +47,11 @@ export default {
required: false,
default: '',
},
+ issuableId: {
+ type: String,
+ required: false,
+ default: '',
+ },
issuableIid: {
type: String,
required: false,
@@ -83,15 +92,18 @@ export default {
return timeTrackingQueries[this.issuableType].query;
},
skip() {
- // We don't fetch info via GraphQL in following cases
- // 1. Time tracking info was provided via prop
- // 2. issuableIid and fullPath are not provided.
- if (!this.initialTimeTracking) {
- return false;
- } else if (this.issuableIid && this.fullPath) {
- return false;
+ // Skip the query if either of the conditions are true
+ // 1. issuableType is not provided
+ // 2. Time tracking info was provided via prop
+ // 3. issuableIid and fullPath are not provided
+ if (!this.issuableType || !timeTrackingQueries[this.issuableType]) {
+ return true;
+ } else if (this.initialTimeTracking) {
+ return true;
+ } else if (!this.issuableIid || !this.fullPath) {
+ return true;
}
- return true;
+ return false;
},
variables() {
return {
@@ -108,7 +120,7 @@ export default {
},
computed: {
isTimeTrackingInfoLoading() {
- return this.$apollo?.queries.issuableTimeTracking.loading ?? false;
+ return this.$apollo?.queries.issuableTimeTracking?.loading ?? false;
},
timeEstimate() {
return this.timeTracking?.timeEstimate || 0;
@@ -146,7 +158,7 @@ export default {
isTimeReportSupported() {
return (
[IssuableType.Issue, IssuableType.MergeRequest].includes(this.issuableType) &&
- this.issuableIid
+ this.issuableId
);
},
},
@@ -240,7 +252,7 @@ export default {
:title="__('Time tracking report')"
:hide-footer="true"
>
- <time-tracking-report :limit-to-hours="limitToHours" :issuable-iid="issuableIid" />
+ <time-tracking-report :limit-to-hours="limitToHours" :issuable-id="issuableId" />
</gl-modal>
</template>
<transition name="help-state-toggle">
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index f53760eab93..67c72b17f1f 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -391,7 +391,7 @@ function mountSubscriptionsComponent() {
function mountTimeTrackingComponent() {
const el = document.getElementById('issuable-time-tracker');
- const { iid, fullPath, issuableType, timeTrackingLimitToHours } = getSidebarOptions();
+ const { id, iid, fullPath, issuableType, timeTrackingLimitToHours } = getSidebarOptions();
if (!el) return;
@@ -404,6 +404,7 @@ function mountTimeTrackingComponent() {
createElement(SidebarTimeTracking, {
props: {
fullPath,
+ issuableId: id.toString(),
issuableIid: iid.toString(),
limitToHours: timeTrackingLimitToHours,
},
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 25e3b9fe4f0..8f5a713af3f 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -3,6 +3,7 @@
class DeployKey < Key
include FromUnion
include IgnorableColumns
+ include PolicyActor
has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :deploy_keys_projects
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 90e06e44165..5f41441058b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -271,9 +271,12 @@ class Namespace < ApplicationRecord
# Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace
def all_projects
- namespace = user? ? self : self_and_descendant_ids
-
- Project.where(namespace: namespace)
+ if Feature.enabled?(:recursive_approach_for_all_projects, default_enabled: :yaml)
+ namespace = user? ? self : self_and_descendants
+ Project.where(namespace: namespace)
+ else
+ Project.inside_path(full_path)
+ end
end
def has_parent?
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index e93c60c3710..3cb4644a60d 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -69,6 +69,16 @@ class ProjectPolicy < BasePolicy
project.merge_requests_allowing_push_to_user(user).any?
end
+ desc "Deploy key with read access"
+ condition(:download_code_deploy_key) do
+ user.is_a?(DeployKey) && user.has_access_to?(project)
+ end
+
+ desc "Deploy key with write access"
+ condition(:push_code_deploy_key) do
+ user.is_a?(DeployKey) && user.can_push_to?(project)
+ end
+
desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry
@@ -616,6 +626,14 @@ class ProjectPolicy < BasePolicy
prevent :move_design
end
+ rule { download_code_deploy_key }.policy do
+ enable :download_code
+ end
+
+ rule { push_code_deploy_key }.policy do
+ enable :push_code
+ end
+
rule { read_package_registry_deploy_token }.policy do
enable :read_package
enable :read_project
diff --git a/app/views/shared/boards/components/sidebar/_time_tracker.html.haml b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml
index 9f230a4a09b..eea3ec35000 100644
--- a/app/views/shared/boards/components/sidebar/_time_tracker.html.haml
+++ b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml
@@ -1,5 +1,6 @@
.block.time-tracking
%time-tracker{ ":limit-to-hours" => "timeTrackingLimitToHours",
+ ":issuable-id" => "issue.id ? issue.id.toString() : ''",
":issuable-iid" => "issue.iid ? issue.iid.toString() : ''",
":full-path" => "issue.project ? issue.project.fullPath : ''",
"root-path" => "#{root_url}" }
diff --git a/config/feature_flags/development/recursive_approach_for_all_projects.yml b/config/feature_flags/development/recursive_approach_for_all_projects.yml
new file mode 100644
index 00000000000..e2d656b7de2
--- /dev/null
+++ b/config/feature_flags/development/recursive_approach_for_all_projects.yml
@@ -0,0 +1,8 @@
+---
+name: recursive_approach_for_all_projects
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64632
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334817
+milestone: '14.1'
+type: development
+group: group::fulfillment
+default_enabled: true
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8f4c6492cad..e311a364977 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -577,12 +577,9 @@ Settings.cron_jobs['users_deactivate_dormant_users_worker']['job_class'] = 'User
Settings.cron_jobs['ci_delete_unit_tests_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_delete_unit_tests_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['ci_delete_unit_tests_worker']['job_class'] = 'Ci::DeleteUnitTestsWorker'
-
-Gitlab.com do
- Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({})
- Settings.cron_jobs['batched_background_migrations_worker']['cron'] ||= '* * * * *'
- Settings.cron_jobs['batched_background_migrations_worker']['job_class'] = 'Database::BatchedBackgroundMigrationWorker'
-end
+Settings.cron_jobs['batched_background_migrations_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['batched_background_migrations_worker']['cron'] ||= '* * * * *'
+Settings.cron_jobs['batched_background_migrations_worker']['job_class'] = 'Database::BatchedBackgroundMigrationWorker'
Gitlab.ee do
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 586f3edf51e..a2e56fc8557 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -9,9 +9,6 @@ type: concepts, howto
## List project deployments
-> The `updated_after` and `updated_before` attributes were removed and replaced
- by `finished_after` and `finished_before` respectively in GitLab 14.0.
-
Get a list of deployments in a project.
```plaintext
@@ -23,8 +20,8 @@ GET /projects/:id/deployments
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `order_by` | string | no | Return deployments ordered by either one of `id`, `iid`, `created_at`, `updated_at` or `ref` fields. Default is `id`. |
| `sort` | string | no | Return deployments sorted in `asc` or `desc` order. Default is `asc`. |
-| `finished_after` | datetime | no | Return deployments updated after the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
-| `finished_before` | datetime | no | Return deployments updated before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `updated_after` | datetime | no | Return deployments updated after the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `updated_before` | datetime | no | Return deployments updated before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
| `environment` | string | no | The [name of the environment](../ci/environments/index.md) to filter deployments by. |
| `status` | string | no | The status to filter deployments by. One of `created`, `running`, `success`, `failed`, `canceled`.
diff --git a/lib/gitlab/local_and_remote_storage_migration/base_migrater.rb b/lib/gitlab/local_and_remote_storage_migration/base_migrater.rb
index f859d293e76..0484957a6fe 100644
--- a/lib/gitlab/local_and_remote_storage_migration/base_migrater.rb
+++ b/lib/gitlab/local_and_remote_storage_migration/base_migrater.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def migrate_to_remote_storage
- logger.info('Starting transfer to remote storage')
+ logger.info('Starting transfer to object storage')
migrate(items_with_files_stored_locally, ObjectStorage::Store::REMOTE)
end
@@ -38,11 +38,11 @@ module Gitlab
end
def log_success(item, store)
- logger.info("Transferred #{item.class.name} ID #{item.id} of type #{item.file_type} with size #{item.size} to #{storage_label(store)} storage")
+ logger.info("Transferred #{item.class.name} ID #{item.id} with size #{item.size} to #{storage_label(store)} storage")
end
def log_error(err, item)
- logger.warn("Failed to transfer #{item.class.name} of type #{item.file_type} and ID #{item.id} with error: #{err.message}")
+ logger.warn("Failed to transfer #{item.class.name} ID #{item.id} with error: #{err.message}")
end
def storage_label(store)
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
index 74441e147cf..7c8996be0b8 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
@@ -26,6 +26,7 @@ describe('BoardSidebarTimeTracker', () => {
store = createStore();
store.state.boardItems = {
1: {
+ id: 1,
iid: 1,
timeEstimate: 3600,
totalTimeSpent: 1800,
@@ -49,6 +50,7 @@ describe('BoardSidebarTimeTracker', () => {
expect(wrapper.find(IssuableTimeTracker).props()).toEqual({
limitToHours: timeTrackingLimitToHours,
showCollapsed: false,
+ issuableId: '1',
issuableIid: '1',
fullPath: '',
initialTimeTracking: {
diff --git a/spec/frontend/frequent_items/utils_spec.js b/spec/frontend/frequent_items/utils_spec.js
index a7ab18b0d10..8c3841558f4 100644
--- a/spec/frontend/frequent_items/utils_spec.js
+++ b/spec/frontend/frequent_items/utils_spec.js
@@ -66,35 +66,36 @@ describe('Frequent Items utils spec', () => {
});
describe('updateExistingFrequentItem', () => {
- let mockedProject;
-
- beforeEach(() => {
- mockedProject = {
- ...mockProject,
- frequency: 1,
- lastAccessedOn: 1497979281815,
- };
+ const LAST_ACCESSED = 1497979281815;
+ const WITHIN_AN_HOUR = LAST_ACCESSED + HOUR_IN_MS;
+ const OVER_AN_HOUR = WITHIN_AN_HOUR + 1;
+ const EXISTING_ITEM = Object.freeze({
+ ...mockProject,
+ frequency: 1,
+ lastAccessedOn: 1497979281815,
});
- it('updates item if accessed over an hour ago', () => {
- const newTimestamp = Date.now() + HOUR_IN_MS + 1;
+ it.each`
+ desc | existingProps | newProps | expected
+ ${'updates item if accessed over an hour ago'} | ${{}} | ${{ lastAccessedOn: OVER_AN_HOUR }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }}
+ ${'does not update is accessed with an hour'} | ${{}} | ${{ lastAccessedOn: WITHIN_AN_HOUR }} | ${{ lastAccessedOn: EXISTING_ITEM.lastAccessedOn, frequency: 1 }}
+ ${'updates if lastAccessedOn not found'} | ${{ lastAccessedOn: undefined }} | ${{ lastAccessedOn: WITHIN_AN_HOUR }} | ${{ lastAccessedOn: Date.now(), frequency: 2 }}
+ `('$desc', ({ existingProps, newProps, expected }) => {
const newItem = {
- ...mockedProject,
- lastAccessedOn: newTimestamp,
+ ...EXISTING_ITEM,
+ ...newProps,
};
- const result = updateExistingFrequentItem(mockedProject, newItem);
-
- expect(result.frequency).toBe(mockedProject.frequency + 1);
- });
-
- it('does not update item if accessed within the hour', () => {
- const newItem = {
- ...mockedProject,
- lastAccessedOn: mockedProject.lastAccessedOn + HOUR_IN_MS,
+ const existingItem = {
+ ...EXISTING_ITEM,
+ ...existingProps,
};
- const result = updateExistingFrequentItem(mockedProject, newItem);
- expect(result.frequency).toBe(mockedProject.frequency);
+ const result = updateExistingFrequentItem(existingItem, newItem);
+
+ expect(result).toEqual({
+ ...newItem,
+ ...expected,
+ });
});
});
diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js
index 7221ea2c5cd..e1b443745e3 100644
--- a/spec/frontend/nav/components/responsive_app_spec.js
+++ b/spec/frontend/nav/components/responsive_app_spec.js
@@ -111,6 +111,7 @@ describe('~/nav/components/responsive_app.vue', () => {
containerClass: 'gl-px-3',
frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.namespace,
frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.vuexModule,
+ currentItem: {},
linksPrimary: TEST_NAV_DATA.views.projects.linksPrimary,
linksSecondary: TEST_NAV_DATA.views.projects.linksSecondary,
};
@@ -118,6 +119,7 @@ describe('~/nav/components/responsive_app.vue', () => {
containerClass: 'gl-px-3',
frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_GROUPS.namespace,
frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_GROUPS.vuexModule,
+ currentItem: {},
linksPrimary: TEST_NAV_DATA.views.groups.linksPrimary,
linksSecondary: TEST_NAV_DATA.views.groups.linksSecondary,
};
diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js
index 06d2179b859..0218f09af0a 100644
--- a/spec/frontend/nav/components/top_nav_container_view_spec.js
+++ b/spec/frontend/nav/components/top_nav_container_view_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { merge } from 'lodash';
import { nextTick } from 'vue';
import FrequentItemsApp from '~/frequent_items/components/app.vue';
import { FREQUENT_ITEMS_PROJECTS } from '~/frequent_items/constants';
@@ -82,7 +83,9 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
it('renders frequent items app', () => {
expect(findFrequentItemsApp()).toEqual({
vuexModule: DEFAULT_PROPS.frequentItemsVuexModule,
- props: expect.objectContaining(TEST_OTHER_PROPS),
+ props: expect.objectContaining(
+ merge({ currentItem: { lastAccessedOn: Date.now() } }, TEST_OTHER_PROPS),
+ ),
attributes: expect.objectContaining(EXTRA_ATTRS),
});
});
diff --git a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
index e08bd80b18e..eb202a8cfcc 100644
--- a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
@@ -19,6 +19,7 @@ describe('Issuable Time Tracker', () => {
const defaultProps = {
limitToHours: false,
fullPath: 'gitlab-org/gitlab-test',
+ issuableId: '1',
issuableIid: '1',
initialTimeTracking: {
...issuableTimeTrackingResponse.data.workspace.issuable,
diff --git a/spec/lib/gitlab/local_and_remote_storage_migration/artifact_migrater_spec.rb b/spec/lib/gitlab/local_and_remote_storage_migration/artifact_migrater_spec.rb
new file mode 100644
index 00000000000..b3f2003c207
--- /dev/null
+++ b/spec/lib/gitlab/local_and_remote_storage_migration/artifact_migrater_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples'
+
+RSpec.describe Gitlab::LocalAndRemoteStorageMigration::ArtifactMigrater do
+ before do
+ stub_artifacts_object_storage(enabled: true)
+ end
+
+ let!(:item) { create(:ci_job_artifact, :archive, file_store: start_store) }
+
+ it_behaves_like 'local and remote storage migration'
+end
diff --git a/spec/lib/gitlab/local_and_remote_storage_migration/pages_deployment_migrater_spec.rb b/spec/lib/gitlab/local_and_remote_storage_migration/pages_deployment_migrater_spec.rb
new file mode 100644
index 00000000000..2cc48b445f1
--- /dev/null
+++ b/spec/lib/gitlab/local_and_remote_storage_migration/pages_deployment_migrater_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples'
+
+RSpec.describe Gitlab::LocalAndRemoteStorageMigration::PagesDeploymentMigrater do
+ before do
+ stub_pages_object_storage(::Pages::DeploymentUploader, enabled: true)
+ end
+
+ let!(:item) { create(:pages_deployment, file_store: start_store) }
+
+ it_behaves_like 'local and remote storage migration'
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index d4ccaa6a10e..fa78527e366 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -93,4 +93,46 @@ RSpec.describe DeployKey, :mailer do
end
end
end
+
+ describe 'PolicyActor methods' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:deploy_key) { create(:deploy_key, user: user) }
+ let_it_be(:project) { create(:project, creator: user, namespace: user.namespace) }
+
+ let(:methods) { PolicyActor.instance_methods }
+
+ subject { deploy_key }
+
+ it 'responds to all PolicyActor methods' do
+ methods.each do |method|
+ expect(subject.respond_to?(method)).to be true
+ end
+ end
+
+ describe '#can?' do
+ it { expect(user.can?(:read_project, project)).to be true }
+
+ context 'when a read deploy key is enabled in the project' do
+ let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
+
+ it { expect(subject.can?(:read_project, project)).to be false }
+ it { expect(subject.can?(:download_code, project)).to be true }
+ it { expect(subject.can?(:push_code, project)).to be false }
+ end
+
+ context 'when a write deploy key is enabled in the project' do
+ let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
+
+ it { expect(subject.can?(:read_project, project)).to be false }
+ it { expect(subject.can?(:download_code, project)).to be true }
+ it { expect(subject.can?(:push_code, project)).to be true }
+ end
+
+ context 'when the deploy key is not enabled in the project' do
+ it { expect(subject.can?(:read_project, project)).to be false }
+ it { expect(subject.can?(:download_code, project)).to be false }
+ it { expect(subject.can?(:push_code, project)).to be false }
+ end
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 2c514593de8..373f3a89e14 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1044,6 +1044,14 @@ RSpec.describe Namespace do
end
describe '#all_projects' do
+ context 'when recursive approach is disabled' do
+ before do
+ stub_feature_flags(recursive_approach_for_all_projects: false)
+ end
+
+ include_examples '#all_projects'
+ end
+
context 'with use_traversal_ids feature flag enabled' do
before do
stub_feature_flags(use_traversal_ids: true)
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index d0fe0cca8a1..f3c92751d06 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -795,6 +795,37 @@ RSpec.describe ProjectPolicy do
end
end
+ context 'deploy key access' do
+ context 'private project' do
+ let(:project) { private_project }
+ let!(:deploy_key) { create(:deploy_key, user: owner) }
+
+ subject { described_class.new(deploy_key, project) }
+
+ context 'when a read deploy key is enabled in the project' do
+ let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
+
+ it { is_expected.to be_allowed(:download_code) }
+ it { is_expected.to be_disallowed(:push_code) }
+ it { is_expected.to be_disallowed(:read_project) }
+ end
+
+ context 'when a write deploy key is enabled in the project' do
+ let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
+
+ it { is_expected.to be_allowed(:download_code) }
+ it { is_expected.to be_allowed(:push_code) }
+ it { is_expected.to be_disallowed(:read_project) }
+ end
+
+ context 'when the deploy key is not enabled in the project' do
+ it { is_expected.to be_disallowed(:download_code) }
+ it { is_expected.to be_disallowed(:push_code) }
+ it { is_expected.to be_disallowed(:read_project) }
+ end
+ end
+ end
+
context 'deploy token access' do
let!(:project_deploy_token) do
create(:project_deploy_token, project: project, deploy_token: deploy_token)
diff --git a/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb
new file mode 100644
index 00000000000..27ca27a9035
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/local_and_remote_storage_migration_shared_examples.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'local and remote storage migration' do
+ let(:logger) { Logger.new("/dev/null") }
+ let(:migrater) { described_class.new(logger) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:start_store, :end_store, :method) do
+ ObjectStorage::Store::LOCAL | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage
+ ObjectStorage::Store::REMOTE | ObjectStorage::Store::REMOTE | :migrate_to_remote_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ ObjectStorage::Store::REMOTE | ObjectStorage::Store::LOCAL | :migrate_to_local_storage
+ ObjectStorage::Store::LOCAL | ObjectStorage::Store::LOCAL | :migrate_to_local_storage # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ end
+
+ with_them do
+ let(:storage_name) { end_store == ObjectStorage::Store::REMOTE ? 'object' : 'local' }
+
+ it 'successfully migrates' do
+ expect(logger).to receive(:info).with("Starting transfer to #{storage_name} storage")
+
+ if start_store != end_store
+ expect(logger).to receive(:info).with("Transferred #{item.class.name} ID #{item.id} with size #{item.size} to #{storage_name} storage")
+ end
+
+ expect(item.file_store).to eq(start_store)
+
+ migrater.send(method)
+
+ expect(item.reload.file_store).to eq(end_store)
+ end
+ end
+
+ context 'when migration fails' do
+ let(:start_store) { ObjectStorage::Store::LOCAL }
+
+ it 'prints error' do
+ expect_next_instance_of(item.file.class) do |file|
+ expect(file).to receive(:migrate!).and_raise("error message")
+ end
+
+ expect(logger).to receive(:info).with("Starting transfer to object storage")
+
+ expect(logger).to receive(:warn).with("Failed to transfer #{item.class.name} ID #{item.id} with error: error message")
+
+ expect(item.file_store).to eq(start_store)
+
+ migrater.migrate_to_remote_storage
+
+ expect(item.reload.file_store).to eq(start_store)
+ end
+ end
+end