summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/notes/constants.js45
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/constants.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js39
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue6
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail_modal.vue66
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue10
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue27
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue2
-rw-r--r--app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql9
-rw-r--r--app/controllers/projects/grafana_api_controller.rb2
-rw-r--r--app/controllers/projects/jobs_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/metrics/dashboards/builder_controller.rb2
-rw-r--r--app/controllers/projects/performance_monitoring/dashboards_controller.rb4
-rw-r--r--app/finders/pending_todos_finder.rb28
-rw-r--r--app/helpers/ci/jobs_helper.rb18
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/packages/package.rb7
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/todo.rb2
-rw-r--r--app/models/user.rb121
-rw-r--r--app/models/user_preference.rb4
-rw-r--r--app/models/users/group_callout.rb6
-rw-r--r--app/models/users/phone_number_validation.rb33
-rw-r--r--app/models/users/project_callout.rb6
-rw-r--r--app/models/users/user_follow_user.rb10
-rw-r--r--app/models/web_ide_terminal.rb14
-rw-r--r--app/models/webauthn_registration.rb2
-rw-r--r--app/models/wiki_page.rb10
-rw-r--r--app/models/work_item.rb6
-rw-r--r--app/services/keys/last_used_service.rb24
-rw-r--r--app/services/members/approve_access_request_service.rb2
-rw-r--r--app/services/members/base_service.rb4
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/projects/open_issues_count_service.rb2
-rw-r--r--app/services/todo_service.rb32
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/ssh_keys/update_last_used_at_worker.rb21
45 files changed, 328 insertions, 298 deletions
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index e7c3385ae5c..419b427682e 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -61,17 +61,7 @@ export const MR_FILTER_OPTIONS = [
{
text: __('Approvals'),
value: 'approval',
- systemNoteIcons: ['approval', 'unapproval'],
- },
- {
- text: __('Commits & branches'),
- value: 'commit_branches',
- systemNoteIcons: ['commit', 'fork'],
- },
- {
- text: __('Merge request status'),
- value: 'status',
- systemNoteIcons: ['git-merge', 'issue-close', 'issues'],
+ systemNoteIcons: ['approval', 'unapproval', 'check'],
},
{
text: __('Assignees & reviewers'),
@@ -84,6 +74,18 @@ export const MR_FILTER_OPTIONS = [
],
},
{
+ text: __('Comments'),
+ value: 'comments',
+ noteType: ['DiscussionNote', 'DiffNote'],
+ individualNote: true,
+ noteText: [s__('IssuableEvents|resolved all threads')],
+ },
+ {
+ text: __('Commits & branches'),
+ value: 'commit_branches',
+ systemNoteIcons: ['commit', 'fork'],
+ },
+ {
text: __('Edits'),
value: 'edits',
systemNoteIcons: ['pencil', 'task-done'],
@@ -94,25 +96,24 @@ export const MR_FILTER_OPTIONS = [
systemNoteIcons: ['label'],
},
{
+ text: __('Lock status'),
+ value: 'lock_status',
+ systemNoteIcons: ['lock', 'lock-open'],
+ },
+ {
text: __('Mentions'),
value: 'mentions',
systemNoteIcons: ['comment-dots'],
},
{
+ text: __('Merge request status'),
+ value: 'status',
+ systemNoteIcons: ['git-merge', 'issue-close', 'issues', 'merge-request-close'],
+ },
+ {
text: __('Tracking'),
value: 'tracking',
noteType: ['MilestoneNote'],
systemNoteIcons: ['timer'],
},
- {
- text: __('Comments'),
- value: 'comments',
- noteType: ['DiscussionNote', 'DiffNote'],
- individualNote: true,
- },
- {
- text: __('Lock status'),
- value: 'lock_status',
- systemNoteIcons: ['lock', 'lock-open'],
- },
];
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
index dd22d29d9a7..c13d49b5379 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
@@ -225,9 +225,6 @@ export default {
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
- <template #secondStrong="{ content }">
- <strong>{{ content }}</strong>
- </template>
</gl-sprintf>
</p>
<expiration-dropdown
@@ -264,9 +261,6 @@ export default {
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
- <template #secondStrong="{ content }">
- <strong>{{ content }}</strong>
- </template>
</gl-sprintf>
</p>
<expiration-dropdown
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
index 5f59372e5ba..05616a0a4f6 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
@@ -29,7 +29,7 @@ export const TEXT_AREA_INVALID_FEEDBACK = s__(
export const KEEP_HEADER_TEXT = s__('ContainerRegistry|Keep these tags');
export const KEEP_INFO_TEXT = s__(
- 'ContainerRegistry|Tags that match these rules are %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{secondStrongStart}latest%{secondStrongEnd} tag is always kept.',
+ 'ContainerRegistry|Tags that match %{strongStart}any of%{strongEnd} these rules are %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{strongStart}latest%{strongEnd} tag is always kept.',
);
export const KEEP_N_LABEL = s__('ContainerRegistry|Keep the most recent:');
export const NAME_REGEX_KEEP_LABEL = s__('ContainerRegistry|Keep tags matching:');
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql
new file mode 100644
index 00000000000..d5092d9ae1a
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql
@@ -0,0 +1,17 @@
+#import "~/graphql_shared/fragments/user.fragment.graphql"
+
+subscription mergeRequestApprovalStateUpdated($issuableId: IssuableID!) {
+ mergeRequestApprovalStateUpdated(issuableId: $issuableId) {
+ ... on MergeRequest {
+ id
+ approvedBy {
+ nodes {
+ ...User
+ }
+ }
+ userPermissions {
+ canApprove
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
index 7e658e77d37..3228c09c9b6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js
@@ -1,5 +1,11 @@
-import { createAlert } from '~/alert';
+import mergeRequestApprovalStateUpdated from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.subscription.graphql';
import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql';
+
+import { createAlert } from '~/alert';
+
+import { convertToGraphQLId } from '../../graphql_shared/utils';
+import { TYPENAME_MERGE_REQUEST } from '../../graphql_shared/constants';
+
import { FETCH_ERROR } from '../components/approvals/messages';
export default {
@@ -25,6 +31,29 @@ export default {
message: FETCH_ERROR,
});
},
+ subscribeToMore: {
+ document: mergeRequestApprovalStateUpdated,
+ variables() {
+ return {
+ issuableId: convertToGraphQLId(TYPENAME_MERGE_REQUEST, this.mr.id),
+ };
+ },
+ skip() {
+ return !this.mr?.id || !this.isRealtimeEnabled;
+ },
+ updateQuery(
+ _,
+ {
+ subscriptionData: {
+ data: { mergeRequestApprovalStateUpdated: queryResult },
+ },
+ },
+ ) {
+ if (queryResult) {
+ this.mr.setApprovals(queryResult);
+ }
+ },
+ },
},
},
data() {
@@ -34,6 +63,14 @@ export default {
disableCommittersApproval: false,
};
},
+ computed: {
+ isRealtimeEnabled() {
+ // This mixin needs glFeatureFlagsMixin, but fatals if it's included here.
+ // Parents that include this mixin (approvals) should also include the
+ // glFeatureFlagsMixin mixin, or this will always be false.
+ return Boolean(this.glFeatures?.realtimeApprovals);
+ },
+ },
methods: {
clearError() {
this.$emit('clearError');
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 56802f76bba..e3ac49b90d5 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -438,15 +438,15 @@ export default {
this.activeToast?.hide();
}
},
- async removeChild(childId) {
+ async removeChild({ id }) {
try {
- const { data } = await this.updateWorkItem(null, childId, null);
+ const { data } = await this.updateWorkItem(null, id, null);
if (data.workItemUpdate.errors.length === 0) {
this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
action: {
text: s__('WorkItem|Undo'),
- onClick: this.undoChildRemoval.bind(this, data.workItemUpdate.workItem, childId),
+ onClick: this.undoChildRemoval.bind(this, data.workItemUpdate.workItem, id),
},
});
}
diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
index 376263503de..f8422dda211 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue
@@ -2,7 +2,6 @@
import { GlAlert, GlModal } from '@gitlab/ui';
import { s__ } from '~/locale';
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
-import deleteWorkItemFromTaskMutation from '../graphql/delete_task_from_work_item.mutation.graphql';
import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql';
export default {
@@ -27,26 +26,6 @@ export default {
required: false,
default: null,
},
- issueGid: {
- type: String,
- required: false,
- default: '',
- },
- lockVersion: {
- type: Number,
- required: false,
- default: null,
- },
- lineNumberStart: {
- type: String,
- required: false,
- default: null,
- },
- lineNumberEnd: {
- type: String,
- required: false,
- default: null,
- },
},
emits: ['workItemDeleted', 'close', 'update-modal'],
data() {
@@ -75,50 +54,6 @@ export default {
},
methods: {
deleteWorkItem() {
- if (this.lockVersion != null && this.lineNumberStart && this.lineNumberEnd) {
- this.deleteWorkItemWithTaskData();
- } else {
- this.deleteWorkItemWithoutTaskData();
- }
- },
- deleteWorkItemWithTaskData() {
- this.$apollo
- .mutate({
- mutation: deleteWorkItemFromTaskMutation,
- variables: {
- input: {
- id: this.issueGid,
- lockVersion: this.lockVersion,
- taskData: {
- id: this.workItemId,
- lineNumberStart: Number(this.lineNumberStart),
- lineNumberEnd: Number(this.lineNumberEnd),
- },
- },
- },
- })
- .then(
- ({
- data: {
- workItemDeleteTask: {
- workItem: { descriptionHtml },
- errors,
- },
- },
- }) => {
- if (errors?.length) {
- throw new Error(errors[0]);
- }
-
- this.$emit('workItemDeleted', descriptionHtml);
- this.hide();
- },
- )
- .catch((error) => {
- this.setErrorMessage(error.message);
- });
- },
- deleteWorkItemWithoutTaskData() {
this.$apollo
.mutate({
mutation: deleteWorkItemMutation,
@@ -191,7 +126,6 @@ export default {
<work-item-detail
is-modal
- :work-item-parent-id="issueGid"
:work-item-id="displayedWorkItemId"
:work-item-iid="displayedWorkItemIid"
class="gl-p-5 gl-mt-n3 gl-reset-bg gl-isolation-isolate"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
index 8152412a5c1..401c8a53eb0 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue
@@ -184,19 +184,19 @@ export default {
showScopedLabel(label) {
return isScopedLabel(label) && this.allowsScopedLabels;
},
- async removeChild(childId) {
+ async removeChild({ id }) {
this.cloneChildren();
this.isLoadingChildren = true;
try {
- const { data } = await this.updateWorkItem(childId, null);
+ const { data } = await this.updateWorkItem(id, null);
if (!data?.workItemUpdate?.errors?.length) {
- this.filterRemovedChild(childId);
+ this.filterRemovedChild(id);
this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
action: {
text: s__('WorkItem|Undo'),
- onClick: this.undoChildRemoval.bind(this, childId),
+ onClick: this.undoChildRemoval.bind(this, id),
},
});
}
@@ -341,7 +341,7 @@ export default {
:work-item-id="childItem.id"
:parent-work-item-id="issuableGid"
data-testid="links-menu"
- @removeChild="$emit('removeChild', childItem.id)"
+ @removeChild="$emit('removeChild', childItem)"
/>
</div>
</div>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index 46c109f2d57..5728e33880e 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -185,29 +185,26 @@ export default {
variables: { id: this.issuableGid, workItem },
});
},
- async updateWorkItem(workItem, childId, parentId) {
- const response = await this.$apollo.mutate({
+ async undoChildRemoval(workItem, childId) {
+ const { data } = await this.$apollo.mutate({
mutation: updateWorkItemMutation,
- variables: { input: { id: childId, hierarchyWidget: { parentId } } },
+ variables: { input: { id: childId, hierarchyWidget: { parentId: this.issuableGid } } },
});
- if (parentId === null) {
- await this.removeHierarchyChild(workItem);
- } else {
- await this.addHierarchyChild(workItem);
- }
-
- return response;
- },
- async undoChildRemoval(workItem, childId) {
- const { data } = await this.updateWorkItem(workItem, childId, this.issuableGid);
+ await this.addHierarchyChild(workItem);
if (data.workItemUpdate.errors.length === 0) {
this.activeToast?.hide();
}
},
- async removeChild(childId) {
- const { data } = await this.updateWorkItem({ id: childId }, childId, null);
+ async removeChild(workItem) {
+ const childId = workItem.id;
+ const { data } = await this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: { input: { id: childId, hierarchyWidget: { parentId: null } } },
+ });
+
+ await this.removeHierarchyChild(workItem);
if (data.workItemUpdate.errors.length === 0) {
this.activeToast = this.$toast.show(s__('WorkItem|Child removed'), {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue
index 121c987da71..2cabf489bc6 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue
@@ -36,7 +36,7 @@ export default {
:issuable-gid="workItemId"
:child-item="child"
:work-item-type="workItemType"
- @removeChild="$emit('removeChild', child.id)"
+ @removeChild="$emit('removeChild', $event)"
@click="$emit('click', Object.assign($event, { childItem: child }))"
/>
</div>
diff --git a/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql
deleted file mode 100644
index 32c07ed48c7..00000000000
--- a/app/assets/javascripts/work_items/graphql/delete_task_from_work_item.mutation.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-mutation workItemDeleteTask($input: WorkItemDeleteTaskInput!) {
- workItemDeleteTask(input: $input) {
- workItem {
- id
- descriptionHtml
- }
- errors
- }
-}
diff --git a/app/controllers/projects/grafana_api_controller.rb b/app/controllers/projects/grafana_api_controller.rb
index 9cd511f6a11..2cc6c6c35ba 100644
--- a/app/controllers/projects/grafana_api_controller.rb
+++ b/app/controllers/projects/grafana_api_controller.rb
@@ -10,6 +10,8 @@ class Projects::GrafanaApiController < Projects::ApplicationController
urgency :low
def proxy
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
result = ::Grafana::ProxyService.new(
project,
params[:datasource_id],
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 36fa1fab68f..79ddcbf732d 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -8,8 +8,8 @@ class Projects::JobsController < Projects::ApplicationController
urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :raw]
- before_action :find_job_as_build, except: [:index, :play, :show, :retry]
- before_action :find_job_as_processable, only: [:play, :show, :retry]
+ before_action :find_job_as_build, except: [:index, :play, :retry]
+ before_action :find_job_as_processable, only: [:play, :retry]
before_action :authorize_read_build_trace!, only: [:trace, :raw]
before_action :authorize_read_build!
before_action :authorize_update_build!,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index dbcbd2467b6..f72f0ccb593 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -47,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:single_file_file_by_file, project)
push_frontend_feature_flag(:mr_experience_survey, project)
push_frontend_feature_flag(:realtime_mr_status_change, project)
+ push_frontend_feature_flag(:realtime_approvals, project)
push_frontend_feature_flag(:saved_replies, current_user)
push_frontend_feature_flag(:code_quality_inline_drawer, project)
push_frontend_feature_flag(:hide_create_issue_resolve_all, project)
diff --git a/app/controllers/projects/metrics/dashboards/builder_controller.rb b/app/controllers/projects/metrics/dashboards/builder_controller.rb
index a6b57798923..02e3afcdc80 100644
--- a/app/controllers/projects/metrics/dashboards/builder_controller.rb
+++ b/app/controllers/projects/metrics/dashboards/builder_controller.rb
@@ -10,6 +10,8 @@ module Projects
urgency :low
def panel_preview
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
respond_to do |format|
format.json do
if rendered_panel.success?
diff --git a/app/controllers/projects/performance_monitoring/dashboards_controller.rb b/app/controllers/projects/performance_monitoring/dashboards_controller.rb
index d043f8d0b9f..1255ec1dde2 100644
--- a/app/controllers/projects/performance_monitoring/dashboards_controller.rb
+++ b/app/controllers/projects/performance_monitoring/dashboards_controller.rb
@@ -16,6 +16,8 @@ module Projects
urgency :low
def create
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
result = ::Metrics::Dashboard::CloneDashboardService.new(project, current_user, dashboard_params).execute
if result[:status] == :success
@@ -26,6 +28,8 @@ module Projects
end
def update
+ return not_found if Feature.enabled?(:remove_monitor_metrics)
+
result = ::Metrics::Dashboard::UpdateDashboardService.new(project, current_user, dashboard_params.merge(file_content_params)).execute
if result[:status] == :success
diff --git a/app/finders/pending_todos_finder.rb b/app/finders/pending_todos_finder.rb
index babff65cc37..a1a72840236 100644
--- a/app/finders/pending_todos_finder.rb
+++ b/app/finders/pending_todos_finder.rb
@@ -13,24 +13,36 @@
class PendingTodosFinder
attr_reader :users, :params
- # users - The list of users to retrieve the todos for.
+ # users - The list of users to retrieve the todos for. If nil is passed, it won't filter todos based on users
# params - A Hash containing columns and values to use for filtering todos.
- def initialize(users, params = {})
- @users = users
+ def initialize(params = {})
@params = params
+
+ # To prevent N+1 queries when fetching the users of the PendingTodos.
+ @preload_user_association = params.fetch(:preload_user_association, false)
end
def execute
- todos = Todo.for_user(users)
- todos = todos.pending
+ todos = Todo.pending
+ todos = by_users(todos)
todos = by_project(todos)
todos = by_target_id(todos)
todos = by_target_type(todos)
+ todos = by_author_id(todos)
todos = by_discussion(todos)
todos = by_commit_id(todos)
+
+ todos = todos.with_preloaded_user if @preload_user_association
+
by_action(todos)
end
+ def by_users(todos)
+ return todos unless params[:users].present?
+
+ todos.for_user(params[:users])
+ end
+
def by_project(todos)
if (id = params[:project_id])
todos.for_project(id)
@@ -55,6 +67,12 @@ class PendingTodosFinder
end
end
+ def by_author_id(todos)
+ return todos unless params[:author_id]
+
+ todos.for_author(params[:author_id])
+ end
+
def by_commit_id(todos)
if (id = params[:commit_id])
todos.for_commit(id)
diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb
index 424e5920fed..a7e1de173bd 100644
--- a/app/helpers/ci/jobs_helper.rb
+++ b/app/helpers/ci/jobs_helper.rb
@@ -18,24 +18,6 @@ module Ci
}
end
- def bridge_data(build, project)
- {
- "build_id" => build.id,
- "empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg'),
- "pipeline_iid" => build.pipeline.iid,
- "project_full_path" => project.full_path
- }
- end
-
- def job_counts
- {
- "all" => limited_counter_with_delimiter(@all_builds),
- "pending" => limited_counter_with_delimiter(@all_builds.pending),
- "running" => limited_counter_with_delimiter(@all_builds.running),
- "finished" => limited_counter_with_delimiter(@all_builds.finished)
- }
- end
-
def job_statuses
statuses = Ci::HasStatus::AVAILABLE_STATUSES
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index fca6c76434a..d2ca88aae0e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -14,6 +14,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
ignore_column :send_user_confirmation_email, remove_with: '15.8', remove_after: '2022-12-18'
ignore_column :web_ide_clientside_preview_enabled, remove_with: '15.11', remove_after: '2023-04-22'
ignore_column :clickhouse_connection_string, remove_with: '16.1', remove_after: '2023-05-22'
+ ignore_columns %i[instance_administration_project_id instance_administrators_group_id], remove_with: '16.2', remove_after: '2023-06-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
@@ -33,17 +34,13 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
enum whats_new_variant: { all_tiers: 0, current_tier: 1, disabled: 2 }, _prefix: true
enum email_confirmation_setting: { off: 0, soft: 1, hard: 2 }, _prefix: true
- add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
+ add_authentication_token_field :runners_registration_token, encrypted: :required
add_authentication_token_field :health_check_access_token
add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required
add_authentication_token_field :error_tracking_access_token, encrypted: :required
belongs_to :push_rule
- belongs_to :instance_group, class_name: "Group", foreign_key: :instance_administrators_group_id,
- inverse_of: :application_setting
- alias_attribute :instance_group_id, :instance_administrators_group_id
- alias_attribute :instance_administrators_group, :instance_group
alias_attribute :housekeeping_optimize_repository_period, :housekeeping_incremental_repack_period
sanitizes! :default_branch_name
diff --git a/app/models/group.rb b/app/models/group.rb
index f13ce2ddca1..ab8e0101684 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -108,8 +108,6 @@ class Group < Namespace
has_one :import_state, class_name: 'GroupImportState', inverse_of: :group
- has_many :application_setting, foreign_key: :instance_administrators_group_id, inverse_of: :instance_group
-
has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :group
has_many :bulk_import_entities, class_name: 'BulkImports::Entity', foreign_key: :namespace_id, inverse_of: :group
diff --git a/app/models/issue.rb b/app/models/issue.rb
index b0535a28640..0d33c6a71aa 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -684,13 +684,13 @@ class Issue < ApplicationRecord
elsif project.personal? && project.team.owner?(user)
true
elsif confidential? && !assignee_or_author?(user)
- project.team.member?(user, Gitlab::Access::REPORTER)
+ project.member?(user, Gitlab::Access::REPORTER)
elsif hidden?
false
elsif project.public? || (project.internal? && !user.external?)
project.feature_available?(:issues, user)
else
- project.team.member?(user)
+ project.member?(user)
end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 596186276bb..2ea71bfcd6d 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -92,7 +92,7 @@ class Key < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
def update_last_used_at
- Keys::LastUsedService.new(self).execute
+ Keys::LastUsedService.new(self).execute_async
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index a3946724fd3..c58ad92d7a6 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -297,9 +297,14 @@ class Packages::Package < ApplicationRecord
end
# Technical debt: to be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/281937
+ # TODO: rename the method https://gitlab.com/gitlab-org/gitlab/-/issues/410352
def original_build_info
strong_memoize(:original_build_info) do
- build_infos.first
+ if Feature.enabled?(:packages_display_last_pipeline, project)
+ build_infos.last
+ else
+ build_infos.first
+ end
end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 52e623db7b0..772a82fa173 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -206,7 +206,7 @@ class ProjectFeature < ApplicationRecord
override :resource_member?
def resource_member?(user, feature)
- project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
+ project.member?(user, ProjectFeature.required_minimum_access_level(feature))
end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 181130d45bd..3c40f4beedc 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -157,7 +157,7 @@ class Snippet < ApplicationRecord
def for_project_with_user(project, user = nil)
return none unless project.snippets_visible?(user)
- if project.team.member?(user)
+ if project.member?(user)
project.snippets
else
project.snippets.public_to_user(user)
diff --git a/app/models/todo.rb b/app/models/todo.rb
index ac41b5d0b2c..e1b5076e3d8 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -13,6 +13,7 @@ class Todo < ApplicationRecord
# and giving it back again.
WAIT_FOR_DELETE = 1.hour
+ # Actions
ASSIGNED = 1
MENTIONED = 2
BUILD_FAILED = 3
@@ -80,6 +81,7 @@ class Todo < ApplicationRecord
end
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
scope :for_internal_notes, -> { joins(:note).where(note: { confidential: true }) }
+ scope :with_preloaded_user, -> { preload(:user) }
enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by
diff --git a/app/models/user.rb b/app/models/user.rb
index 71c5afb1c79..daee2687d2f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -80,14 +80,14 @@ class User < ApplicationRecord
algorithm: 'aes-256-cbc'
devise :two_factor_authenticatable,
- otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
+ otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
devise :two_factor_backupable_pbkdf2
serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
- :validatable, :omniauthable, :confirmable, :registerable
+ :validatable, :omniauthable, :confirmable, :registerable
# Must be included after `devise`
include EncryptedUserPassword
@@ -132,11 +132,11 @@ class User < ApplicationRecord
# Namespace for personal projects
has_one :namespace,
- -> { where(type: Namespaces::UserNamespace.sti_name) },
- dependent: :destroy, # rubocop:disable Cop/ActiveRecordDependent
- foreign_key: :owner_id,
- inverse_of: :owner,
- autosave: true # rubocop:disable Cop/ActiveRecordDependent
+ -> { where(type: Namespaces::UserNamespace.sti_name) },
+ dependent: :destroy, # rubocop:disable Cop/ActiveRecordDependent
+ foreign_key: :owner_id,
+ inverse_of: :owner,
+ autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -171,18 +171,18 @@ class User < ApplicationRecord
has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group
has_many :owned_or_maintainers_groups,
- -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
- through: :group_members,
- source: :group
+ -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
+ through: :group_members,
+ source: :group
alias_attribute :masters_groups, :maintainers_groups
has_many :developer_maintainer_owned_groups,
- -> { where(members: { access_level: [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
- through: :group_members,
- source: :group
+ -> { where(members: { access_level: [Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
+ through: :group_members,
+ source: :group
has_many :reporter_developer_maintainer_owned_groups,
- -> { where(members: { access_level: [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
- through: :group_members,
- source: :group
+ -> { where(members: { access_level: [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
+ through: :group_members,
+ source: :group
has_many :minimal_access_group_members, -> { where(access_level: [Gitlab::Access::MINIMAL_ACCESS]) }, class_name: 'GroupMember'
has_many :minimal_access_groups, through: :minimal_access_group_members, source: :group
@@ -349,27 +349,27 @@ class User < ApplicationRecord
enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
delegate :notes_filter_for,
- :set_notes_filter,
- :first_day_of_week, :first_day_of_week=,
- :timezone, :timezone=,
- :time_display_relative, :time_display_relative=,
- :show_whitespace_in_diffs, :show_whitespace_in_diffs=,
- :view_diffs_file_by_file, :view_diffs_file_by_file=,
- :pass_user_identities_to_ci_jwt, :pass_user_identities_to_ci_jwt=,
- :tab_width, :tab_width=,
- :sourcegraph_enabled, :sourcegraph_enabled=,
- :gitpod_enabled, :gitpod_enabled=,
- :setup_for_company, :setup_for_company=,
- :render_whitespace_in_code, :render_whitespace_in_code=,
- :markdown_surround_selection, :markdown_surround_selection=,
- :markdown_automatic_lists, :markdown_automatic_lists=,
- :diffs_deletion_color, :diffs_deletion_color=,
- :diffs_addition_color, :diffs_addition_color=,
- :use_new_navigation, :use_new_navigation=,
- :pinned_nav_items, :pinned_nav_items=,
- :achievements_enabled, :achievements_enabled=,
- :enabled_following, :enabled_following=,
- to: :user_preference
+ :set_notes_filter,
+ :first_day_of_week, :first_day_of_week=,
+ :timezone, :timezone=,
+ :time_display_relative, :time_display_relative=,
+ :show_whitespace_in_diffs, :show_whitespace_in_diffs=,
+ :view_diffs_file_by_file, :view_diffs_file_by_file=,
+ :pass_user_identities_to_ci_jwt, :pass_user_identities_to_ci_jwt=,
+ :tab_width, :tab_width=,
+ :sourcegraph_enabled, :sourcegraph_enabled=,
+ :gitpod_enabled, :gitpod_enabled=,
+ :setup_for_company, :setup_for_company=,
+ :render_whitespace_in_code, :render_whitespace_in_code=,
+ :markdown_surround_selection, :markdown_surround_selection=,
+ :markdown_automatic_lists, :markdown_automatic_lists=,
+ :diffs_deletion_color, :diffs_deletion_color=,
+ :diffs_addition_color, :diffs_addition_color=,
+ :use_new_navigation, :use_new_navigation=,
+ :pinned_nav_items, :pinned_nav_items=,
+ :achievements_enabled, :achievements_enabled=,
+ :enabled_following, :enabled_following=,
+ to: :user_preference
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
@@ -517,28 +517,27 @@ class User < ApplicationRecord
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
scope :with_public_profile, -> { where(private_profile: false) }
scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do
- where('EXISTS (?)',
- ::PersonalAccessToken
- .where('personal_access_tokens.user_id = users.id')
- .without_impersonation
- .expiring_and_not_notified(at).select(1))
+ where('EXISTS (?)', ::PersonalAccessToken
+ .where('personal_access_tokens.user_id = users.id')
+ .without_impersonation
+ .expiring_and_not_notified(at).select(1)
+ )
end
scope :with_personal_access_tokens_expired_today, -> do
- where('EXISTS (?)',
- ::PersonalAccessToken
- .select(1)
- .where('personal_access_tokens.user_id = users.id')
- .without_impersonation
- .expired_today_and_not_notified)
+ where('EXISTS (?)', ::PersonalAccessToken
+ .select(1)
+ .where('personal_access_tokens.user_id = users.id')
+ .without_impersonation
+ .expired_today_and_not_notified
+ )
end
scope :with_ssh_key_expiring_soon, -> do
includes(:expiring_soon_and_unnotified_keys)
- .where('EXISTS (?)',
- ::Key
- .select(1)
- .where('keys.user_id = users.id')
- .expiring_soon_and_not_notified)
+ .where('EXISTS (?)', ::Key
+ .select(1)
+ .where('keys.user_id = users.id')
+ .expiring_soon_and_not_notified)
end
scope :order_recent_sign_in, -> { reorder(arel_table[:current_sign_in_at].desc.nulls_last) }
scope :order_oldest_sign_in, -> { reorder(arel_table[:current_sign_in_at].asc.nulls_last) }
@@ -2057,9 +2056,11 @@ class User < ApplicationRecord
#
# Returns a Hash mapping project ID -> maximum access level.
def max_member_access_for_project_ids(project_ids)
- Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(Project),
- resource_ids: project_ids,
- default_value: Gitlab::Access::NO_ACCESS) do |project_ids|
+ Gitlab::SafeRequestLoader.execute(
+ resource_key: max_member_access_for_resource_key(Project),
+ resource_ids: project_ids,
+ default_value: Gitlab::Access::NO_ACCESS
+ ) do |project_ids|
project_authorizations.where(project: project_ids)
.group(:project_id)
.maximum(:access_level)
@@ -2074,9 +2075,11 @@ class User < ApplicationRecord
#
# Returns a Hash mapping project ID -> maximum access level.
def max_member_access_for_group_ids(group_ids)
- Gitlab::SafeRequestLoader.execute(resource_key: max_member_access_for_resource_key(Group),
- resource_ids: group_ids,
- default_value: Gitlab::Access::NO_ACCESS) do |group_ids|
+ Gitlab::SafeRequestLoader.execute(
+ resource_key: max_member_access_for_resource_key(Group),
+ resource_ids: group_ids,
+ default_value: Gitlab::Access::NO_ACCESS
+ ) do |group_ids|
group_members.where(source: group_ids).group(:source_id).maximum(:access_level)
end
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 3a1fce9c0b4..9c40376bb13 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -20,8 +20,8 @@ class UserPreference < ApplicationRecord
less_than_or_equal_to: Gitlab::TabWidth::MAX
}
validates :diffs_deletion_color, :diffs_addition_color,
- format: { with: ColorsHelper::HEX_COLOR_PATTERN },
- allow_blank: true
+ format: { with: ColorsHelper::HEX_COLOR_PATTERN },
+ allow_blank: true
validates :pass_user_identities_to_ci_jwt, allow_nil: false, inclusion: { in: [true, false] }
diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb
index 601622b8c4c..1cc9f1f50ad 100644
--- a/app/models/users/group_callout.rb
+++ b/app/models/users/group_callout.rb
@@ -30,9 +30,9 @@ module Users
validates :group, presence: true
validates :feature_name,
- presence: true,
- uniqueness: { scope: [:user_id, :group_id] },
- inclusion: { in: GroupCallout.feature_names.keys }
+ presence: true,
+ uniqueness: { scope: [:user_id, :group_id] },
+ inclusion: { in: GroupCallout.feature_names.keys }
def source_feature_name
"#{feature_name}_#{group_id}"
diff --git a/app/models/users/phone_number_validation.rb b/app/models/users/phone_number_validation.rb
index b9e4e908ddd..52f16a7861f 100644
--- a/app/models/users/phone_number_validation.rb
+++ b/app/models/users/phone_number_validation.rb
@@ -8,28 +8,25 @@ module Users
belongs_to :user, foreign_key: :user_id
belongs_to :banned_user, class_name: '::Users::BannedUser', foreign_key: :user_id
- validates :country,
- presence: true,
- length: { maximum: 3 }
+ validates :country, presence: true, length: { maximum: 3 }
validates :international_dial_code,
- presence: true,
- numericality: {
- only_integer: true,
- greater_than_or_equal_to: 1,
- less_than_or_equal_to: 999
- }
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 1,
+ less_than_or_equal_to: 999
+ }
validates :phone_number,
- presence: true,
- format: {
- with: /\A\d+\Z/,
- message: -> (object, data) { _('can contain only digits') }
- },
- length: { maximum: 12 }
-
- validates :telesign_reference_xid,
- length: { maximum: 255 }
+ presence: true,
+ format: {
+ with: /\A\d+\Z/,
+ message: -> (object, data) { _('can contain only digits') }
+ },
+ length: { maximum: 12 }
+
+ validates :telesign_reference_xid, length: { maximum: 255 }
scope :for_user, -> (user_id) { where(user_id: user_id) }
diff --git a/app/models/users/project_callout.rb b/app/models/users/project_callout.rb
index 6199c3697f7..3964f202be6 100644
--- a/app/models/users/project_callout.rb
+++ b/app/models/users/project_callout.rb
@@ -20,8 +20,8 @@ module Users
validates :project, presence: true
validates :feature_name,
- presence: true,
- uniqueness: { scope: [:user_id, :project_id] },
- inclusion: { in: ProjectCallout.feature_names.keys }
+ presence: true,
+ uniqueness: { scope: [:user_id, :project_id] },
+ inclusion: { in: ProjectCallout.feature_names.keys }
end
end
diff --git a/app/models/users/user_follow_user.rb b/app/models/users/user_follow_user.rb
index 5a82a81364a..c9d4bee496c 100644
--- a/app/models/users/user_follow_user.rb
+++ b/app/models/users/user_follow_user.rb
@@ -14,9 +14,13 @@ module Users
followee_count = self.class.where(follower_id: follower_id).limit(MAX_FOLLOWEE_LIMIT).count
return if followee_count < MAX_FOLLOWEE_LIMIT
- errors.add(:base, format(
- _("You can't follow more than %{limit} users. To follow more users, unfollow some others."),
- limit: MAX_FOLLOWEE_LIMIT))
+ errors.add(
+ :base,
+ format(
+ _("You can't follow more than %{limit} users. To follow more users, unfollow some others."),
+ limit: MAX_FOLLOWEE_LIMIT
+ )
+ )
end
end
end
diff --git a/app/models/web_ide_terminal.rb b/app/models/web_ide_terminal.rb
index ef70df2405f..fe69ca80c32 100644
--- a/app/models/web_ide_terminal.rb
+++ b/app/models/web_ide_terminal.rb
@@ -39,12 +39,14 @@ class WebIdeTerminal
private
def web_ide_terminal_route_generator(action, options = {})
- options.reverse_merge!(action: action,
- controller: 'projects/web_ide_terminals',
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: build.id,
- only_path: true)
+ options.reverse_merge!(
+ action: action,
+ controller: 'projects/web_ide_terminals',
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: build.id,
+ only_path: true
+ )
url_for(options)
end
diff --git a/app/models/webauthn_registration.rb b/app/models/webauthn_registration.rb
index 86e7e2dedc3..c8b2513e702 100644
--- a/app/models/webauthn_registration.rb
+++ b/app/models/webauthn_registration.rb
@@ -12,5 +12,5 @@ class WebauthnRegistration < ApplicationRecord
validates :credential_xid, :public_key, :counter, presence: true
validates :name, length: { minimum: 0, allow_nil: false }
validates :counter,
- numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**32 - 1 }
+ numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**32 - 1 }
end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index b04aa196883..e1468872f52 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -145,10 +145,12 @@ class WikiPage
default_per_page = Kaminari.config.default_per_page
offset = [options[:page].to_i - 1, 0].max * options.fetch(:per_page, default_per_page)
- wiki.repository.commits(wiki.default_branch,
- path: page.path,
- limit: options.fetch(:limit, default_per_page),
- offset: offset)
+ wiki.repository.commits(
+ wiki.default_branch,
+ path: page.path,
+ limit: options.fetch(:limit, default_per_page),
+ offset: offset
+ )
end
def count_versions
diff --git a/app/models/work_item.rb b/app/models/work_item.rb
index 4724d644318..24d1078516e 100644
--- a/app/models/work_item.rb
+++ b/app/models/work_item.rb
@@ -16,10 +16,10 @@ class WorkItem < Issue
has_many :child_links, class_name: '::WorkItems::ParentLink', foreign_key: :work_item_parent_id
has_many :work_item_children, through: :child_links, class_name: 'WorkItem',
- foreign_key: :work_item_id, source: :work_item
+ foreign_key: :work_item_id, source: :work_item
has_many :work_item_children_by_relative_position, -> { work_item_children_keyset_order },
- through: :child_links, class_name: 'WorkItem',
- foreign_key: :work_item_id, source: :work_item
+ through: :child_links, class_name: 'WorkItem',
+ foreign_key: :work_item_id, source: :work_item
scope :inc_relations_for_permission_check, -> { includes(:author, project: :project_feature) }
diff --git a/app/services/keys/last_used_service.rb b/app/services/keys/last_used_service.rb
index daef544bac0..3683c03b7a4 100644
--- a/app/services/keys/last_used_service.rb
+++ b/app/services/keys/last_used_service.rb
@@ -2,7 +2,7 @@
module Keys
class LastUsedService
- TIMEOUT = 1.day.to_i
+ TIMEOUT = 1.day
attr_reader :key
@@ -12,26 +12,24 @@ module Keys
end
def execute
+ return unless update?
+
# We _only_ want to update last_used_at and not also updated_at (which
# would be updated when using #touch).
- key.update_column(:last_used_at, Time.zone.now) if update?
+ key.update_column(:last_used_at, Time.zone.now)
end
- def update?
- return false if ::Gitlab::Database.read_only?
-
- last_used = key.last_used_at
+ def execute_async
+ return unless update?
- return false if last_used && (Time.zone.now - last_used) <= TIMEOUT
-
- !!redis_lease.try_obtain
+ ::SshKeys::UpdateLastUsedAtWorker.perform_async(key.id)
end
- private
+ def update?
+ return false if ::Gitlab::Database.read_only?
- def redis_lease
- Gitlab::ExclusiveLease
- .new("key_update_last_used_at:#{key.id}", timeout: TIMEOUT)
+ last_used = key.last_used_at
+ last_used.blank? || last_used <= TIMEOUT.ago
end
end
end
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
index 20f96ac2949..f8c91fbae7d 100644
--- a/app/services/members/approve_access_request_service.rb
+++ b/app/services/members/approve_access_request_service.rb
@@ -18,7 +18,7 @@ module Members
def after_execute(member:, skip_log_audit_event:)
super
- resolve_access_request_todos(current_user, member)
+ resolve_access_request_todos(member)
end
def validate_access!(access_requester)
diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb
index 801f77ae082..80fba33b20e 100644
--- a/app/services/members/base_service.rb
+++ b/app/services/members/base_service.rb
@@ -53,8 +53,8 @@ module Members
end
end
- def resolve_access_request_todos(current_user, requester)
- todo_service.resolve_access_request_todos(current_user, requester)
+ def resolve_access_request_todos(member)
+ todo_service.resolve_access_request_todos(member)
end
def enqueue_delete_todos(member)
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 0f195663a61..b77485ce744 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -84,7 +84,7 @@ module Members
delete_subresources(member) unless skip_subresources
delete_project_invitations_by(member) unless skip_subresources
- resolve_access_request_todos(current_user, member)
+ resolve_access_request_todos(member)
after_execute(member: member)
end
diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb
index b373a099020..d31f4596fa5 100644
--- a/app/services/projects/open_issues_count_service.rb
+++ b/app/services/projects/open_issues_count_service.rb
@@ -26,7 +26,7 @@ module Projects
def user_is_at_least_reporter?
strong_memoize(:user_is_at_least_reporter) do
- @project.team.member?(@user, Gitlab::Access::REPORTER)
+ @project.member?(@user, Gitlab::Access::REPORTER)
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 2025d438ae7..c55e1680bfe 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -175,13 +175,26 @@ class TodoService
TodosFinder.new(current_user).any_for_target?(issuable, :pending)
end
- # Resolves all todos related to target
+ # Resolves all todos related to target for the current_user
def resolve_todos_for_target(target, current_user)
attributes = attributes_for_target(target)
resolve_todos(pending_todos([current_user], attributes), current_user)
end
+ # Resolves all todos related to target for all users
+ def resolve_todos_with_attributes_for_target(target, attributes, resolution: :done, resolved_by_action: :system_done)
+ target_attributes = { target_id: target.id, target_type: target.class.polymorphic_name }
+ attributes.merge!(target_attributes)
+ attributes[:preload_user_association] = true
+
+ todos = PendingTodosFinder.new(attributes).execute
+ users = todos.map(&:user)
+ todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
+ users.each(&:update_todos_count_cache)
+ todos_ids
+ end
+
def resolve_todos(todos, current_user, resolution: :done, resolved_by_action: :system_done)
todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
@@ -198,21 +211,20 @@ class TodoService
current_user.update_todos_count_cache
end
- def resolve_access_request_todos(current_user, member)
- return if current_user.nil? || member.nil?
+ def resolve_access_request_todos(member)
+ return if member.nil?
+ # Group or Project
target = member.source
- finder_params = {
+ todos_params = {
state: :pending,
author_id: member.user_id,
- action_id: ::Todo::MEMBER_ACCESS_REQUESTED,
- type: target.class.polymorphic_name,
- target: target.id
+ action: ::Todo::MEMBER_ACCESS_REQUESTED,
+ type: target.class.polymorphic_name
}
- todos = TodosFinder.new(current_user, finder_params).execute
- resolve_todos(todos, current_user)
+ resolve_todos_with_attributes_for_target(target, todos_params)
end
def restore_todos(todos, current_user)
@@ -419,7 +431,7 @@ class TodoService
end
def pending_todos(users, criteria = {})
- PendingTodosFinder.new(users, criteria).execute
+ PendingTodosFinder.new(criteria.merge(users: users)).execute
end
def track_todo_creation(user, issue_type, namespace, project)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index f53b2051835..7c936c849d0 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -4,7 +4,7 @@
- if @snippets.exists?
- if current_user
.top-area
- - include_private = @project.team.member?(current_user) || current_user.admin?
+ - include_private = @project.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private, counts: @snippet_counts }
- if new_project_snippet_link.present?
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e9965ff0027..d6f410f4b1d 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -3468,6 +3468,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: ssh_keys_update_last_used_at
+ :worker_name: SshKeys::UpdateLastUsedAtWorker
+ :feature_category: :source_code_management
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: system_hook_push
:worker_name: SystemHookPushWorker
:feature_category: :source_code_management
diff --git a/app/workers/ssh_keys/update_last_used_at_worker.rb b/app/workers/ssh_keys/update_last_used_at_worker.rb
new file mode 100644
index 00000000000..80c2132b8d8
--- /dev/null
+++ b/app/workers/ssh_keys/update_last_used_at_worker.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module SshKeys
+ class UpdateLastUsedAtWorker
+ include ApplicationWorker
+
+ idempotent!
+ deduplicate :until_executed
+ data_consistency :sticky
+
+ feature_category :source_code_management
+
+ def perform(key_id)
+ key = Key.find_by_id(key_id)
+
+ return unless key
+
+ Keys::LastUsedService.new(key).execute
+ end
+ end
+end