From 7f35b02e86cd3d2e8b4a81c5c3a8483ff6973c5a Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 14 Nov 2022 21:10:12 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/api/ensure_string_detail.yml | 5 + .rubocop_todo/layout/line_length.yml | 2 - .../javascripts/boards/components/board_list.vue | 3 + .../graphql/board_lists_deferred.query.graphql | 2 +- .../javascripts/sidebar/mount_milestone_sidebar.js | 2 +- app/assets/javascripts/sidebar/mount_sidebar.js | 294 +++++++++------------ .../_close_reopen_draft_report_toggle.html.haml | 4 +- app/views/projects/merge_requests/show.html.haml | 2 +- .../shared/issuable/_bulk_update_sidebar.html.haml | 2 +- .../shared/issuable/_milestone_dropdown.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 28 +- .../shared/issuable/_sidebar_assignees.html.haml | 2 +- .../shared/issuable/_sidebar_reviewers.html.haml | 2 +- app/views/shared/milestones/_sidebar.html.haml | 2 +- config/open_api.yml | 4 + doc/api/openapi/openapi_v2.yaml | 40 +++ doc/development/packages/dependency_proxy.md | 2 +- doc/user/application_security/index.md | 6 +- doc/user/application_security/sast/index.md | 88 +++--- doc/user/project/settings/index.md | 5 +- lib/api/api.rb | 8 +- lib/api/branches.rb | 41 ++- lib/api/entities/branch.rb | 44 ++- lib/api/entities/merge_request_approvals.rb | 6 +- lib/api/entities/package.rb | 20 +- lib/api/entities/user_counts.rb | 25 ++ lib/api/group_avatar.rb | 4 +- lib/api/group_packages.rb | 19 +- lib/api/helpers/merge_requests_helpers.rb | 5 + lib/api/merge_request_approvals.rb | 28 +- lib/api/user_counts.rb | 9 +- locale/gitlab.pot | 13 +- rubocop/cop/api/ensure_string_detail.rb | 51 ++++ spec/lib/api/entities/user_counts_spec.rb | 24 ++ spec/requests/api/user_counts_spec.rb | 26 +- spec/rubocop/cop/api/ensure_string_detail_spec.rb | 136 ++++++++++ spec/support/helpers/test_env.rb | 38 +-- ...se_reopen_draft_report_toggle.html.haml_spec.rb | 4 +- .../projects/merge_requests/edit.html.haml_spec.rb | 4 +- .../shared/issuable/_sidebar.html.haml_spec.rb | 6 +- 40 files changed, 675 insertions(+), 333 deletions(-) create mode 100644 .rubocop_todo/api/ensure_string_detail.yml create mode 100644 lib/api/entities/user_counts.rb create mode 100644 rubocop/cop/api/ensure_string_detail.rb create mode 100644 spec/lib/api/entities/user_counts_spec.rb create mode 100644 spec/rubocop/cop/api/ensure_string_detail_spec.rb diff --git a/.rubocop_todo/api/ensure_string_detail.yml b/.rubocop_todo/api/ensure_string_detail.yml new file mode 100644 index 00000000000..feac99aa569 --- /dev/null +++ b/.rubocop_todo/api/ensure_string_detail.yml @@ -0,0 +1,5 @@ +--- +API/EnsureStringDetail: + Details: grace period + Exclude: + - 'ee/lib/api/analytics/group_activity_analytics.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 9bc4734ed44..a53c450fee3 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2943,7 +2943,6 @@ Layout/LineLength: - 'lib/api/entities/issue_basic.rb' - 'lib/api/entities/merge_request.rb' - 'lib/api/entities/namespace.rb' - - 'lib/api/entities/package.rb' - 'lib/api/entities/project.rb' - 'lib/api/entities/user.rb' - 'lib/api/environments.rb' @@ -2955,7 +2954,6 @@ Layout/LineLength: - 'lib/api/group_clusters.rb' - 'lib/api/group_import.rb' - 'lib/api/group_labels.rb' - - 'lib/api/group_packages.rb' - 'lib/api/group_variables.rb' - 'lib/api/groups.rb' - 'lib/api/helm_packages.rb' diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 6a5e2801de5..816b22e4dc6 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -63,6 +63,9 @@ export default { filters: this.filterParams, }; }, + context: { + isSingleRequest: true, + }, skip() { return this.isEpicBoard; }, diff --git a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql index f48383624c9..d5498f03e4b 100644 --- a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql +++ b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql @@ -1,4 +1,4 @@ -query BoardList($id: ListID!, $filters: BoardIssueInput) { +query BoardListCount($id: ListID!, $filters: BoardIssueInput) { boardList(id: $id, issueFilters: $filters) { id issuesCount diff --git a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js index cc5de5e4083..afce59d304f 100644 --- a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js @@ -5,7 +5,7 @@ import TimeTracker from './components/time_tracking/time_tracker.vue'; export default class SidebarMilestone { constructor() { - const el = document.getElementById('issuable-time-tracker'); + const el = document.querySelector('.js-sidebar-time-tracking-root'); if (!el) return; diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index d078511151b..b37486283ca 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -49,27 +49,24 @@ function getSidebarOptions(sidebarOptEl = document.querySelector('.js-sidebar-op return JSON.parse(sidebarOptEl.innerHTML); } -function mountSidebarToDoWidget() { - const el = document.querySelector('.js-issuable-todo'); +function mountSidebarTodoWidget() { + const el = document.querySelector('.js-sidebar-todo-widget-root'); if (!el) { - return false; + return null; } const { projectPath, iid, id } = el.dataset; return new Vue({ el, - name: 'SidebarTodoRoot', + name: 'SidebarTodoWidgetRoot', apolloProvider, - components: { - SidebarTodoWidget, - }, provide: { isClassicSidebar: true, }, render: (createElement) => - createElement('sidebar-todo-widget', { + createElement(SidebarTodoWidget, { props: { fullPath: projectPath, issuableId: @@ -99,23 +96,22 @@ function getSidebarAssigneeAvailabilityData() { ); } -function mountAssigneesComponentDeprecated(mediator) { - const el = document.getElementById('js-vue-sidebar-assignees'); +function mountSidebarAssigneesDeprecated(mediator) { + const el = document.querySelector('.js-sidebar-assignees-root'); - if (!el) return; + if (!el) { + return null; + } const { id, iid, fullPath } = getSidebarOptions(); const assigneeAvailabilityStatus = getSidebarAssigneeAvailabilityData(); - // eslint-disable-next-line no-new - new Vue({ + + return new Vue({ el, name: 'SidebarAssigneesRoot', apolloProvider, - components: { - SidebarAssignees, - }, render: (createElement) => - createElement('sidebar-assignees', { + createElement(SidebarAssignees, { props: { mediator, issuableIid: String(iid), @@ -133,10 +129,12 @@ function mountAssigneesComponentDeprecated(mediator) { }); } -function mountAssigneesComponent() { - const el = document.getElementById('js-vue-sidebar-assignees'); +function mountSidebarAssigneesWidget() { + const el = document.querySelector('.js-sidebar-assignees-root'); - if (!el) return; + if (!el) { + return; + } const { id, iid, fullPath, editable } = getSidebarOptions(); const isIssuablePage = isInIssuePage() || isInIncidentPage() || isInDesignPage(); @@ -146,9 +144,6 @@ function mountAssigneesComponent() { el, name: 'SidebarAssigneesRoot', apolloProvider, - components: { - SidebarAssigneesWidget, - }, provide: { canUpdate: editable, directlyInviteMembers: Object.prototype.hasOwnProperty.call( @@ -157,7 +152,7 @@ function mountAssigneesComponent() { ), }, render: (createElement) => - createElement('sidebar-assignees-widget', { + createElement(SidebarAssigneesWidget, { props: { iid: String(iid), fullPath, @@ -185,10 +180,12 @@ function mountAssigneesComponent() { } } -function mountReviewersComponent(mediator) { - const el = document.getElementById('js-vue-sidebar-reviewers'); +function mountSidebarReviewers(mediator) { + const el = document.querySelector('.js-sidebar-reviewers-root'); - if (!el) return; + if (!el) { + return; + } const { iid, fullPath } = getSidebarOptions(); // eslint-disable-next-line no-new @@ -196,11 +193,8 @@ function mountReviewersComponent(mediator) { el, name: 'SidebarReviewersRoot', apolloProvider, - components: { - SidebarReviewers, - }, render: (createElement) => - createElement('sidebar-reviewers', { + createElement(SidebarReviewers, { props: { mediator, issuableIid: String(iid), @@ -231,22 +225,21 @@ function mountReviewersComponent(mediator) { } } -function mountCrmContactsComponent() { - const el = document.getElementById('js-issue-crm-contacts'); +function mountSidebarCrmContacts() { + const el = document.querySelector('.js-sidebar-crm-contacts-root'); - if (!el) return; + if (!el) { + return null; + } const { issueId, groupIssuesPath } = el.dataset; - // eslint-disable-next-line no-new - new Vue({ + + return new Vue({ el, name: 'SidebarCrmContactsRoot', apolloProvider, - components: { - CrmContacts, - }, render: (createElement) => - createElement('crm-contacts', { + createElement(CrmContacts, { props: { issueId, groupIssuesPath, @@ -255,28 +248,25 @@ function mountCrmContactsComponent() { }); } -function mountMilestoneSelect() { - const el = document.querySelector('.js-milestone-select'); +function mountSidebarMilestoneWidget() { + const el = document.querySelector('.js-sidebar-milestone-widget-root'); if (!el) { - return false; + return null; } const { canEdit, projectPath, issueIid } = el.dataset; return new Vue({ el, - name: 'SidebarMilestoneRoot', + name: 'SidebarMilestoneWidgetRoot', apolloProvider, - components: { - SidebarDropdownWidget, - }, provide: { canUpdate: parseBoolean(canEdit), isClassicSidebar: true, }, render: (createElement) => - createElement('sidebar-dropdown-widget', { + createElement(SidebarDropdownWidget, { props: { attrWorkspacePath: projectPath, workspacePath: projectPath, @@ -291,7 +281,7 @@ function mountMilestoneSelect() { } export function mountMilestoneDropdown() { - const el = document.querySelector('.js-milestone-dropdown'); + const el = document.querySelector('.js-milestone-dropdown-root'); if (!el) { return null; @@ -330,21 +320,17 @@ export function mountMilestoneDropdown() { }); } -export function mountSidebarLabels() { - const el = document.querySelector('.js-sidebar-labels'); +export function mountSidebarLabelsWidget() { + const el = document.querySelector('.js-sidebar-labels-widget-root'); if (!el) { - return false; + return null; } return new Vue({ el, - name: 'SidebarLabelsRoot', + name: 'SidebarLabelsWidgetRoot', apolloProvider, - - components: { - LabelsSelectWidget, - }, provide: { ...el.dataset, canUpdate: parseBoolean(el.dataset.canEdit), @@ -354,7 +340,7 @@ export function mountSidebarLabels() { isClassicSidebar: true, }, render: (createElement) => - createElement('labels-select-widget', { + createElement(LabelsSelectWidget, { props: { iid: String(el.dataset.iid), fullPath: el.dataset.projectPath, @@ -381,31 +367,27 @@ export function mountSidebarLabels() { }); } -function mountConfidentialComponent() { - const el = document.getElementById('js-confidential-entry-point'); +function mountSidebarConfidentialityWidget() { + const el = document.querySelector('.js-sidebar-confidential-widget-root'); + if (!el) { - return; + return null; } const { fullPath, iid } = getSidebarOptions(); const dataNode = document.getElementById('js-confidential-issue-data'); const initialData = JSON.parse(dataNode.innerHTML); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, - name: 'SidebarConfidentialRoot', + name: 'SidebarConfidentialityWidgetRoot', apolloProvider, - components: { - SidebarConfidentialityWidget, - }, provide: { canUpdate: initialData.is_editable, isClassicSidebar: true, }, - render: (createElement) => - createElement('sidebar-confidentiality-widget', { + createElement(SidebarConfidentialityWidget, { props: { iid: String(iid), fullPath, @@ -418,28 +400,24 @@ function mountConfidentialComponent() { }); } -function mountDueDateComponent() { - const el = document.getElementById('js-due-date-entry-point'); +function mountSidebarDueDateWidget() { + const el = document.querySelector('.js-sidebar-due-date-widget-root'); + if (!el) { - return; + return null; } const { fullPath, iid, editable } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, - name: 'SidebarDueDateRoot', + name: 'SidebarDueDateWidgetRoot', apolloProvider, - components: { - SidebarDueDateWidget, - }, provide: { canUpdate: editable, }, - render: (createElement) => - createElement('sidebar-due-date-widget', { + createElement(SidebarDueDateWidget, { props: { iid: String(iid), fullPath, @@ -449,29 +427,25 @@ function mountDueDateComponent() { }); } -function mountReferenceComponent() { - const el = document.getElementById('js-reference-entry-point'); +function mountSidebarReferenceWidget() { + const el = document.querySelector('.js-sidebar-reference-widget-root'); + if (!el) { - return; + return null; } const { fullPath, iid } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, - name: 'SidebarReferenceRoot', + name: 'SidebarReferenceWidgetRoot', apolloProvider, - components: { - SidebarReferenceWidget, - }, provide: { iid: String(iid), fullPath, }, - render: (createElement) => - createElement('sidebar-reference-widget', { + createElement(SidebarReferenceWidget, { props: { issuableType: isInIssuePage() || isInIncidentPage() || isInDesignPage() @@ -482,17 +456,16 @@ function mountReferenceComponent() { }); } -function mountLockComponent(store) { - const el = document.getElementById('js-lock-entry-point'); +function mountIssuableLockForm(store) { + const el = document.querySelector('.js-sidebar-lock-root'); if (!el || !store) { - return; + return null; } const { fullPath, editable } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, name: 'SidebarLockRoot', store, @@ -508,23 +481,21 @@ function mountLockComponent(store) { }); } -function mountParticipantsComponent() { - const el = document.querySelector('.js-sidebar-participants-entry-point'); +function mountSidebarParticipantsWidget() { + const el = document.querySelector('.js-sidebar-participants-widget-root'); - if (!el) return; + if (!el) { + return null; + } const { fullPath, iid } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, - name: 'SidebarParticipantsRoot', + name: 'SidebarParticipantsWidgetRoot', apolloProvider, - components: { - SidebarParticipantsWidget, - }, render: (createElement) => - createElement('sidebar-participants-widget', { + createElement(SidebarParticipantsWidget, { props: { iid: String(iid), fullPath, @@ -537,26 +508,24 @@ function mountParticipantsComponent() { }); } -function mountSubscriptionsComponent() { - const el = document.querySelector('.js-sidebar-subscriptions-entry-point'); +function mountSidebarSubscriptionsWidget() { + const el = document.querySelector('.js-sidebar-subscriptions-widget-root'); - if (!el) return; + if (!el) { + return null; + } const { fullPath, iid, editable } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, - name: 'SidebarSubscriptionsRoot', + name: 'SidebarSubscriptionsWidgetRoot', apolloProvider, - components: { - SidebarSubscriptionsWidget, - }, provide: { canUpdate: editable, }, render: (createElement) => - createElement('sidebar-subscriptions-widget', { + createElement(SidebarSubscriptionsWidget, { props: { iid: String(iid), fullPath, @@ -569,14 +538,15 @@ function mountSubscriptionsComponent() { }); } -function mountTimeTrackingComponent() { - const el = document.getElementById('issuable-time-tracker'); +function mountSidebarTimeTracking() { + const el = document.querySelector('.js-sidebar-time-tracking-root'); const { id, iid, fullPath, issuableType, timeTrackingLimitToHours } = getSidebarOptions(); - if (!el) return; + if (!el) { + return null; + } - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, name: 'SidebarTimeTrackingRoot', apolloProvider, @@ -593,27 +563,24 @@ function mountTimeTrackingComponent() { }); } -function mountSeverityComponent() { - const severityContainerEl = document.querySelector('#js-severity'); +function mountSidebarSeverity() { + const el = document.querySelector('.js-sidebar-severity-root'); - if (!severityContainerEl) { - return false; + if (!el) { + return null; } const { fullPath, iid, severity, editable } = getSidebarOptions(); return new Vue({ - el: severityContainerEl, + el, name: 'SidebarSeverityRoot', apolloProvider, - components: { - SidebarSeverity, - }, provide: { canUpdate: editable, }, render: (createElement) => - createElement('sidebar-severity', { + createElement(SidebarSeverity, { props: { projectPath: fullPath, iid: String(iid), @@ -623,27 +590,25 @@ function mountSeverityComponent() { }); } -function mountEscalationStatusComponent() { - const statusContainerEl = document.querySelector('#js-escalation-status'); +function mountSidebarEscalationStatus() { + const el = document.querySelector('.js-sidebar-escalation-status-root'); - if (!statusContainerEl) { - return false; + if (!el) { + return null; } const { issuableType } = getSidebarOptions(); - const { canUpdate, issueIid, projectPath } = statusContainerEl.dataset; + const { canUpdate, issueIid, projectPath } = el.dataset; return new Vue({ - el: statusContainerEl, + el, + name: 'SidebarEscalationStatusRoot', apolloProvider, - components: { - SidebarEscalationStatus, - }, provide: { canUpdate: parseBoolean(canUpdate), }, render: (createElement) => - createElement('sidebar-escalation-status', { + createElement(SidebarEscalationStatus, { props: { iid: issueIid, issuableType, @@ -653,15 +618,16 @@ function mountEscalationStatusComponent() { }); } -function mountCopyEmailComponent() { - const el = document.getElementById('issuable-copy-email'); +function mountCopyEmailToClipboard() { + const el = document.querySelector('.js-sidebar-copy-email-root'); - if (!el) return; + if (!el) { + return null; + } const { createNoteEmail } = getSidebarOptions(); - // eslint-disable-next-line no-new - new Vue({ + return new Vue({ el, name: 'SidebarCopyEmailRoot', render: (createElement) => @@ -675,36 +641,32 @@ const isAssigneesWidgetShown = export function mountSidebar(mediator, store) { initInviteMembersModal(); initInviteMembersTrigger(); - - mountSidebarToDoWidget(); + mountSidebarTodoWidget(); if (isAssigneesWidgetShown) { - mountAssigneesComponent(); + mountSidebarAssigneesWidget(); } else { - mountAssigneesComponentDeprecated(mediator); + mountSidebarAssigneesDeprecated(mediator); } - mountReviewersComponent(mediator); - mountCrmContactsComponent(); - mountSidebarLabels(); - mountMilestoneSelect(); - mountConfidentialComponent(mediator); - mountDueDateComponent(mediator); - mountReferenceComponent(mediator); - mountLockComponent(store); - mountParticipantsComponent(); - mountSubscriptionsComponent(); - mountCopyEmailComponent(); + mountSidebarReviewers(mediator); + mountSidebarCrmContacts(); + mountSidebarLabelsWidget(); + mountSidebarMilestoneWidget(); + mountSidebarConfidentialityWidget(); + mountSidebarDueDateWidget(); + mountSidebarReferenceWidget(); + mountIssuableLockForm(store); + mountSidebarParticipantsWidget(); + mountSidebarSubscriptionsWidget(); + mountCopyEmailToClipboard(); + mountSidebarTimeTracking(); + mountSidebarSeverity(); + mountSidebarEscalationStatus(); new SidebarMoveIssue( mediator, $('.js-move-issue'), $('.js-move-issue-confirmation-button'), ).init(); - - mountTimeTrackingComponent(); - - mountSeverityComponent(); - - mountEscalationStatusComponent(); } export { getSidebarOptions }; diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml index a2b6e6213e0..78fce3f7087 100644 --- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml +++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml @@ -11,7 +11,7 @@ .gl-new-dropdown-contents %ul - if current_user && moved_mr_sidebar_enabled? - %li.gl-new-dropdown-item.js-sidebar-subscriptions-entry-point + %li.gl-new-dropdown-item.js-sidebar-subscriptions-widget-root %li.gl-new-dropdown-divider %hr.dropdown-divider - if can?(current_user, :update_merge_request, @merge_request) @@ -36,7 +36,7 @@ = _('Reopen') = display_issuable_type - if moved_mr_sidebar_enabled? - %li.gl-new-dropdown-item#js-lock-entry-point + %li.gl-new-dropdown-item.js-sidebar-lock-root %li.gl-new-dropdown-item %button.dropdown-item.js-copy-reference{ type: "button", data: { 'clipboard-text': @merge_request.to_reference(full: true) } } .gl-new-dropdown-item-text-wrapper diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 615aac340bc..64a7f068eff 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -48,7 +48,7 @@ #js-vue-discussion-counter{ data: { blocks_merge: @project.only_allow_merge_if_all_discussions_are_resolved?.to_s } } - if moved_mr_sidebar_enabled? - if !!@issuable_sidebar.dig(:current_user, :id) - .js-issuable-todo{ data: { project_path: @issuable_sidebar[:project_full_path], iid: @issuable_sidebar[:iid], id: @issuable_sidebar[:id] } } + .js-sidebar-todo-widget-root{ data: { project_path: @issuable_sidebar[:project_full_path], iid: @issuable_sidebar[:iid], id: @issuable_sidebar[:id] } } .gl-ml-auto.gl-align-items-center.gl-display-none.gl-md-display-flex.gl-ml-3.js-expand-sidebar{ class: "gl-lg-display-none!" } = render Pajamas::ButtonComponent.new(icon: 'chevron-double-lg-left', button_options: { class: 'js-sidebar-toggle' }) do diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml index 843d2462851..da8477f4b2e 100644 --- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml +++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml @@ -34,7 +34,7 @@ .title = _('Milestone') .filter-item - .js-milestone-dropdown{ data: { full_path: @project.full_path, workspace_type: Namespaces::ProjectNamespace.sti_name.downcase } } + .js-milestone-dropdown-root{ data: { full_path: @project.full_path, workspace_type: Namespaces::ProjectNamespace.sti_name.downcase } } - if is_issue = render_if_exists 'shared/issuable/iterations_dropdown', parent: @project.group - if is_issue diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 6984709e735..a02d2851c4c 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,7 +2,7 @@ - project = @target_project || @project - selected = local_assigns.fetch(:selected, nil) -.js-milestone-dropdown{ data: { can_admin_milestone: can?(current_user, :admin_milestone, project), +.js-milestone-dropdown-root{ data: { can_admin_milestone: can?(current_user, :admin_milestone, project), full_path: project.full_path, input_name: name, milestone_id: selected.try(:id), diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index daac65b2722..0fd128df997 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -17,14 +17,14 @@ %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", class: "#{'gl-display-block' if moved_sidebar_enabled}", href: "#", "aria-label" => _('Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } = sidebar_gutter_toggle_icon - if signed_in && !moved_sidebar_enabled - .js-issuable-todo{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } + .js-sidebar-todo-widget-root{ data: { project_path: issuable_sidebar[:project_full_path], iid: issuable_sidebar[:iid], id: issuable_sidebar[:id] } } = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| .block.assignee{ class: "#{'gl-mt-3' if !signed_in && moved_sidebar_enabled}", data: { qa_selector: 'assignee_block_container' } } = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - if issuable_sidebar[:supports_severity] - #js-severity + .js-sidebar-severity-root - if reviewers .block.reviewer{ data: { qa_selector: 'reviewers_block_container' } } @@ -32,17 +32,17 @@ - if issuable_sidebar[:supports_escalation] .block.escalation-status{ data: { testid: 'escalation_status_container' } } - #js-escalation-status{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + .js-sidebar-escalation-status-root{ data: { can_update: issuable_sidebar.dig(:current_user, :can_update_escalation_status).to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } = render_if_exists 'shared/issuable/sidebar_escalation_policy', issuable_sidebar: issuable_sidebar - if @project.group.present? = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type - .js-sidebar-labels{ data: sidebar_labels_data(issuable_sidebar, @project) } + .js-sidebar-labels-widget-root{ data: sidebar_labels_data(issuable_sidebar, @project) } - if issuable_sidebar[:supports_milestone] .block.milestone{ :class => ("gl-border-b-0!" if in_group_context_with_iterations), data: { qa_selector: 'milestone_block', testid: 'sidebar-milestones' } } - .js-milestone-select{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } + .js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - if in_group_context_with_iterations .block.gl-collapse-empty{ data: { qa_selector: 'iteration_container', testid: 'iteration_container' } }< @@ -50,15 +50,15 @@ - if issuable_sidebar[:show_crm_contacts] .block.contact - #js-issue-crm-contacts{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } + .js-sidebar-crm-contacts-root{ data: { issue_id: issuable_sidebar[:id], group_issues_path: issues_group_path(@project.group) } } = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar, can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] - if issuable_sidebar.has_key?(:due_date) - #js-due-date-entry-point + .js-sidebar-due-date-widget-root - if issuable_sidebar[:supports_time_tracking] - #issuable-time-tracker.block + .js-sidebar-time-tracking-root.block // Fallback while content is loading .title.hide-collapsed = _('Time tracking') @@ -70,20 +70,20 @@ - if issuable_sidebar.has_key?(:confidential) -# haml-lint:disable InlineJavaScript %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe - #js-confidential-entry-point + .js-sidebar-confidential-widget-root = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar - if !moved_sidebar_enabled - #js-lock-entry-point + .js-sidebar-lock-root - if signed_in - .js-sidebar-subscriptions-entry-point + .js-sidebar-subscriptions-widget-root - .js-sidebar-participants-entry-point + .js-sidebar-participants-widget-root .block.with-sub-blocks - if !moved_sidebar_enabled - #js-reference-entry-point + .js-sidebar-reference-widget-root - if issuable_type == 'merge_request' && !moved_sidebar_enabled .sub-block.js-sidebar-source-branch .sidebar-collapsed-icon.js-dont-change-state @@ -95,7 +95,7 @@ - if show_forwarding_email .block - #issuable-copy-email + .js-sidebar-copy-email-root - if issuable_sidebar.dig(:current_user, :can_move) .block.js-sidebar-move-issue-block .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') } diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index 62221fb8218..b8a9a82d9b2 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -1,7 +1,7 @@ - issuable_type = issuable_sidebar[:type] - dropdown_options = assignees_dropdown_options(issuable_type) -#js-vue-sidebar-assignees{ data: { field: issuable_type, +.js-sidebar-assignees-root{ data: { field: issuable_type, signed_in: signed_in, max_assignees: dropdown_options[:data][:"max-select"], directly_invite_members: can_admin_project_member?(@project) } } diff --git a/app/views/shared/issuable/_sidebar_reviewers.html.haml b/app/views/shared/issuable/_sidebar_reviewers.html.haml index 6bbd522a43b..4df393eeb67 100644 --- a/app/views/shared/issuable/_sidebar_reviewers.html.haml +++ b/app/views/shared/issuable/_sidebar_reviewers.html.haml @@ -1,6 +1,6 @@ - issuable_type = issuable_sidebar[:type] -#js-vue-sidebar-reviewers{ data: { field: issuable_type, signed_in: signed_in } } +.js-sidebar-reviewers-root{ data: { field: issuable_type, signed_in: signed_in } } .title.hide-collapsed = _('Reviewers') = gl_loading_icon(inline: true) diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 6a65909b1c2..cc1965945ac 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -94,7 +94,7 @@ = milestone.issues_visible_to_user(current_user).closed.count .block - #issuable-time-tracker{ data: { time_estimate: @milestone.total_time_estimate, + .js-sidebar-time-tracking-root{ data: { time_estimate: @milestone.total_time_estimate, time_spent: @milestone.total_time_spent, human_time_estimate: @milestone.human_total_time_estimate, human_time_spent: @milestone.human_total_time_spent, diff --git a/config/open_api.yml b/config/open_api.yml index dc01b2730c3..41036a26291 100644 --- a/config/open_api.yml +++ b/config/open_api.yml @@ -47,10 +47,14 @@ metadata: description: Operations related to deploy freeze periods - name: ci_lint description: Operations related to linting a CI config file + - name: geo_nodes + description: Operations related Geo Nodes - name: group_export description: Operations related to exporting groups - name: group_import description: Operations related to importing groups + - name: group_packages + description: Operations related to group packages - name: merge_requests description: Operations related to merge requests - name: metadata diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index a3c1934bf2a..5a99f6c8793 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -16,6 +16,8 @@ securityDefinitions: in: query host: gitlab.com tags: +- name: user_counts + description: Operations about user_counts - name: metadata description: Operations related to metadata of the GitLab instance - name: access_requests @@ -289,6 +291,20 @@ paths: tags: - access_requests operationId: getApiV4ProjectsIdAccessRequests + "/api/v4/user_counts": + get: + summary: Return the user specific counts + description: Assigned open issues, assigned MRs and pending todos count + produces: + - application/json + responses: + '200': + description: Return the user specific counts + schema: + "$ref": "#/definitions/API_Entities_UserCounts" + tags: + - user_counts + operationId: getApiV4UserCounts "/api/v4/metadata": get: summary: Retrieve metadata information for this GitLab instance. @@ -374,6 +390,30 @@ definitions: type: string value: type: string + API_Entities_UserCounts: + type: object + properties: + merge_requests: + type: integer + format: int32 + example: 10 + assigned_issues: + type: integer + format: int32 + example: 10 + assigned_merge_requests: + type: integer + format: int32 + example: 10 + review_requested_merge_requests: + type: integer + format: int32 + example: 10 + todos: + type: integer + format: int32 + example: 10 + description: API_Entities_UserCounts model API_Entities_Metadata: type: object properties: diff --git a/doc/development/packages/dependency_proxy.md b/doc/development/packages/dependency_proxy.md index 0996c8be3e6..d5cc219cba0 100644 --- a/doc/development/packages/dependency_proxy.md +++ b/doc/development/packages/dependency_proxy.md @@ -130,7 +130,7 @@ graph TD A[Receive manifest request] --> | We have the manifest cached.| B{Docker manifest HEAD request} A --> | We do not have manifest cached.| C{Docker manifest GET request} B --> | Digest matches the one in the DB | D[Fetch manifest from cache] - B --> | Network failure, cannot reach DockerHub | D[Fetch manifest from cache] + B --> | HEAD request error, network failure, cannot reach DockerHub | D[Fetch manifest from cache] B --> | Digest does not match the one in DB | C C --> E[Save manifest to cache, save digest to database] D --> F diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 30db267d891..f58c146f773 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -499,8 +499,8 @@ GitLab provides two methods of accomplishing this, each with advantages and disa are recommended when: - Scan execution enforcement is required for DAST which uses a DAST site or scan profile. - - Scan execution enforcement is required for SAST, Secret Detection, or Container Scanning with project-specific variable - customizations. To accomplish this, users must create a separate security policy per project. + - Scan execution enforcement is required for SAST, Secret Detection, Dependency Scanning, or Container Scanning with project-specific +variable customizations. To accomplish this, users must create a separate security policy per project. - Scans are required to run on a regular, scheduled cadence. - Either solution can be used equally well when: @@ -514,7 +514,7 @@ Additional details about the differences between the two solutions are outlined | | Compliance Framework Pipelines | Scan Execution Policies | | ------ | ------ | ------ | -| **Flexibility** | Supports anything that can be done in a CI file. | Limited to only the items for which GitLab has explicitly added support. DAST, SAST, Secret Detection, and Container Scanning scans are supported. | +| **Flexibility** | Supports anything that can be done in a CI file. | Limited to only the items for which GitLab has explicitly added support. DAST, SAST, Secret Detection, Dependency Scanning, and Container Scanning scans are supported. | | **Usability** | Requires knowledge of CI YAML. | Follows a `rules` and `actions`-based YAML structure. | | **Inclusion in CI pipeline** | The compliance pipeline is executed instead of the project's `.gitlab-ci.yml` file. To include the project's `.gitlab-ci.yml` file, use an `include` statement. Defined variables aren't allowed to be overwritten by the included project's YAML file. | Forced inclusion of a new job into the CI pipeline. DAST jobs that must be customized on a per-project basis can have project-level Site Profiles and Scan Profiles defined. To ensure separation of duties, these profiles are immutable when referenced in a scan execution policy. All jobs can be customized as part of the security policy itself with the same variables that are normally available to the CI job. | | **Schedulable** | Can be scheduled through a scheduled pipeline on the group. | Can be scheduled natively through the policy configuration itself. | diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index ffb9dea4aaa..b1bc9794ced 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -117,7 +117,7 @@ Check the [SAST direction page](https://about.gitlab.com/direction/secure/static and the [Maven wrapper](https://github.com/takari/maven-wrapper). However, SpotBugs has [limitations](https://gitlab.com/gitlab-org/gitlab/-/issues/350801) when used against [Ant](https://ant.apache.org/)-based projects. We recommend using the Semgrep-based analyzer for Ant-based Java projects. 1. These analyzers reached [End of Support](https://about.gitlab.com/handbook/product/gitlab-the-product/#end-of-support) status [in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/352554). -### Multi-project support +## Multi-project support > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4895) in GitLab 13.7. @@ -137,16 +137,52 @@ The following analyzers have multi-project support: - SpotBugs - Sobelow -#### Enable multi-project support for Security Code Scan +### Enable multi-project support for Security Code Scan Multi-project support in the Security Code Scan requires a Solution (`.sln`) file in the root of the repository. For details on the Solution format, see the Microsoft reference [Solution (`.sln`) file](https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). -### Supported distributions +## False positive detection **(ULTIMATE)** + +> Introduced in GitLab 14.2. + +Vulnerabilities that have been detected and are false positives will be flagged as false positives in the security dashboard. + +False positive detection is available in a subset of the [supported languages](#supported-languages-and-frameworks) and [analyzers](analyzers.md): + +- Ruby, in the Brakeman-based analyzer + +![SAST false-positives show in Vulnerability Pages](img/sast_vulnerability_page_fp_detection_v15_2.png) + +## Advanced vulnerability tracking **(ULTIMATE)** + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5144) in GitLab 14.2. + +Source code is volatile; as developers make changes, source code may move within files or between files. +Security analyzers may have already reported vulnerabilities that are being tracked in the [Vulnerability Report](../vulnerability_report/index.md). +These vulnerabilities are linked to specific problematic code fragments so that they can be found and fixed. +If the code fragments are not tracked reliably as they move, vulnerability management is harder because the same vulnerability could be reported again. + +GitLab SAST uses an advanced vulnerability tracking algorithm to more accurately identify when the same vulnerability has moved within a file due to refactoring or unrelated changes. + +Advanced vulnerability tracking is available in a subset of the [supported languages](#supported-languages-and-frameworks) and [analyzers](analyzers.md): + +- C, in the Semgrep-based analyzer only +- Go, in the Gosec- and Semgrep-based analyzers +- Java, in the Semgrep-based analyzer only +- JavaScript, in the Semgrep-based analyzer only +- Python, in the Semgrep-based analyzer only +- Ruby, in the Brakeman-based analyzer + +Support for more languages and analyzers is tracked in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/5144). + +For more information, see the confidential project `https://gitlab.com/gitlab-org/security-products/post-analyzers/tracking-calculator`. The content of this project is available only to GitLab team members. + +## Supported distributions The default scanner images are build off a base Alpine image for size and maintainability. -#### FIPS-enabled images +### FIPS-enabled images > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6479) in GitLab 14.10. @@ -170,11 +206,7 @@ A FIPS-compliant image is only available for the Semgrep-based analyzer. To use SAST in a FIPS-compliant manner, you must [exclude other analyzers from running](analyzers.md#customize-analyzers). -### Making SAST analyzers available to all GitLab tiers - -All open source (OSS) analyzers have been moved to the GitLab Free tier as of GitLab 13.3. - -#### Summary of features per tier +## Summary of features per tier Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table: @@ -302,7 +334,7 @@ spotbugs-sast: FAIL_NEVER: 1 ``` -#### Pinning to minor image version +### Pinning to minor image version The GitLab-managed CI/CD template specifies a major version and automatically pulls the latest analyzer release within that major version. @@ -336,42 +368,6 @@ brakeman-sast: SAST_ANALYZER_IMAGE_TAG: "3.1.1" ``` -### False Positive Detection **(ULTIMATE)** - -> Introduced in GitLab 14.2. - -Vulnerabilities that have been detected and are false positives will be flagged as false positives in the security dashboard. - -False positive detection is available in a subset of the [supported languages](#supported-languages-and-frameworks) and [analyzers](analyzers.md): - -- Ruby, in the Brakeman-based analyzer - -![SAST false-positives show in Vulnerability Pages](img/sast_vulnerability_page_fp_detection_v15_2.png) - -### Advanced vulnerability tracking **(ULTIMATE)** - -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5144) in GitLab 14.2. - -Source code is volatile; as developers make changes, source code may move within files or between files. -Security analyzers may have already reported vulnerabilities that are being tracked in the [Vulnerability Report](../vulnerability_report/index.md). -These vulnerabilities are linked to specific problematic code fragments so that they can be found and fixed. -If the code fragments are not tracked reliably as they move, vulnerability management is harder because the same vulnerability could be reported again. - -GitLab SAST uses an advanced vulnerability tracking algorithm to more accurately identify when the same vulnerability has moved within a file due to refactoring or unrelated changes. - -Advanced vulnerability tracking is available in a subset of the [supported languages](#supported-languages-and-frameworks) and [analyzers](analyzers.md): - -- C, in the Semgrep-based analyzer only -- Go, in the Gosec- and Semgrep-based analyzers -- Java, in the Semgrep-based analyzer only -- JavaScript, in the Semgrep-based analyzer only -- Python, in the Semgrep-based analyzer only -- Ruby, in the Brakeman-based analyzer - -Support for more languages and analyzers is tracked in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/5144). - -For more information, see the confidential project `https://gitlab.com/gitlab-org/security-products/post-analyzers/tracking-calculator`. The content of this project is available only to GitLab team members. - ### Using CI/CD variables to pass credentials for private repositories Some analyzers require downloading the project's dependencies to diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 7eb2c649f84..a872a339433 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -91,9 +91,12 @@ Use the toggles to enable or disable features in the project. | **Wiki** | ✓ | Enables a separate system for [documentation](../wiki/index.md). | | **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md). | | **Pages** | ✓ | Allows you to [publish static websites](../pages/index.md). | -| **Operations** | ✓ | Control access to Operations-related features, including [Operations Dashboard](../../../operations/index.md), [Environments and Deployments](../../../ci/environments/index.md), [Feature Flags](../../../operations/feature_flags.md). | | **Metrics Dashboard** | ✓ | Control access to [metrics dashboard](../integrations/prometheus.md). | | **Releases** | ✓ | Control access to [Releases](../releases/index.md). | +| **Environments** | ✓ | Control access to [Environments and Deployments](../../../ci/environments/index.md). | +| **Feature flags** | ✓ | Control access to [Feature Flags](../../../operations/feature_flags.md). | +| **Monitor** | ✓ | Control access to [Monitor](../../../operations/index.md) features. | +| **Infrastructure** | ✓ | Control access to [Infrastructure](../../infrastructure/index.md) features. | When you disable a feature, the following additional features are also disabled: diff --git a/lib/api/api.rb b/lib/api/api.rb index 9d2e5f545d2..3857979b67b 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -176,6 +176,7 @@ module API mount ::API::Appearance mount ::API::Applications mount ::API::Badges + mount ::API::Branches mount ::API::BroadcastMessages mount ::API::BulkImports mount ::API::Ci::Jobs @@ -199,15 +200,18 @@ module API mount ::API::Features mount ::API::Files mount ::API::FreezePeriods + mount ::API::GroupAvatar mount ::API::GroupClusters mount ::API::GroupExport mount ::API::GroupImport + mount ::API::GroupPackages mount ::API::GroupVariables mount ::API::ImportBitbucketServer mount ::API::ImportGithub mount ::API::Invitations mount ::API::Keys mount ::API::Lint + mount ::API::MergeRequestApprovals mount ::API::MergeRequestDiffs mount ::API::Metadata mount ::API::Metrics::UserStarredDashboards @@ -256,7 +260,6 @@ module API mount ::API::Avatar mount ::API::AwardEmoji mount ::API::Boards - mount ::API::Branches mount ::API::Ci::JobArtifacts mount ::API::Ci::Pipelines mount ::API::Ci::PipelineSchedules @@ -278,13 +281,11 @@ module API mount ::API::GenericPackages mount ::API::Geo mount ::API::GoProxy - mount ::API::GroupAvatar mount ::API::GroupBoards mount ::API::GroupContainerRepositories mount ::API::GroupDebianDistributions mount ::API::GroupLabels mount ::API::GroupMilestones - mount ::API::GroupPackages mount ::API::Groups mount ::API::HelmPackages mount ::API::Integrations @@ -295,7 +296,6 @@ module API mount ::API::Markdown mount ::API::MavenPackages mount ::API::Members - mount ::API::MergeRequestApprovals mount ::API::MergeRequests mount ::API::Metrics::Dashboard::Annotations mount ::API::Namespaces diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 51c9a4e53df..a7418deb88a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -34,12 +34,16 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a project repository branches' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Project Not Found' }] + tags %w[branches] + is_array true end params do use :pagination use :filter_params - optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' + optional :page_token, type: String, desc: 'Name of branch to start the pagination from' end get ':id/repository/branches', urgency: :low do cache_action([user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do @@ -65,15 +69,23 @@ module API end resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do - desc 'Get a single branch' do - success Entities::Branch - end params do requires :branch, type: String, desc: 'The name of the branch' end + desc 'Check if a branch exists' do + success [{ code: 204, message: 'No Content' }] + failure [{ code: 404, message: 'Not Found' }] + tags %w[branches] + end head do user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found! end + desc 'Get a single repository branch' do + success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: 'Branch Not Found' }, { code: 404, message: 'Project Not Found' }] + tags %w[branches] + end get '/', urgency: :low do branch = find_branch!(params[:branch]) @@ -87,6 +99,9 @@ module API # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. desc 'Protect a single branch' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Branch Not Found' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -126,6 +141,9 @@ module API # Note: This API will be deprecated in favor of the protected branches API. desc 'Unprotect a single branch' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Project Not Found' }, { code: 404, message: '404 Branch Not Found' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -145,6 +163,9 @@ module API desc 'Create branch' do success Entities::Branch + success code: 201, model: Entities::Branch + failure [{ code: 400, message: 'Failed to create branch' }, { code: 400, message: 'Branch already exists' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -166,7 +187,11 @@ module API end end - desc 'Delete a branch' + desc 'Delete a branch' do + success code: 204 + failure [{ code: 404, message: 'Branch Not Found' }] + tags %w[branches] + end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false end @@ -187,7 +212,11 @@ module API end end - desc 'Delete all merged branches' + desc 'Delete all merged branches' do + success code: 202, message: '202 Accepted' + failure [{ code: 404, message: '404 Project Not Found' }] + tags %w[branches] + end delete ':id/repository/merged_branches' do ::Branches::DeleteMergedService.new(user_project, current_user).async_execute diff --git a/lib/api/entities/branch.rb b/lib/api/entities/branch.rb index 6a75dcddeda..01eaf5c8d31 100644 --- a/lib/api/entities/branch.rb +++ b/lib/api/entities/branch.rb @@ -5,13 +5,17 @@ module API class Branch < Grape::Entity include Gitlab::Routing - expose :name + expose :name, documentation: { type: 'string', example: 'master' } expose :commit, using: Entities::Commit do |repo_branch, options| options[:project].repository.commit(repo_branch.dereferenced_target) end - expose :merged do |repo_branch, options| + expose :merged, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| if options[:merged_branch_names] options[:merged_branch_names].include?(repo_branch.name) else @@ -19,27 +23,51 @@ module API end end - expose :protected do |repo_branch, options| + expose :protected, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.protected?(options[:project], repo_branch.name) end - expose :developers_can_push do |repo_branch, options| + expose :developers_can_push, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.developers_can?(:push, repo_branch.name, protected_refs: options[:project].protected_branches) end - expose :developers_can_merge do |repo_branch, options| + expose :developers_can_merge, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.developers_can?(:merge, repo_branch.name, protected_refs: options[:project].protected_branches) end - expose :can_push do |repo_branch, options| + expose :can_push, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| Gitlab::UserAccess.new(options[:current_user], container: options[:project]).can_push_to_branch?(repo_branch.name) end - expose :default do |repo_branch, options| + expose :default, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| options[:project].default_branch == repo_branch.name end - expose :web_url do |repo_branch| + expose :web_url, + documentation: { + type: 'string', + example: 'https://gitlab.example.com/Commit921/the-dude/-/tree/master' + } do |repo_branch| project_tree_url(options[:project], repo_branch.name) end end diff --git a/lib/api/entities/merge_request_approvals.rb b/lib/api/entities/merge_request_approvals.rb index 6810952b2fc..54f196e0d74 100644 --- a/lib/api/entities/merge_request_approvals.rb +++ b/lib/api/entities/merge_request_approvals.rb @@ -3,15 +3,15 @@ module API module Entities class MergeRequestApprovals < Grape::Entity - expose :user_has_approved do |merge_request, options| + expose :user_has_approved, documentation: { type: 'boolean' } do |merge_request, options| merge_request.approved_by?(options[:current_user]) end - expose :user_can_approve do |merge_request, options| + expose :user_can_approve, documentation: { type: 'boolean' } do |merge_request, options| merge_request.eligible_for_approval_by?(options[:current_user]) end - expose :approved do |merge_request| + expose :approved, documentation: { type: 'boolean' } do |merge_request| merge_request.approvals.present? end diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index 9b73a142fcd..c92a4677220 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -7,9 +7,9 @@ module API include ::Routing::PackagesHelper extend ::API::Entities::EntityHelpers - expose :id + expose :id, documentation: { type: 'integer', example: 1 } - expose :name do |package| + expose :name, documentation: { type: 'string', example: '@foo/bar' } do |package| if package.conan? package.conan_recipe else @@ -21,9 +21,9 @@ module API package.name end - expose :version - expose :package_type - expose :status + expose :version, documentation: { type: 'string', example: '1.0.3' } + expose :package_type, documentation: { type: 'string', example: 'npm' } + expose :status, documentation: { type: 'string', example: 'default' } expose :_links do expose :web_path do |package| @@ -35,10 +35,12 @@ module API end end - expose :created_at - expose :last_downloaded_at - expose :project_id, if: ->(_, opts) { opts[:group] } - expose :project_path, if: ->(obj, opts) { opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) } + expose :created_at, documentation: { type: 'dateTime', example: '2022-09-16T12:47:31.949Z' } + expose :last_downloaded_at, documentation: { type: 'dateTime', example: '2022-09-19T11:32:35.169Z' } + expose :project_id, documentation: { type: 'integer', example: 2 }, if: ->(_, opts) { opts[:group] } + expose :project_path, documentation: { type: 'string', example: 'gitlab/foo/bar' }, if: ->(obj, opts) do + opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) + end expose :tags expose :pipeline, if: ->(package) { package.original_build_info }, using: Package::Pipeline diff --git a/lib/api/entities/user_counts.rb b/lib/api/entities/user_counts.rb new file mode 100644 index 00000000000..e86454c249b --- /dev/null +++ b/lib/api/entities/user_counts.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module API + module Entities + class UserCounts < Grape::Entity + expose( + :assigned_open_merge_requests_count, # @deprecated + as: :merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose :assigned_open_issues_count, as: :assigned_issues, documentation: { type: 'integer', example: 10 } + expose( + :assigned_open_merge_requests_count, + as: :assigned_merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose( + :review_requested_open_merge_requests_count, + as: :review_requested_merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose :todos_pending_count, as: :todos, documentation: { type: 'integer', example: 10 } + end + end +end diff --git a/lib/api/group_avatar.rb b/lib/api/group_avatar.rb index 9063040c763..0820011fd89 100644 --- a/lib/api/group_avatar.rb +++ b/lib/api/group_avatar.rb @@ -7,11 +7,13 @@ module API feature_category :subgroups params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, type: String, desc: 'The ID of the group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the group avatar' do detail 'This feature was introduced in GitLab 14.0' + tags %w[group_avatar] + success code: 200 end get ':id/avatar' do avatar = user_group.avatar diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb index 72d67b41c31..c2b4cbf732f 100644 --- a/lib/api/group_packages.rb +++ b/lib/api/group_packages.rb @@ -14,13 +14,19 @@ module API helpers ::API::Helpers::PackagesHelpers params do - requires :id, type: String, desc: "Group's ID or path" + requires :id, types: [String, Integer], desc: 'ID or URL-encoded path of the group' optional :exclude_subgroups, type: Boolean, default: false, desc: 'Determines if subgroups should be excluded' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all project packages within a group' do - detail 'This feature was introduced in GitLab 12.5' + desc 'List packages within a group' do + detail 'Get a list of project packages at the group level. This feature was introduced in GitLab 12.5' success ::API::Entities::Package + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Group Not Found' } + ] + is_array true + tags %w[group_packages] end params do use :pagination @@ -53,10 +59,13 @@ module API packages = Packages::GroupPackagesFinder.new( current_user, user_group, - declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status) + declared(params).slice( + :exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status + ) ).execute - present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true, namespace: user_group + present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true, + namespace: user_group end end end diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 85648cd166d..eed9fa30d3c 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -8,6 +8,9 @@ module API UNPROCESSABLE_ERROR_KEYS = [:project_access, :branch_conflict, :validate_fork, :base].freeze + params :ee_approval_params do + end + params :merge_requests_negatable_params do optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username' @@ -136,3 +139,5 @@ module API end end end + +API::Helpers::MergeRequestsHelpers.prepend_mod_with('API::Helpers::MergeRequestsHelpers') diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 71ca8331ed6..7622ec717cc 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -6,10 +6,9 @@ module API feature_category :source_code_management - helpers do - params :ee_approval_params do - end + helpers ::API::Helpers::MergeRequestsHelpers + helpers do def present_approval(merge_request) present merge_request, with: ::API::Entities::MergeRequestApprovals, current_user: current_user end @@ -24,7 +23,12 @@ module API # merge_request_iid (required) - IID of MR # Examples: # GET /projects/:id/merge_requests/:merge_request_iid/approvals - desc 'List approvals for merge request' + desc 'List approvals for merge request' do + success ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' } + ] + end get 'approvals', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -39,7 +43,13 @@ module API # Examples: # POST /projects/:id/merge_requests/:merge_request_iid/approve # - desc 'Approve a merge request' + desc 'Approve a merge request' do + success code: 201, model: ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' }, + { code: 401, message: 'Unauthorized' } + ] + end params do optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' @@ -60,7 +70,13 @@ module API present_approval(merge_request) end - desc 'Remove an approval from a merge request' + desc 'Remove an approval from a merge request' do + success code: 201, model: ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' }, + { code: 401, message: 'Unauthorized' } + ] + end post 'unapprove', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb index 388aa5e375c..e9420f1e2b7 100644 --- a/lib/api/user_counts.rb +++ b/lib/api/user_counts.rb @@ -8,17 +8,12 @@ module API resource :user_counts do desc 'Return the user specific counts' do detail 'Assigned open issues, assigned MRs and pending todos count' + success Entities::UserCounts end get do unauthorized! unless current_user - { - merge_requests: current_user.assigned_open_merge_requests_count, # @deprecated - assigned_issues: current_user.assigned_open_issues_count, - assigned_merge_requests: current_user.assigned_open_merge_requests_count, - review_requested_merge_requests: current_user.review_requested_open_merge_requests_count, - todos: current_user.todos_pending_count - } + present current_user, with: Entities::UserCounts end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 86179ee79dd..7abfb806d2a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2651,6 +2651,9 @@ msgstr "" msgid "AdminArea|Total users" msgstr "" +msgid "AdminArea|Updated %{last_update_time}" +msgstr "" + msgid "AdminArea|Users" msgstr "" @@ -4010,9 +4013,6 @@ msgstr "" msgid "All users with matching cards" msgstr "" -msgid "Allow %{strongOpen}%{group_name}%{strongClose} to sign you in?" -msgstr "" - msgid "Allow access to members of the following group" msgstr "" @@ -35517,16 +35517,19 @@ msgstr "" msgid "SAML single sign-on for %{group_name}" msgstr "" +msgid "SAML|Allow %{groupName} to sign you in?" +msgstr "" + msgid "SAML|Sign in to GitLab to connect your organization's account" msgstr "" -msgid "SAML|The %{strongOpen}%{group_path}%{strongClose} group allows you to sign in using single sign-on." +msgid "SAML|The %{groupName} group allows you to sign in using single sign-on." msgstr "" msgid "SAML|To access %{strongOpen}%{group_name}%{strongClose}, you must sign in using single sign-on through an external sign-in page." msgstr "" -msgid "SAML|To allow %{strongOpen}%{group_name}%{strongClose} to manage your GitLab account %{strongOpen}%{username}%{strongClose} (%{email}) after you sign in successfully using single sign-on, select %{strongOpen}Authorize%{strongClose}." +msgid "SAML|To allow %{groupName} to manage your GitLab account %{username} after you sign in successfully using single sign-on, select %{strongStart}Authorize%{strongEnd}." msgstr "" msgid "SAML|Your organization's SSO has been connected to your GitLab account" diff --git a/rubocop/cop/api/ensure_string_detail.rb b/rubocop/cop/api/ensure_string_detail.rb new file mode 100644 index 00000000000..bc999525055 --- /dev/null +++ b/rubocop/cop/api/ensure_string_detail.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative '../../code_reuse_helpers' + +module RuboCop + module Cop + module API + class EnsureStringDetail < RuboCop::Cop::Base + include CodeReuseHelpers + + # This cop checks that API detail entries use Strings + # + # https://gitlab.com/gitlab-org/gitlab/-/issues/379037 + # + # @example + # + # # bad + # detail ['Foo bar baz bat', 'http://example.com'] + # + # # good + # detail 'Foo bar baz bat. http://example.com' + # + # end + # + MSG = 'Only String objects are permitted in API detail field.' + + def_node_matcher :detail_in_desc, <<~PATTERN + (block + (send nil? :desc ...) + _args + `(send nil? :detail $_ ...) + ) + PATTERN + + RESTRICT_ON_SEND = %i[detail].freeze + + def on_send(node) + return unless in_api?(node) + + parent = node.each_ancestor(:block).first + detail_arg = detail_in_desc(parent) + + return unless detail_arg + return if [:str, :dstr].include?(detail_arg.type) + + add_offense(node) + end + end + end + end +end diff --git a/spec/lib/api/entities/user_counts_spec.rb b/spec/lib/api/entities/user_counts_spec.rb new file mode 100644 index 00000000000..0ed989ad7e9 --- /dev/null +++ b/spec/lib/api/entities/user_counts_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::UserCounts do + let(:user) { build(:user) } + + subject(:entity) { described_class.new(user).as_json } + + it 'represents user counts', :aggregate_failures do + expect(user).to receive(:assigned_open_merge_requests_count).and_return(1).twice + expect(user).to receive(:assigned_open_issues_count).and_return(2).once + expect(user).to receive(:review_requested_open_merge_requests_count).and_return(3).once + expect(user).to receive(:todos_pending_count).and_return(4).once + + expect(entity).to include( + merge_requests: 1, + assigned_issues: 2, + assigned_merge_requests: 1, + review_requested_merge_requests: 3, + todos: 4 + ) + end +end diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb index ab2aa87d1b7..369ae49de08 100644 --- a/spec/requests/api/user_counts_spec.rb +++ b/spec/requests/api/user_counts_spec.rb @@ -26,24 +26,22 @@ RSpec.describe API::UserCounts do expect(json_response['assigned_issues']).to eq(1) end - context 'merge requests' do - it 'returns assigned MR counts for current user' do - get api('/user_counts', user) + it 'returns assigned MR counts for current user' do + get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(1) - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(1) + end - it 'updates the mr count when a new mr is assigned' do - create(:merge_request, source_project: project, author: user, assignees: [user]) + it 'updates the mr count when a new mr is assigned' do + create(:merge_request, source_project: project, author: user, assignees: [user]) - get api('/user_counts', user) + get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(2) - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(2) end it 'returns pending todo counts for current_user' do diff --git a/spec/rubocop/cop/api/ensure_string_detail_spec.rb b/spec/rubocop/cop/api/ensure_string_detail_spec.rb new file mode 100644 index 00000000000..d4f68711e78 --- /dev/null +++ b/spec/rubocop/cop/api/ensure_string_detail_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'rubocop_spec_helper' +require_relative '../../../../rubocop/cop/api/ensure_string_detail' + +RSpec.describe RuboCop::Cop::API::EnsureStringDetail do + context "when in_api? == true" do + before do + allow(cop).to receive(:in_api?).and_return(true) + end + + context "when detail field uses a string" do + it "does not add an offense" do + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + detail "foo bar" + end + end + end + CODE + end + end + + context "when detail field uses interpolation in a string" do + it "does not add an offense" do + baz = "bat" + + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + detail "foo bar #{baz}" + end + end + end + CODE + end + end + + context "when detail field uses a multiline string" do + it "does not add an offense" do + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + detail "foo bar"\ + "baz bat" + end + end + end + CODE + end + end + + context "when detail field uses a constant" do + it "does not add an offense" do + pending + + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + DESCRIPTION = 'A string' + + desc 'Some API thing related to a project' do + detail DESCRIPTION + end + end + end + CODE + end + end + + context "when detail field uses a HEREDOC string" do + it "does not add an offense" do + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + detail <<~END + foo bar + baz bat + END + end + end + end + CODE + end + end + + context "when detail field uses an array" do + it "adds an offense" do + expect_offense(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + something 'else' + detail ["foo", "bar"] + ^^^^^^^^^^^^^^^^^^^^^ Only String objects are permitted in API detail field. + end + end + end + CODE + end + end + + context "when detail field is outside of desc block" do + it "does not add an offense" do + expect_no_offenses(<<~CODE) + class Foo + detail ["foo", "bar"] + end + CODE + end + end + end + + context "when in_api? == false" do + before do + allow(cop).to receive(:in_api?).and_return(false) + end + + it "does not add an offense" do + expect_no_offenses(<<~CODE) + class SomeAPI + resource :projects do + desc 'Some API thing related to a project' do + detail ["foo", "bar"] + end + end + end + CODE + end + end +end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 31d24052b3e..e1b461cf37e 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -284,15 +284,30 @@ module TestEnv unless File.directory?(repo_path) start = Time.now system(*%W(#{Gitlab.config.git.bin_path} clone --quiet -- #{clone_url} #{repo_path})) - system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} remote remove origin)) puts "==> #{repo_path} set up in #{Time.now - start} seconds...\n" end - set_repo_refs(repo_path, refs) + create_bundle = !File.file?(repo_bundle_path) - unless File.file?(repo_bundle_path) + unless set_repo_refs(repo_path, refs) + # Prefer not to fetch over the network. Only fetch when we have failed to + # set all the required local branches. This would happen when a new + # branch is added to BRANCH_SHA, in which case we want to update + # everything. + unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin)) + raise 'Could not fetch test seed repository.' + end + + unless set_repo_refs(repo_path, refs) + raise "Could not update test seed repository, please delete #{repo_path} and try again" + end + + create_bundle = true + end + + if create_bundle start = Time.now - system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{repo_path} bundle create #{repo_bundle_path} --all)) + system(git_env, *%W(#{Gitlab.config.git.bin_path} -C #{repo_path} bundle create #{repo_bundle_path} --exclude refs/remotes/* --all)) puts "==> #{repo_bundle_path} generated in #{Time.now - start} seconds...\n" end end @@ -394,20 +409,13 @@ module TestEnv end def set_repo_refs(repo_path, branch_sha) - instructions = branch_sha.map { |branch, sha| "update refs/heads/#{branch}\x00#{sha}\x00" }.join("\x00") << "\x00" - update_refs = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z) - reset = proc do - Dir.chdir(repo_path) do - IO.popen(update_refs, "w") { |io| io.write(instructions) } - $?.success? + IO.popen(%W[#{Gitlab.config.git.bin_path} -C #{repo_path} update-ref --stdin -z], "w") do |io| + branch_sha.each do |branch, sha| + io.write("update refs/heads/#{branch}\x00#{sha}\x00\x00") end end - # Try to reset without fetching to avoid using the network. - unless reset.call - raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin)) - raise "Could not update test seed repository, please delete #{repo_path} and try again" unless reset.call - end + $?.success? end def component_timed_setup(component, install_dir:, version:, task:, fresh_install: true, task_args: []) diff --git a/spec/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml_spec.rb b/spec/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml_spec.rb index 416f4253e1b..99339e956cc 100644 --- a/spec/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml_spec.rb @@ -19,7 +19,7 @@ RSpec.describe 'projects/merge_requests/_close_reopen_draft_report_toggle.html.h render - expect(rendered).to have_css('li', class: 'js-sidebar-subscriptions-entry-point') + expect(rendered).to have_css('li', class: 'js-sidebar-subscriptions-widget-root') end end @@ -27,7 +27,7 @@ RSpec.describe 'projects/merge_requests/_close_reopen_draft_report_toggle.html.h it 'is not present' do render - expect(rendered).not_to have_css('li', class: 'js-sidebar-subscriptions-entry-point') + expect(rendered).not_to have_css('li', class: 'js-sidebar-subscriptions-widget-root') end end end diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 05ac037c9cb..75956160c0a 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -45,7 +45,7 @@ RSpec.describe 'projects/merge_requests/edit.html.haml' do expect(rendered).to have_field('merge_request[title]') expect(rendered).to have_field('merge_request[description]') expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false) - expect(rendered).to have_selector('.js-milestone-dropdown') + expect(rendered).to have_selector('.js-milestone-dropdown-root') expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false) end end @@ -57,7 +57,7 @@ RSpec.describe 'projects/merge_requests/edit.html.haml' do expect(rendered).to have_field('merge_request[title]') expect(rendered).to have_field('merge_request[description]') expect(rendered).to have_selector('input[name="merge_request[label_ids][]"]', visible: false) - expect(rendered).to have_selector('.js-milestone-dropdown') + expect(rendered).to have_selector('.js-milestone-dropdown-root') expect(rendered).to have_selector('#merge_request_target_branch', visible: false) end end diff --git a/spec/views/shared/issuable/_sidebar.html.haml_spec.rb b/spec/views/shared/issuable/_sidebar.html.haml_spec.rb index 43a723dbb2c..31f79c25073 100644 --- a/spec/views/shared/issuable/_sidebar.html.haml_spec.rb +++ b/spec/views/shared/issuable/_sidebar.html.haml_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'shared/issuable/_sidebar.html.haml' do it 'is expected not to be shown' do create(:contact, group: group) - expect(rendered).not_to have_css('#js-issue-crm-contacts') + expect(rendered).not_to have_css('.js-sidebar-crm-contacts-root') end end @@ -51,7 +51,7 @@ RSpec.describe 'shared/issuable/_sidebar.html.haml' do it 'is expected not to be shown' do group.add_developer(user) - expect(rendered).not_to have_css('#js-issue-crm-contacts') + expect(rendered).not_to have_css('.js-sidebar-crm-contacts-root') end end @@ -60,7 +60,7 @@ RSpec.describe 'shared/issuable/_sidebar.html.haml' do create(:contact, group: group) group.add_developer(user) - expect(rendered).to have_css('#js-issue-crm-contacts') + expect(rendered).to have_css('.js-sidebar-crm-contacts-root') end end end -- cgit v1.2.3