From 049d16d168fdee408b78f5f38619c092fd3b2265 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 20 Oct 2022 15:10:58 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../artifacts/components/artifact_row.vue | 88 ++++++ .../components/artifacts_table_row_details.vue | 89 ++++++ .../artifacts/components/job_artifacts_table.vue | 314 +++++++++++++++++++++ app/assets/javascripts/artifacts/constants.js | 47 +++ .../javascripts/artifacts/graphql/cache_update.js | 30 ++ .../mutations/destroy_artifact.mutation.graphql | 7 + .../queries/get_job_artifacts.query.graphql | 56 ++++ app/assets/javascripts/artifacts/index.js | 29 ++ app/assets/javascripts/artifacts/utils.js | 26 ++ .../javascripts/diffs/components/diff_row_utils.js | 21 +- app/assets/javascripts/diffs/store/utils.js | 11 +- .../javascripts/pages/projects/artifacts/index.js | 3 + .../components/reviewers/sidebar_reviewers.vue | 35 +++ .../reviewers/sidebar_reviewers_inputs.vue | 34 +++ app/assets/javascripts/sidebar/mount_sidebar.js | 13 + .../javascripts/sidebar/stores/sidebar_store.js | 4 + app/assets/javascripts/users_select/index.js | 4 + .../merge_request_reviewers.subscription.graphql | 22 ++ app/assets/stylesheets/pages/notes.scss | 2 +- app/controllers/dashboard/projects_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 1 + app/controllers/users_controller.rb | 2 - app/finders/projects_finder.rb | 7 +- .../resolvers/concerns/project_search_arguments.rb | 1 - app/models/concerns/protected_ref.rb | 1 - app/models/group.rb | 2 + app/models/project_authorization.rb | 10 + app/models/protected_branch.rb | 16 ++ app/models/protected_tag.rb | 1 + .../_account_and_limit.html.haml | 2 + app/views/projects/artifacts/_artifact.html.haml | 61 ---- app/views/projects/artifacts/_table.html.haml | 16 -- app/views/projects/artifacts/index.html.haml | 13 +- .../shared/issuable/_sidebar_reviewers.html.haml | 9 +- 34 files changed, 872 insertions(+), 112 deletions(-) create mode 100644 app/assets/javascripts/artifacts/components/artifact_row.vue create mode 100644 app/assets/javascripts/artifacts/components/artifacts_table_row_details.vue create mode 100644 app/assets/javascripts/artifacts/components/job_artifacts_table.vue create mode 100644 app/assets/javascripts/artifacts/constants.js create mode 100644 app/assets/javascripts/artifacts/graphql/cache_update.js create mode 100644 app/assets/javascripts/artifacts/graphql/mutations/destroy_artifact.mutation.graphql create mode 100644 app/assets/javascripts/artifacts/graphql/queries/get_job_artifacts.query.graphql create mode 100644 app/assets/javascripts/artifacts/index.js create mode 100644 app/assets/javascripts/artifacts/utils.js create mode 100644 app/assets/javascripts/pages/projects/artifacts/index.js create mode 100644 app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers_inputs.vue create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql delete mode 100644 app/views/projects/artifacts/_artifact.html.haml delete mode 100644 app/views/projects/artifacts/_table.html.haml (limited to 'app') diff --git a/app/assets/javascripts/artifacts/components/artifact_row.vue b/app/assets/javascripts/artifacts/components/artifact_row.vue new file mode 100644 index 00000000000..92044a3641a --- /dev/null +++ b/app/assets/javascripts/artifacts/components/artifact_row.vue @@ -0,0 +1,88 @@ + + diff --git a/app/assets/javascripts/artifacts/components/artifacts_table_row_details.vue b/app/assets/javascripts/artifacts/components/artifacts_table_row_details.vue new file mode 100644 index 00000000000..089bfd80222 --- /dev/null +++ b/app/assets/javascripts/artifacts/components/artifacts_table_row_details.vue @@ -0,0 +1,89 @@ + + diff --git a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue new file mode 100644 index 00000000000..7b11e4f17f3 --- /dev/null +++ b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue @@ -0,0 +1,314 @@ + + diff --git a/app/assets/javascripts/artifacts/constants.js b/app/assets/javascripts/artifacts/constants.js new file mode 100644 index 00000000000..9ed0821ac2d --- /dev/null +++ b/app/assets/javascripts/artifacts/constants.js @@ -0,0 +1,47 @@ +import { __, s__, n__ } from '~/locale'; + +export const JOB_STATUS_GROUP_SUCCESS = 'success'; + +export const STATUS_BADGE_VARIANTS = { + success: 'success', + passed: 'success', + error: 'danger', + failed: 'danger', + pending: 'warning', + 'waiting-for-resource': 'warning', + 'failed-with-warnings': 'warning', + 'success-with-warnings': 'warning', + running: 'info', + canceled: 'neutral', + disabled: 'neutral', + scheduled: 'neutral', + manual: 'neutral', + notification: 'muted', + preparing: 'muted', + created: 'muted', + skipped: 'muted', + notfound: 'muted', +}; + +export const I18N_DOWNLOAD = __('Download'); +export const I18N_BROWSE = s__('Artifacts|Browse'); +export const I18N_DELETE = __('Delete'); +export const I18N_EXPIRED = __('Expired'); +export const I18N_DESTROY_ERROR = s__('Artifacts|An error occurred while deleting the artifact'); +export const I18N_FETCH_ERROR = s__('Artifacts|An error occurred while retrieving job artifacts'); +export const I18N_ARTIFACTS = __('Artifacts'); +export const I18N_JOB = __('Job'); +export const I18N_SIZE = __('Size'); +export const I18N_CREATED = __('Created'); +export const I18N_ARTIFACTS_COUNT = (count) => n__('%d file', '%d files', count); + +export const INITIAL_CURRENT_PAGE = 1; +export const INITIAL_PREVIOUS_PAGE_CURSOR = ''; +export const INITIAL_NEXT_PAGE_CURSOR = ''; +export const JOBS_PER_PAGE = 20; +export const INITIAL_LAST_PAGE_SIZE = null; + +export const ARCHIVE_FILE_TYPE = 'ARCHIVE'; + +export const ARTIFACT_ROW_HEIGHT = 56; +export const ARTIFACTS_SHOWN_WITHOUT_SCROLLING = 4; diff --git a/app/assets/javascripts/artifacts/graphql/cache_update.js b/app/assets/javascripts/artifacts/graphql/cache_update.js new file mode 100644 index 00000000000..c620e03c80d --- /dev/null +++ b/app/assets/javascripts/artifacts/graphql/cache_update.js @@ -0,0 +1,30 @@ +import produce from 'immer'; + +export const hasErrors = ({ errors = [] }) => errors?.length; + +export function removeArtifactFromStore(store, deletedArtifactId, query, variables) { + if (!hasErrors(deletedArtifactId)) { + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, (draftData) => { + draftData.project.jobs.nodes = draftData.project.jobs.nodes.map((jobNode) => { + return { + ...jobNode, + artifacts: { + ...jobNode.artifacts, + nodes: jobNode.artifacts.nodes.filter(({ id }) => id !== deletedArtifactId), + }, + }; + }); + }); + + store.writeQuery({ + query, + variables, + data, + }); + } +} diff --git a/app/assets/javascripts/artifacts/graphql/mutations/destroy_artifact.mutation.graphql b/app/assets/javascripts/artifacts/graphql/mutations/destroy_artifact.mutation.graphql new file mode 100644 index 00000000000..529224b47e6 --- /dev/null +++ b/app/assets/javascripts/artifacts/graphql/mutations/destroy_artifact.mutation.graphql @@ -0,0 +1,7 @@ +mutation destroyArtifact($id: CiJobArtifactID!) { + artifactDestroy(input: { id: $id }) { + artifact { + id + } + } +} diff --git a/app/assets/javascripts/artifacts/graphql/queries/get_job_artifacts.query.graphql b/app/assets/javascripts/artifacts/graphql/queries/get_job_artifacts.query.graphql new file mode 100644 index 00000000000..685196e28d5 --- /dev/null +++ b/app/assets/javascripts/artifacts/graphql/queries/get_job_artifacts.query.graphql @@ -0,0 +1,56 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" + +query getJobArtifacts( + $projectPath: ID! + $firstPageSize: Int + $lastPageSize: Int + $prevPageCursor: String = "" + $nextPageCursor: String = "" +) { + project(fullPath: $projectPath) { + id + jobs( + statuses: [SUCCESS, FAILED] + first: $firstPageSize + last: $lastPageSize + after: $nextPageCursor + before: $prevPageCursor + ) { + count + nodes { + id + name + webPath + detailedStatus { + id + group + icon + label + } + pipeline { + id + iid + path + } + refName + refPath + shortSha + commitPath + finishedAt + artifacts { + nodes { + id + name + fileType + downloadPath + size + expireAt + } + } + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/javascripts/artifacts/index.js b/app/assets/javascripts/artifacts/index.js new file mode 100644 index 00000000000..b5146e0f0e9 --- /dev/null +++ b/app/assets/javascripts/artifacts/index.js @@ -0,0 +1,29 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import JobArtifactsTable from './components/job_artifacts_table.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export const initArtifactsTable = () => { + const el = document.querySelector('#js-artifact-management'); + + if (!el) { + return false; + } + + const { projectPath } = el.dataset; + + return new Vue({ + el, + apolloProvider, + provide: { + projectPath, + }, + render: (createElement) => createElement(JobArtifactsTable), + }); +}; diff --git a/app/assets/javascripts/artifacts/utils.js b/app/assets/javascripts/artifacts/utils.js new file mode 100644 index 00000000000..ebcf0af8d2a --- /dev/null +++ b/app/assets/javascripts/artifacts/utils.js @@ -0,0 +1,26 @@ +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { ARCHIVE_FILE_TYPE, JOB_STATUS_GROUP_SUCCESS } from './constants'; + +export const totalArtifactsSizeForJob = (job) => + numberToHumanSize( + job.artifacts.nodes + .map((artifact) => artifact.size) + .reduce((total, artifact) => total + artifact, 0), + ); + +export const mapArchivesToJobNodes = (jobNode) => { + return { + archive: { + ...jobNode.artifacts.nodes.find((artifact) => artifact.fileType === ARCHIVE_FILE_TYPE), + }, + ...jobNode, + }; +}; + +export const mapBooleansToJobNodes = (jobNode) => { + return { + succeeded: jobNode.detailedStatus.group === JOB_STATUS_GROUP_SUCCESS, + hasArtifacts: jobNode.artifacts.nodes.length > 0, + ...jobNode, + }; +}; diff --git a/app/assets/javascripts/diffs/components/diff_row_utils.js b/app/assets/javascripts/diffs/components/diff_row_utils.js index 7732badde34..e0749b63021 100644 --- a/app/assets/javascripts/diffs/components/diff_row_utils.js +++ b/app/assets/javascripts/diffs/components/diff_row_utils.js @@ -57,21 +57,32 @@ export const classNameMapCell = ({ line, hll, isLoggedIn, isHover }) => { export const addCommentTooltip = (line) => { let tooltip; - if (!line) return tooltip; + if (!line) { + return tooltip; + } tooltip = __('Add a comment to this line or drag for multiple lines'); - const brokenSymlinks = line.commentsDisabled; - if (brokenSymlinks) { - if (brokenSymlinks.wasSymbolic || brokenSymlinks.isSymbolic) { + if (!line.problems) { + return tooltip; + } + + const { brokenSymlink, brokenLineCode, fileOnlyMoved } = line.problems; + + if (brokenSymlink) { + if (brokenSymlink.wasSymbolic || brokenSymlink.isSymbolic) { tooltip = __( 'Commenting on symbolic links that replace or are replaced by files is currently not supported.', ); - } else if (brokenSymlinks.wasReal || brokenSymlinks.isReal) { + } else if (brokenSymlink.wasReal || brokenSymlink.isReal) { tooltip = __( 'Commenting on files that replace or are replaced by symbolic links is currently not supported.', ); } + } else if (fileOnlyMoved) { + tooltip = __('Commenting on files that are only moved or renamed is currently not supported'); + } else if (brokenLineCode) { + tooltip = __('Commenting on this line is currently not supported'); } return tooltip; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index cf86ebea4a9..0519ca3d715 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -324,15 +324,24 @@ function cleanRichText(text) { } function prepareLine(line, file) { + const problems = { + brokenSymlink: file.brokenSymlink, + brokenLineCode: !line.line_code, + fileOnlyMoved: file.renamed_file && file.added_lines === 0 && file.removed_lines === 0, + }; + if (!line.alreadyPrepared) { Object.assign(line, { - commentsDisabled: file.brokenSymlink, + commentsDisabled: Boolean( + problems.brokenSymlink || problems.fileOnlyMoved || problems.brokenLineCode, + ), rich_text: cleanRichText(line.rich_text), discussionsExpanded: true, discussions: [], hasForm: false, text: undefined, alreadyPrepared: true, + problems, }); } } diff --git a/app/assets/javascripts/pages/projects/artifacts/index.js b/app/assets/javascripts/pages/projects/artifacts/index.js new file mode 100644 index 00000000000..4aa9b225790 --- /dev/null +++ b/app/assets/javascripts/pages/projects/artifacts/index.js @@ -0,0 +1,3 @@ +import { initArtifactsTable } from '~/artifacts/index'; + +initArtifactsTable(); diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue index ad061dd2e6b..5f1350690eb 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue @@ -9,6 +9,8 @@ import eventHub from '~/sidebar/event_hub'; import Store from '~/sidebar/stores/sidebar_store'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import getMergeRequestReviewersQuery from '~/vue_shared/components/sidebar/queries/get_merge_request_reviewers.query.graphql'; +import mergeRequestReviewersUpdatedSubscription from '~/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import ReviewerTitle from './reviewer_title.vue'; import Reviewers from './reviewers.vue'; @@ -66,6 +68,36 @@ export default { error() { createAlert({ message: __('An error occurred while fetching reviewers.') }); }, + subscribeToMore: { + document() { + return mergeRequestReviewersUpdatedSubscription; + }, + variables() { + return { + issuableId: this.issuable?.id, + }; + }, + skip() { + return !this.issuable?.id || !this.isRealtimeEnabled; + }, + updateQuery( + _, + { + subscriptionData: { + data: { mergeRequestReviewersUpdated }, + }, + }, + ) { + if (mergeRequestReviewersUpdated) { + this.store.setReviewersFromRealtime( + mergeRequestReviewersUpdated.reviewers.nodes.map((r) => ({ + ...r, + id: getIdFromGraphQLId(r.id), + })), + ); + } + }, + }, }, }, data() { @@ -87,6 +119,9 @@ export default { canUpdate() { return this.issuable.userPermissions?.adminMergeRequest || false; }, + isRealtimeEnabled() { + return this.glFeatures.realtimeReviewers; + }, }, created() { this.store = new Store(); diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers_inputs.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers_inputs.vue new file mode 100644 index 00000000000..a135dfdca72 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers_inputs.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 9b5bad710dd..1d9ddfe6bfd 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -33,6 +33,7 @@ import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue'; import SidebarEscalationStatus from './components/incidents/sidebar_escalation_status.vue'; import IssuableLockForm from './components/lock/issuable_lock_form.vue'; import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue'; +import SidebarReviewersInputs from './components/reviewers/sidebar_reviewers_inputs.vue'; import SidebarSeverity from './components/severity/sidebar_severity.vue'; import SidebarSubscriptionsWidget from './components/subscriptions/sidebar_subscriptions_widget.vue'; import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue'; @@ -210,6 +211,18 @@ function mountReviewersComponent(mediator) { }), }); + const reviewersInputEl = document.querySelector('.js-reviewers-inputs'); + + if (reviewersInputEl) { + // eslint-disable-next-line no-new + new Vue({ + el: reviewersInputEl, + render(createElement) { + return createElement(SidebarReviewersInputs); + }, + }); + } + const reviewerDropdown = document.querySelector('.js-sidebar-reviewer-dropdown'); if (reviewerDropdown) { diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js index e2581a8f30e..baf906bb96c 100644 --- a/app/assets/javascripts/sidebar/stores/sidebar_store.js +++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js @@ -138,6 +138,10 @@ export default class SidebarStore { this.assignees = data; } + setReviewersFromRealtime(data) { + this.reviewers = data; + } + setAutocompleteProjects(projects) { this.autocompleteProjects = projects; } diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js index bd425bdc2a8..8fc5c354802 100644 --- a/app/assets/javascripts/users_select/index.js +++ b/app/assets/javascripts/users_select/index.js @@ -431,6 +431,10 @@ function UsersSelect(currentUser, els, options = {}) { hidden() { if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass(elsClassName)) { + if (window.gon?.features?.realtimeReviewers) { + $dropdown.data('deprecatedJQueryDropdown').clearMenu(); + $dropdown.closest('.selectbox').children('input[type="hidden"]').remove(); + } emitSidebarEvent('sidebar.saveReviewers'); } else { emitSidebarEvent('sidebar.saveAssignees'); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql b/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql new file mode 100644 index 00000000000..a1b16b378b3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/queries/merge_request_reviewers.subscription.graphql @@ -0,0 +1,22 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" +#import "~/graphql_shared/fragments/user_availability.fragment.graphql" + +subscription mergeRequestReviewersUpdated($issuableId: IssuableID!) { + mergeRequestReviewersUpdated(issuableId: $issuableId) { + ... on MergeRequest { + id + reviewers { + nodes { + ...User + ...UserAvailability + mergeRequestInteraction { + canMerge + canUpdate + approved + reviewed + } + } + } + } + } +} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 438b7b1afa6..955dcdc1c0f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -968,7 +968,7 @@ $system-note-svg-size: 1rem; height: 12px; } - &:hover, + &:hover:not([disabled]), &.inverted { &::before { background-color: $white; diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 0e4592259d8..89d362c88a4 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -66,11 +66,10 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end def load_projects(finder_params) - @total_user_projects_count = ProjectsFinder.new(params: { non_public: true, without_deleted: true, not_aimed_for_deletion: true }, current_user: current_user).execute - @total_starred_projects_count = ProjectsFinder.new(params: { starred: true, without_deleted: true, not_aimed_for_deletion: true }, current_user: current_user).execute + @total_user_projects_count = ProjectsFinder.new(params: { non_public: true, not_aimed_for_deletion: true }, current_user: current_user).execute + @total_starred_projects_count = ProjectsFinder.new(params: { starred: true, not_aimed_for_deletion: true }, current_user: current_user).execute finder_params[:use_cte] = true if use_cte_for_finder? - finder_params[:without_deleted] = true projects = ProjectsFinder.new(params: finder_params, current_user: current_user).execute @@ -93,7 +92,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController def load_events projects = ProjectsFinder - .new(params: params.merge(non_public: true, without_deleted: true), current_user: current_user) + .new(params: params.merge(non_public: true, not_aimed_for_deletion: true), current_user: current_user) .execute @events = EventCollection diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9c139733248..36c050be76b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -44,6 +44,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:paginated_mr_discussions, project) push_frontend_feature_flag(:mr_review_submit_comment, project) push_frontend_feature_flag(:mr_experience_survey, project) + push_frontend_feature_flag(:realtime_reviewers, project) end before_action do diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c35aa8e4346..0f03333d793 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -274,8 +274,6 @@ class UsersController < ApplicationController def finder_params { - # don't display projects pending deletion - without_deleted: true, # don't display projects marked for deletion not_aimed_for_deletion: true } diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 6bfe730ebc9..126687ae41f 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -27,7 +27,6 @@ # last_activity_after: datetime # last_activity_before: datetime # repository_storage: string -# without_deleted: boolean # not_aimed_for_deletion: boolean # class ProjectsFinder < UnionFinder @@ -76,6 +75,7 @@ class ProjectsFinder < UnionFinder # EE would override this to add more filters def filter_projects(collection) + collection = collection.without_deleted collection = by_ids(collection) collection = by_personal(collection) collection = by_starred(collection) @@ -86,7 +86,6 @@ class ProjectsFinder < UnionFinder collection = by_search(collection) collection = by_archived(collection) collection = by_custom_attributes(collection) - collection = by_deleted_status(collection) collection = by_not_aimed_for_deletion(collection) collection = by_last_activity_after(collection) collection = by_last_activity_before(collection) @@ -212,10 +211,6 @@ class ProjectsFinder < UnionFinder items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?) end - def by_deleted_status(items) - params[:without_deleted].present? ? items.without_deleted : items - end - def by_not_aimed_for_deletion(items) params[:not_aimed_for_deletion].present? ? items.not_aimed_for_deletion : items end diff --git a/app/graphql/resolvers/concerns/project_search_arguments.rb b/app/graphql/resolvers/concerns/project_search_arguments.rb index 7e03963f412..faf3b85fc14 100644 --- a/app/graphql/resolvers/concerns/project_search_arguments.rb +++ b/app/graphql/resolvers/concerns/project_search_arguments.rb @@ -25,7 +25,6 @@ module ProjectSearchArguments def project_finder_params(params) { - without_deleted: true, non_public: params[:membership], search: params[:search], search_namespaces: params[:search_namespaces], diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index ec56f4a32af..7e1ebd1eba3 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -7,7 +7,6 @@ module ProtectedRef belongs_to :project, touch: true validates :name, presence: true - validates :project, presence: true delegate :matching, :matches?, :wildcard?, to: :ref_matcher diff --git a/app/models/group.rb b/app/models/group.rb index 38623d91705..708fe83a7e5 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -119,6 +119,8 @@ class Group < Namespace has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id + has_many :protected_branches, inverse_of: :group + has_one :group_feature, inverse_of: :group, class_name: 'Groups::FeatureSetting' delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb index 8b43e5e5d63..3623b3be20d 100644 --- a/app/models/project_authorization.rb +++ b/app/models/project_authorization.rb @@ -31,6 +31,7 @@ class ProjectAuthorization < ApplicationRecord def self.insert_all_in_batches(attributes, per_batch = BATCH_SIZE) add_delay = add_delay_between_batches?(entire_size: attributes.size, batch_size: per_batch) + log_details(entire_size: attributes.size) if add_delay attributes.each_slice(per_batch) do |attributes_batch| insert_all(attributes_batch) @@ -40,6 +41,7 @@ class ProjectAuthorization < ApplicationRecord def self.delete_all_in_batches_for_project(project:, user_ids:, per_batch: BATCH_SIZE) add_delay = add_delay_between_batches?(entire_size: user_ids.size, batch_size: per_batch) + log_details(entire_size: user_ids.size) if add_delay user_ids.each_slice(per_batch) do |user_ids_batch| project.project_authorizations.where(user_id: user_ids_batch).delete_all @@ -49,6 +51,7 @@ class ProjectAuthorization < ApplicationRecord def self.delete_all_in_batches_for_user(user:, project_ids:, per_batch: BATCH_SIZE) add_delay = add_delay_between_batches?(entire_size: project_ids.size, batch_size: per_batch) + log_details(entire_size: project_ids.size) if add_delay project_ids.each_slice(per_batch) do |project_ids_batch| user.project_authorizations.where(project_id: project_ids_batch).delete_all @@ -65,6 +68,13 @@ class ProjectAuthorization < ApplicationRecord Feature.enabled?(:enable_minor_delay_during_project_authorizations_refresh) end + private_class_method def self.log_details(entire_size:) + Gitlab::AppLogger.info( + entire_size: entire_size, + message: 'Project authorizations refresh performed with delay' + ) + end + private_class_method def self.perform_delay sleep(SLEEP_DELAY) end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index dfd5c315f6e..80967c1b072 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -4,6 +4,10 @@ class ProtectedBranch < ApplicationRecord include ProtectedRef include Gitlab::SQL::Pattern + belongs_to :group, foreign_key: :namespace_id, touch: true, inverse_of: :protected_branches + + validate :validate_either_project_or_top_group + scope :requiring_code_owner_approval, -> { where(code_owner_approval_required: true) } @@ -99,6 +103,18 @@ class ProtectedBranch < ApplicationRecord def default_branch? name == project.default_branch end + + private + + def validate_either_project_or_top_group + if !project && !group + errors.add(:base, _('must be associated with a Group or a Project')) + elsif project && group + errors.add(:base, _('cannot be associated with both a Group and a Project')) + elsif group && group.root_ancestor != group + errors.add(:base, _('cannot be associated with a subgroup')) + end + end end ProtectedBranch.prepend_mod_with('ProtectedBranch') diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb index 5b2467daddc..e89cb3aabc7 100644 --- a/app/models/protected_tag.rb +++ b/app/models/protected_tag.rb @@ -4,6 +4,7 @@ class ProtectedTag < ApplicationRecord include ProtectedRef validates :name, uniqueness: { scope: :project_id } + validates :project, presence: true protected_ref_access_levels :create diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index c091a2180c5..0f7b10f822d 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -69,4 +69,6 @@ = render 'admin/application_settings/invitation_flow_enforcement', form: f = render 'admin/application_settings/user_restrictions', form: f = render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f + -# This is added for Jihu edition which should not be deleted without notifying Jihu + = render_if_exists 'admin/application_settings/password_expiration_setting', form: f = f.submit _('Save changes'), pajamas_button: true, data: { qa_selector: 'save_changes_button' } diff --git a/app/views/projects/artifacts/_artifact.html.haml b/app/views/projects/artifacts/_artifact.html.haml deleted file mode 100644 index 9e548582396..00000000000 --- a/app/views/projects/artifacts/_artifact.html.haml +++ /dev/null @@ -1,61 +0,0 @@ -.gl-responsive-table-row.px-md-3 - .table-section.section-25.section-wrap.commit - .table-mobile-header{ role: 'rowheader' }= _('Job') - .table-mobile-content - .branch-commit.cgray - - if can?(current_user, :read_build, @project) - = link_to project_job_path(@project, artifact.job) do - %span.build-link ##{artifact.job_id} - - else - %span.build-link ##{artifact.job_id} - - - if artifact.job.ref - .icon-container.gl-display-inline-block{ "aria-label" => artifact.job.tag? ? _('Tag') : _('Branch') } - = artifact.job.tag? ? sprite_icon('tag', css_class: 'sprite') : sprite_icon('branch', css_class: 'sprite') - = link_to artifact.job.ref, project_ref_path(@project, artifact.job.ref), class: 'ref-name' - - else - .light= _('none') - .icon-container.commit-icon{ "aria-label" => _('Commit') } - = sprite_icon('commit') - - - if artifact.job.sha - = link_to artifact.job.short_sha, project_commit_path(@project, artifact.job.sha), class: 'commit-sha mr-0' - - .table-section.section-15.section-wrap - .table-mobile-header{ role: 'rowheader' }= _('Name') - .table-mobile-content - = artifact.job.name - - .table-section.section-20 - .table-mobile-header{ role: 'rowheader' }= _('Creation date') - .table-mobile-content - %p.finished-at - = sprite_icon("calendar") - %span= time_ago_with_tooltip(artifact.created_at) - - .table-section.section-20 - .table-mobile-header{ role: 'rowheader' }= _('Expiration date') - .table-mobile-content - - if artifact.expire_at - %p.finished-at - = sprite_icon("calendar") - %span= time_ago_with_tooltip(artifact.expire_at) - - .table-section.section-10 - .table-mobile-header{ role: 'rowheader' }= _('Size') - .table-mobile-content - = number_to_human_size(artifact.size, precision: 2) - - .table-section.table-button-footer.section-10 - .table-action-buttons - .btn-group - - if can?(current_user, :read_build, @project) - = link_to download_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', download: '', title: _('Download artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Download artifacts') }, class: 'gl-button btn btn-default btn-icon has-tooltip' do - = sprite_icon('download', css_class: 'gl-icon') - - = link_to browse_project_job_artifacts_path(@project, artifact.job), rel: 'nofollow', title: _('Browse artifacts'), data: { placement: 'top', container: 'body' }, ref: 'tooltip', aria: { label: _('Browse artifacts') }, class: 'gl-button btn btn-default btn-icon has-tooltip' do - = sprite_icon('folder-open', css_class: 'gl-icon') - - - if can?(current_user, :destroy_artifacts, @project) - = link_to project_artifact_path(@project, artifact), data: { placement: 'top', container: 'body', confirm: _('Are you sure you want to delete these artifacts?'), confirm_btn_variant: "danger" }, method: :delete, title: _('Delete artifacts'), ref: 'tooltip', aria: { label: _('Delete artifacts') }, class: 'gl-button btn btn-danger btn-icon has-tooltip' do - = sprite_icon('remove', css_class: 'gl-icon') diff --git a/app/views/projects/artifacts/_table.html.haml b/app/views/projects/artifacts/_table.html.haml deleted file mode 100644 index 1963449d704..00000000000 --- a/app/views/projects/artifacts/_table.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -- if artifacts.blank? - .nothing-here-block= _('No jobs to show') -- else - .table-holder - .ci-table - .gl-responsive-table-row.table-row-header.px-md-3{ role: 'row' } - .table-section.section-25{ role: 'rowheader' }= _('Job') - .table-section.section-15{ role: 'rowheader' }= _('Name') - .table-section.section-20{ role: 'rowheader' }= _('Creation date') - .table-section.section-20{ role: 'rowheader' }= _('Expiration date') - .table-section.section-10{ role: 'rowheader' }= _('Size') - .table-section.section-10{ role: 'rowheader' } - - = render partial: 'artifact', collection: artifacts, as: :artifact - - = paginate artifacts, theme: "gitlab", total_pages: @total_pages diff --git a/app/views/projects/artifacts/index.html.haml b/app/views/projects/artifacts/index.html.haml index 1ab3e8e67d8..9cbc149177c 100644 --- a/app/views/projects/artifacts/index.html.haml +++ b/app/views/projects/artifacts/index.html.haml @@ -1,10 +1,9 @@ -- @no_container = true - page_title _('Artifacts') %div{ class: container_class } - .top-area.py-3 - .align-self-center - = _('Total artifacts size: %{total_size}') % { total_size: number_to_human_size(@total_size, precicion: 2) } - - .content-list.builds-content-list - = render "table", artifacts: @artifacts, project: @project + %h1.page-title.gl-font-size-h-display.gl-mb-0 + = s_('Artifacts|Artifacts') + .gl-mb-6 + %strong= s_('Artifacts|Total artifacts size') + = number_to_human_size(@total_size, precicion: 2) + #js-artifact-management{ data: { "project-path" => @project.full_path } } diff --git a/app/views/shared/issuable/_sidebar_reviewers.html.haml b/app/views/shared/issuable/_sidebar_reviewers.html.haml index 771db8af6a8..c764cfff2fd 100644 --- a/app/views/shared/issuable/_sidebar_reviewers.html.haml +++ b/app/views/shared/issuable/_sidebar_reviewers.html.haml @@ -6,11 +6,7 @@ = gl_loading_icon(inline: true) .selectbox.hide-collapsed - - if reviewers.none? - = hidden_field_tag "#{issuable_type}[reviewer_ids][]", 0, id: nil - - else - - reviewers.each do |reviewer| - = hidden_field_tag "#{issuable_type}[reviewer_ids][]", reviewer.id, id: nil, data: reviewer_sidebar_data(reviewer, merge_request: @merge_request) + .js-reviewers-inputs - options = { toggle_class: 'js-reviewer-search js-author-search', title: _('Request review from'), @@ -32,8 +28,7 @@ - dropdown_options = reviewers_dropdown_options(issuable_type) - title = dropdown_options[:title] - options[:toggle_class] += ' js-multiselect js-save-user-data' - - data = { field_name: "#{issuable_type}[reviewer_ids][]" } - - data[:multi_select] = true + - data = { multi_select: true } - data['dropdown-title'] = title - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header'] - data[:suggested_reviewers_header] = dropdown_options[:data][:suggested_reviewers_header] -- cgit v1.2.3