Commit 4ee20b8e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 7812b4de 84bd4dff
...@@ -39,6 +39,8 @@ import initPersistentUserCallouts from './persistent_user_callouts'; ...@@ -39,6 +39,8 @@ import initPersistentUserCallouts from './persistent_user_callouts';
import { initUserTracking, initDefaultTrackers } from './tracking'; import { initUserTracking, initDefaultTrackers } from './tracking';
import { __ } from './locale'; import { __ } from './locale';
import * as tooltips from '~/tooltips';
import 'ee_else_ce/main_ee'; import 'ee_else_ce/main_ee';
applyGitLabUIConfig(); applyGitLabUIConfig();
...@@ -77,7 +79,7 @@ document.addEventListener('beforeunload', () => { ...@@ -77,7 +79,7 @@ document.addEventListener('beforeunload', () => {
// Unbind scroll events // Unbind scroll events
$(document).off('scroll'); $(document).off('scroll');
// Close any open tooltips // Close any open tooltips
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('dispose'); tooltips.dispose(document.querySelectorAll('.has-tooltip, [data-toggle="tooltip"]'));
// Close any open popover // Close any open popover
$('[data-toggle="popover"]').popover('dispose'); $('[data-toggle="popover"]').popover('dispose');
}); });
...@@ -133,8 +135,10 @@ function deferredInitialisation() { ...@@ -133,8 +135,10 @@ function deferredInitialisation() {
addSelectOnFocusBehaviour('.js-select-on-focus'); addSelectOnFocusBehaviour('.js-select-on-focus');
$('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() { $('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() {
tooltips.dispose(this);
// eslint-disable-next-line no-jquery/no-fade
$(this) $(this)
.tooltip('dispose')
.closest('li') .closest('li')
.fadeOut(); .fadeOut();
}); });
...@@ -154,7 +158,7 @@ function deferredInitialisation() { ...@@ -154,7 +158,7 @@ function deferredInitialisation() {
const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0; const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
// Initialize tooltips // Initialize tooltips
$body.tooltip({ tooltips.initTooltips({
selector: '.has-tooltip, [data-toggle="tooltip"]', selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover', trigger: 'hover',
boundary: 'viewport', boundary: 'viewport',
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
import $ from 'jquery'; import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import eventHub from '~/vue_merge_request_widget/event_hub';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import TaskList from './task_list'; import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs'; import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility'; import { addDelimiter } from './lib/utils/text_utility';
import { getParameterValues, setUrlParams } from './lib/utils/url_utility';
function MergeRequest(opts) { function MergeRequest(opts) {
// Initialize MergeRequest behavior // Initialize MergeRequest behavior
...@@ -23,7 +24,6 @@ function MergeRequest(opts) { ...@@ -23,7 +24,6 @@ function MergeRequest(opts) {
this.initTabs(); this.initTabs();
this.initMRBtnListeners(); this.initMRBtnListeners();
this.initCommitMessageListeners(); this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($('.description.js-task-list-container').length) { if ($('.description.js-task-list-container').length) {
this.taskList = new TaskList({ this.taskList = new TaskList({
...@@ -66,13 +66,38 @@ MergeRequest.prototype.showAllCommits = function() { ...@@ -66,13 +66,38 @@ MergeRequest.prototype.showAllCommits = function() {
MergeRequest.prototype.initMRBtnListeners = function() { MergeRequest.prototype.initMRBtnListeners = function() {
const _this = this; const _this = this;
const draftToggles = document.querySelectorAll('.js-draft-toggle-button');
$('.report-abuse-link').on('click', e => { if (draftToggles.length) {
// this is needed because of the implementation of draftToggles.forEach(draftToggle => {
// the dropdown toggle and Report Abuse needing to be draftToggle.addEventListener('click', e => {
// linked to another page. e.preventDefault();
e.stopPropagation(); e.stopImmediatePropagation();
const url = draftToggle.href;
const wipEvent = getParameterValues('merge_request[wip_event]', url)[0];
const mobileDropdown = draftToggle.closest('.dropdown.show');
if (mobileDropdown) {
$(mobileDropdown.firstElementChild).dropdown('toggle');
}
draftToggle.setAttribute('disabled', 'disabled');
axios
.put(draftToggle.href, null, { params: { format: 'json' } })
.then(({ data }) => {
draftToggle.removeAttribute('disabled');
eventHub.$emit('MRWidgetUpdateRequested');
MergeRequest.toggleDraftStatus(data.title, wipEvent === 'unwip');
})
.catch(() => {
draftToggle.removeAttribute('disabled');
createFlash(__('Something went wrong. Please try again.'));
}); });
});
});
}
return $('.btn-close, .btn-reopen').on('click', function(e) { return $('.btn-close, .btn-reopen').on('click', function(e) {
const $this = $(this); const $this = $(this);
...@@ -89,8 +114,6 @@ MergeRequest.prototype.initMRBtnListeners = function() { ...@@ -89,8 +114,6 @@ MergeRequest.prototype.initMRBtnListeners = function() {
return; return;
} }
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
if (shouldSubmit) { if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) { if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault(); e.preventDefault();
...@@ -151,14 +174,35 @@ MergeRequest.hideCloseButton = function() { ...@@ -151,14 +174,35 @@ MergeRequest.hideCloseButton = function() {
const closeDropdownItem = el.querySelector('li.close-item'); const closeDropdownItem = el.querySelector('li.close-item');
if (closeDropdownItem) { if (closeDropdownItem) {
closeDropdownItem.classList.add('hidden'); closeDropdownItem.classList.add('hidden');
// Selects the next dropdown item
el.querySelector('li.report-item').click();
} else {
// No dropdown just hide the Close button
el.querySelector('.btn-close').classList.add('hidden');
} }
// Dropdown for mobile screen // Dropdown for mobile screen
el.querySelector('li.js-close-item').classList.add('hidden'); el.querySelector('li.js-close-item').classList.add('hidden');
}; };
MergeRequest.toggleDraftStatus = function(title, isReady) {
if (isReady) {
createFlash(__('The merge request can now be merged.'), 'notice');
}
const titleEl = document.querySelector('.merge-request .detail-page-description .title');
if (titleEl) {
titleEl.textContent = title;
}
const draftToggles = document.querySelectorAll('.js-draft-toggle-button');
if (draftToggles.length) {
draftToggles.forEach(el => {
const draftToggle = el;
const url = setUrlParams(
{ 'merge_request[wip_event]': isReady ? 'wip' : 'unwip' },
draftToggle.href,
);
draftToggle.setAttribute('href', url);
draftToggle.textContent = isReady ? __('Mark as draft') : __('Mark as ready');
});
}
};
export default MergeRequest; export default MergeRequest;
...@@ -57,6 +57,10 @@ export default { ...@@ -57,6 +57,10 @@ export default {
text: s__('ProjectTemplates|Static Site Editor/Middleman'), text: s__('ProjectTemplates|Static Site Editor/Middleman'),
icon: '.template-option .icon-sse_middleman', icon: '.template-option .icon-sse_middleman',
}, },
gitpod_spring_petclinic: {
text: s__('ProjectTemplates|Gitpod/Spring Petclinic'),
icon: '.template-option .icon-gitpod_spring_petclinic',
},
nfhugo: { nfhugo: {
text: s__('ProjectTemplates|Netlify/Hugo'), text: s__('ProjectTemplates|Netlify/Hugo'),
icon: '.template-option .icon-nfhugo', icon: '.template-option .icon-nfhugo',
......
...@@ -58,6 +58,8 @@ const applyToElements = (elements, handler) => toArray(elements).forEach(handler ...@@ -58,6 +58,8 @@ const applyToElements = (elements, handler) => toArray(elements).forEach(handler
const invokeBootstrapApi = (elements, method) => { const invokeBootstrapApi = (elements, method) => {
if (isFunction(elements.tooltip)) { if (isFunction(elements.tooltip)) {
elements.tooltip(method);
} else {
jQuery(elements).tooltip(method); jQuery(elements).tooltip(method);
} }
}; };
......
...@@ -3,6 +3,7 @@ import $ from 'jquery'; ...@@ -3,6 +3,7 @@ import $ from 'jquery';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import MergeRequest from '~/merge_request';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import getStateQuery from '../../queries/get_state.query.graphql'; import getStateQuery from '../../queries/get_state.query.graphql';
...@@ -128,8 +129,7 @@ export default { ...@@ -128,8 +129,7 @@ export default {
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
eventHub.$emit('UpdateWidgetData', data); eventHub.$emit('UpdateWidgetData', data);
createFlash(__('The merge request can now be merged.'), 'notice'); MergeRequest.toggleDraftStatus(this.mr.title, true);
$('.merge-request .detail-page-description .title').text(this.mr.title);
}) })
.catch(() => { .catch(() => {
this.isMakingRequest = false; this.isMakingRequest = false;
......
...@@ -35,6 +35,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -35,6 +35,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:source_branch, :source_branch,
:source_project_id, :source_project_id,
:state_event, :state_event,
:wip_event,
:squash, :squash,
:target_branch, :target_branch,
:target_project_id, :target_project_id,
......
...@@ -342,6 +342,12 @@ module IssuablesHelper ...@@ -342,6 +342,12 @@ module IssuablesHelper
issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable) issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
end end
def toggle_draft_issuable_path(issuable)
wip_event = issuable.work_in_progress? ? 'unwip' : 'wip'
issuable_path(issuable, { merge_request: { wip_event: wip_event } })
end
def issuable_path(issuable, *options) def issuable_path(issuable, *options)
polymorphic_path(issuable, *options) polymorphic_path(issuable, *options)
end end
......
...@@ -37,7 +37,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -37,7 +37,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
def remove_wip_path def remove_wip_path
if work_in_progress? && can?(current_user, :update_merge_request, merge_request.project) if can?(current_user, :update_merge_request, merge_request.project)
remove_wip_project_merge_request_path(project, merge_request) remove_wip_project_merge_request_path(project, merge_request)
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class MergeRequestBasicEntity < Grape::Entity class MergeRequestBasicEntity < Grape::Entity
expose :title
expose :public_merge_status, as: :merge_status expose :public_merge_status, as: :merge_status
expose :merge_error expose :merge_error
expose :state expose :state
......
...@@ -25,6 +25,6 @@ ...@@ -25,6 +25,6 @@
'secondary-action': s_('AdminUsers|Block user') } } 'secondary-action': s_('AdminUsers|Block user') } }
= s_('AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, = s_('AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues,
merge requests, and groups linked to them. To avoid data loss, merge requests, and groups linked to them. To avoid data loss,
consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd},
it cannot be undone or recovered.') it cannot be undone or recovered.')
...@@ -75,12 +75,12 @@ ...@@ -75,12 +75,12 @@
= javascript_include_tag locale_path unless I18n.locale == :en = javascript_include_tag locale_path unless I18n.locale == :en
= webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
- if content_for?(:page_specific_javascripts) - if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts = yield :page_specific_javascripts
= webpack_controller_bundle_tags = webpack_controller_bundle_tags
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
= webpack_bundle_tag "chrome_84_icon_fix" if browser.chrome?([">=84", "<84.0.4147.125"]) || browser.edge?([">=84", "<84.0.522.59"]) = webpack_bundle_tag "chrome_84_icon_fix" if browser.chrome?([">=84", "<84.0.4147.125"]) || browser.edge?([">=84", "<84.0.522.59"])
= yield :project_javascripts = yield :project_javascripts
......
...@@ -32,16 +32,19 @@ ...@@ -32,16 +32,19 @@
%ul %ul
- if can_update_merge_request - if can_update_merge_request
%li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request - if can_update_merge_request
- unless @merge_request.closed?
%li
= link_to @merge_request.work_in_progress? ? _('Mark as ready') : _('Mark as draft'), toggle_draft_issuable_path(@merge_request), method: :put, class: "js-draft-toggle-button"
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] } %li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
- if can_reopen_merge_request - if can_reopen_merge_request
%li{ class: merge_request_button_visibility(@merge_request, false) } %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request - if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn btn-grouped js-issuable-edit qa-edit-button" = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn gl-button btn-grouped js-issuable-edit qa-edit-button"
= render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_reopen_merge_request = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_reopen_merge_request
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- if defined? warn_before_close - if defined? warn_before_close
- add_blocked_class = warn_before_close - add_blocked_class = warn_before_close
- if is_current_user - if is_current_user && !issuable.is_a?(MergeRequest)
- if can_update - if can_update
%button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", %button{ class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}",
data: { remote: 'true', endpoint: close_issuable_path(issuable), qa_selector: 'close_issue_button' } } data: { remote: 'true', endpoint: close_issuable_path(issuable), qa_selector: 'close_issue_button' } }
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
= _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type } = _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }
- else - else
- if can_update && !are_close_and_open_buttons_hidden - if can_update && !are_close_and_open_buttons_hidden
- if issuable.is_a?(MergeRequest)
= render 'shared/issuable/close_reopen_draft_report_toggle', issuable: issuable
- else
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class
- else - else
= link_to _('Report abuse'), new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)), = link_to _('Report abuse'), new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
......
- display_issuable_type = issuable_display_type(issuable)
- button_action_class = issuable.closed? ? 'btn-default' : 'btn-warning btn-warning-secondary'
- button_class = "btn gl-button #{!issuable.closed? && 'js-draft-toggle-button'}"
- toggle_class = "btn gl-button dropdown-toggle"
.float-left.btn-group.gl-ml-3.issuable-close-dropdown.d-none.d-md-inline-flex.js-issuable-close-dropdown
= link_to issuable.closed? ? reopen_issuable_path(issuable) : toggle_draft_issuable_path(issuable), method: :put, class: "#{button_class} #{button_action_class}" do
- if issuable.closed?
= _('Reopen')
= display_issuable_type
- else
= issuable.work_in_progress? ? _('Mark as ready') : _('Mark as draft')
- if !issuable.closed? || !issuable_author_is_current_user(issuable)
= button_tag type: 'button', class: "#{toggle_class} #{button_action_class}", data: { 'toggle' => 'dropdown' } do
%span.sr-only= _('Toggle dropdown')
= sprite_icon "angle-down", size: 12
%ul.js-issuable-close-menu.dropdown-menu.dropdown-menu-right
- if issuable.open?
%li
= link_to close_issuable_path(issuable), method: :put do
.description
%strong.title
= _('Close')
= display_issuable_type
- unless issuable_author_is_current_user(issuable)
- unless issuable.closed?
%li.divider.droplab-item-ignore
%li.report-item
%a.report-abuse-link{ href: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)) }
.description
%strong.title= _('Report abuse')
%p.text
= _('Report %{display_issuable_type} that are abusive, inappropriate or spam.') % { display_issuable_type: display_issuable_type.pluralize }
---
title: Fix verifying LFS uploads with GitHub
merge_request: 43852
author:
type: fixed
---
title: "Add Gitpod Spring Petclinic to Project Templates"
merge_request: 43319
author:
type: added
---
title: Fix Delete User dialog formatted strings
merge_request: 43871
author:
type: fixed
---
title: Update node-sass from 4.12.0 to 4.14.1
merge_request: 43808
author: Takuya Noguchi
type: other
---
title: Adds button to update merge request draft status on merge request show page
merge_request: 43098
author:
type: added
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
- auto_devops - auto_devops
- backup_restore - backup_restore
- behavior_analytics - behavior_analytics
- boards
- chatops - chatops
- cloud_native_installation - cloud_native_installation
- cluster_cost_optimization - cluster_cost_optimization
...@@ -61,15 +62,15 @@ ...@@ -61,15 +62,15 @@
- importers - importers
- incident_management - incident_management
- infrastructure_as_code - infrastructure_as_code
- insights
- instance_statistics
- integrations - integrations
- interactive_application_security_testing - interactive_application_security_testing
- internationalization - internationalization
- instance_statistics
- issue_tracking - issue_tracking
- jenkins_importer - jenkins_importer
- jira_importer - jira_importer
- jupyter_notebooks - jupyter_notebooks
- kanban_boards
- kubernetes_management - kubernetes_management
- license_compliance - license_compliance
- live_preview - live_preview
...@@ -82,6 +83,7 @@ ...@@ -82,6 +83,7 @@
- omnibus_package - omnibus_package
- package_registry - package_registry
- pages - pages
- pipeline_authoring
- pki_management - pki_management
- planning_analytics - planning_analytics
- product_analytics - product_analytics
......
...@@ -267,6 +267,7 @@ POST /groups/:id/epics ...@@ -267,6 +267,7 @@ POST /groups/:id/epics
| `labels` | string | no | The comma separated list of labels | | `labels` | string | no | The comma separated list of labels |
| `description` | string | no | The description of the epic. Limited to 1,048,576 characters. | | `description` | string | no | The description of the epic. Limited to 1,048,576 characters. |
| `confidential` | boolean | no | Whether the epic should be confidential | | `confidential` | boolean | no | Whether the epic should be confidential |
| `created_at` | string | no | When the epic was created. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` . Requires administrator or project/group owner privileges ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255309) in GitLab 13.5) |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) | | `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) | | `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) | | `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
...@@ -349,6 +350,7 @@ PUT /groups/:id/epics/:epic_iid ...@@ -349,6 +350,7 @@ PUT /groups/:id/epics/:epic_iid
| `description` | string | no | The description of an epic. Limited to 1,048,576 characters. | | `description` | string | no | The description of an epic. Limited to 1,048,576 characters. |
| `confidential` | boolean | no | Whether the epic should be confidential | | `confidential` | boolean | no | Whether the epic should be confidential |
| `labels` | string | no | The comma separated list of labels | | `labels` | string | no | The comma separated list of labels |
| `updated_at` | string | no | When the epic was updated. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` . Requires administrator or project/group owner privileges ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255309) in GitLab 13.5) |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) | | `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) | | `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) | | `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
......
...@@ -127,6 +127,7 @@ field). ...@@ -127,6 +127,7 @@ field).
| title | `issue[title]` | | | title | `issue[title]` | |
| description | `issue[description]` | | | description | `issue[description]` | |
| description template | `issuable_template` | | | description template | `issuable_template` | |
| issue type | `issue[issue_type]` | Either `incident` or `issue` |
| confidential | `issue[confidential]` | Parameter value must be `true` to set to confidential | | confidential | `issue[confidential]` | Parameter value must be `true` to set to confidential |
Follow these examples to form your new issue URL with prefilled fields. Follow these examples to form your new issue URL with prefilled fields.
......
...@@ -223,6 +223,7 @@ export default { ...@@ -223,6 +223,7 @@ export default {
first: this.$options.PROJECTS_PER_PAGE, first: this.$options.PROJECTS_PER_PAGE,
after: pageInfo.endCursor, after: pageInfo.endCursor,
searchNamespaces: true, searchNamespaces: true,
sort: 'similarity',
}, },
}); });
}, },
......
...@@ -122,10 +122,15 @@ module EE ...@@ -122,10 +122,15 @@ module EE
enable :admin_wiki enable :admin_wiki
end end
rule { owner }.policy do rule { owner | admin }.policy do
enable :owner_access enable :owner_access
end end
rule { can?(:owner_access) }.policy do
enable :set_epic_created_at
enable :set_epic_updated_at
end
rule { can?(:read_cluster) & cluster_deployments_available } rule { can?(:read_cluster) & cluster_deployments_available }
.enable :read_cluster_environments .enable :read_cluster_environments
......
---
title: Use similarity sort when searching projects from Security Dashboard
merge_request: 43610
author:
type: changed
---
title: Allow created_at and updated_at to be set through Epics API
merge_request: 43279
author: stingrayza
type: added
...@@ -70,6 +70,7 @@ module API ...@@ -70,6 +70,7 @@ module API
requires :title, type: String, desc: 'The title of an epic' requires :title, type: String, desc: 'The title of an epic'
optional :description, type: String, desc: 'The description of an epic' optional :description, type: String, desc: 'The description of an epic'
optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential' optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential'
optional :created_at, type: DateTime, desc: 'Date time when the epic was created. Available only for admins and project owners.'
optional :start_date, as: :start_date_fixed, type: String, desc: 'The start date of an epic' optional :start_date, as: :start_date_fixed, type: String, desc: 'The start date of an epic'
optional :start_date_is_fixed, type: Boolean, desc: 'Indicates start date should be sourced from start_date_fixed field not the issue milestones' optional :start_date_is_fixed, type: Boolean, desc: 'Indicates start date should be sourced from start_date_fixed field not the issue milestones'
optional :end_date, as: :due_date_fixed, type: String, desc: 'The due date of an epic' optional :end_date, as: :due_date_fixed, type: String, desc: 'The due date of an epic'
...@@ -80,6 +81,9 @@ module API ...@@ -80,6 +81,9 @@ module API
post ':id/(-/)epics' do post ':id/(-/)epics' do
authorize_can_create! authorize_can_create!
# Setting created_at is allowed only for admins and owners
params.delete(:created_at) unless current_user.can?(:set_epic_created_at, user_group)
epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute
if epic.valid? if epic.valid?
present epic, epic_options present epic, epic_options
...@@ -96,6 +100,7 @@ module API ...@@ -96,6 +100,7 @@ module API
optional :title, type: String, desc: 'The title of an epic' optional :title, type: String, desc: 'The title of an epic'
optional :description, type: String, desc: 'The description of an epic' optional :description, type: String, desc: 'The description of an epic'
optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential' optional :confidential, type: Boolean, desc: 'Indicates if the epic is confidential'
optional :updated_at, type: DateTime, desc: 'Date time when the epic was updated. Available only for admins and project owners.'
optional :start_date, as: :start_date_fixed, type: String, desc: 'The start date of an epic' optional :start_date, as: :start_date_fixed, type: String, desc: 'The start date of an epic'
optional :start_date_is_fixed, type: Boolean, desc: 'Indicates start date should be sourced from start_date_fixed field not the issue milestones' optional :start_date_is_fixed, type: Boolean, desc: 'Indicates start date should be sourced from start_date_fixed field not the issue milestones'
optional :end_date, as: :due_date_fixed, type: String, desc: 'The due date of an epic' optional :end_date, as: :due_date_fixed, type: String, desc: 'The due date of an epic'
...@@ -108,6 +113,10 @@ module API ...@@ -108,6 +113,10 @@ module API
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/194104') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/194104')
authorize_can_admin_epic! authorize_can_admin_epic!
# Setting updated_at is allowed only for admins and owners
params.delete(:updated_at) unless current_user.can?(:set_epic_updated_at, user_group)
update_params = declared_params(include_missing: false) update_params = declared_params(include_missing: false)
update_params.delete(:epic_iid) update_params.delete(:epic_iid)
......
...@@ -87,6 +87,7 @@ describe('Project Manager component', () => { ...@@ -87,6 +87,7 @@ describe('Project Manager component', () => {
first: wrapper.vm.$options.PROJECTS_PER_PAGE, first: wrapper.vm.$options.PROJECTS_PER_PAGE,
after: '', after: '',
searchNamespaces: true, searchNamespaces: true,
sort: 'similarity',
}, },
}); });
}); });
......
...@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Graphql::Aggregations::Vulnerabilities::LazyUserNotesCoun ...@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Graphql::Aggregations::Vulnerabilities::LazyUserNotesCoun
it 'uses lazy_user_notes_count_aggregate to collect aggregates' do it 'uses lazy_user_notes_count_aggregate to collect aggregates' do
subject = described_class.new({ lazy_user_notes_count_aggregate: { pending_vulnerability_ids: [10, 20, 30].to_set, loaded_objects: {} } }, vulnerability) subject = described_class.new({ lazy_user_notes_count_aggregate: { pending_vulnerability_ids: [10, 20, 30].to_set, loaded_objects: {} } }, vulnerability)
expect(subject.lazy_state[:pending_vulnerability_ids]).to match [10, 20, 30, vulnerability.id] expect(subject.lazy_state[:pending_vulnerability_ids]).to match_array [10, 20, 30, vulnerability.id]
expect(subject.vulnerability).to match vulnerability expect(subject.vulnerability).to match vulnerability
end end
end end
......
...@@ -22,6 +22,12 @@ RSpec.describe GroupPolicy do ...@@ -22,6 +22,12 @@ RSpec.describe GroupPolicy do
it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic, :read_confidential_epic, :destroy_epic_link) } it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic, :read_confidential_epic, :destroy_epic_link) }
end end
context 'when user is admin' do
let(:current_user) { admin }
it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic, :read_confidential_epic, :destroy_epic_link) }
end
context 'when user is maintainer' do context 'when user is maintainer' do
let(:current_user) { maintainer } let(:current_user) { maintainer }
......
...@@ -610,6 +610,39 @@ RSpec.describe API::Epics do ...@@ -610,6 +610,39 @@ RSpec.describe API::Epics do
end end
end end
context 'setting created_at' do
let(:creation_time) { 2.weeks.ago }
let(:params) { { title: 'new epic', created_at: creation_time } }
it 'sets the creation time on the new epic if the user is an admin' do
admin = create(:user, :admin)
post api(url, admin), params: params
expect(response).to have_gitlab_http_status(:created)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
end
it 'sets the creation time on the new epic if the user is a group owner' do
group.add_owner(user)
post api(url, user), params: params
expect(response).to have_gitlab_http_status(:created)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
end
it 'ignores the given creation time if the user is another user' do
user2 = create(:user)
group.add_developer(user2)
post api(url, user2), params: params
expect(response).to have_gitlab_http_status(:created)
expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time)
end
end
it 'creates a new epic with labels param as array' do it 'creates a new epic with labels param as array' do
params[:labels] = ['label1', 'label2', 'foo, bar', '&,?'] params[:labels] = ['label1', 'label2', 'foo, bar', '&,?']
...@@ -765,6 +798,38 @@ RSpec.describe API::Epics do ...@@ -765,6 +798,38 @@ RSpec.describe API::Epics do
end end
end end
context 'setting updated_at' do
let(:update_time) { 1.week.ago }
it 'ignores the given update time when run by another user' do
user2 = create(:user)
group.add_developer(user2)
put api(url, user2), params: { title: 'updated by other user', updated_at: update_time }
expect(response).to have_gitlab_http_status(:ok)
expect(Time.parse(json_response['updated_at'])).not_to be_like_time(update_time)
end
it 'sets the update time on the epic when run by an admin' do
admin = create(:user, :admin)
put api(url, admin), params: { title: 'updated by admin', updated_at: update_time }
expect(response).to have_gitlab_http_status(:ok)
expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
end
it 'sets the update time on the epic when run by a group owner' do
group.add_owner(user)
put api(url, user), params: { title: 'updated by owner', updated_at: update_time }
expect(response).to have_gitlab_http_status(:ok)
expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time)
end
end
context 'when deprecated start_date and end_date params are present' do context 'when deprecated start_date and end_date params are present' do
let(:epic) { create(:epic, :use_fixed_dates, group: group) } let(:epic) { create(:epic, :use_fixed_dates, group: group) }
let(:new_start_date) { epic.start_date + 1.day } let(:new_start_date) { epic.start_date + 1.day }
......
...@@ -17,11 +17,9 @@ RSpec.describe API::ProjectClusters do ...@@ -17,11 +17,9 @@ RSpec.describe API::ProjectClusters do
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: "#{namespace}-token", metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'), token: Base64.encode64('sample-token'),
namespace: namespace namespace: namespace
}
) )
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace) stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
......
...@@ -7,9 +7,11 @@ module Gitlab ...@@ -7,9 +7,11 @@ module Gitlab
# * https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md # * https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md
class Client class Client
GIT_LFS_CONTENT_TYPE = 'application/vnd.git-lfs+json' GIT_LFS_CONTENT_TYPE = 'application/vnd.git-lfs+json'
GIT_LFS_USER_AGENT = "GitLab #{Gitlab::VERSION} LFS client"
DEFAULT_HEADERS = { DEFAULT_HEADERS = {
'Accept' => GIT_LFS_CONTENT_TYPE, 'Accept' => GIT_LFS_CONTENT_TYPE,
'Content-Type' => GIT_LFS_CONTENT_TYPE 'Content-Type' => GIT_LFS_CONTENT_TYPE,
'User-Agent' => GIT_LFS_USER_AGENT
}.freeze }.freeze
attr_reader :base_url attr_reader :base_url
...@@ -53,7 +55,8 @@ module Gitlab ...@@ -53,7 +55,8 @@ module Gitlab
body_stream: file, body_stream: file,
headers: { headers: {
'Content-Length' => object.size.to_s, 'Content-Length' => object.size.to_s,
'Content-Type' => 'application/octet-stream' 'Content-Type' => 'application/octet-stream',
'User-Agent' => GIT_LFS_USER_AGENT
}.merge(upload_action['header'] || {}) }.merge(upload_action['header'] || {})
} }
......
...@@ -52,6 +52,7 @@ module Gitlab ...@@ -52,6 +52,7 @@ module Gitlab
ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook', 'illustrations/logos/gitbook.svg'), ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook', 'illustrations/logos/gitbook.svg'),
ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo', 'illustrations/logos/hexo.svg'), ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo', 'illustrations/logos/hexo.svg'),
ProjectTemplate.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman'), ProjectTemplate.new('sse_middleman', 'Static Site Editor/Middleman', _('Middleman project with Static Site Editor support'), 'https://gitlab.com/gitlab-org/project-templates/static-site-editor-middleman'),
ProjectTemplate.new('gitpod_spring_petclinic', 'Gitpod/Spring Petclinic', _('A Gitpod configured Webapplication in Spring and Java'), 'https://gitlab.com/gitlab-org/project-templates/gitpod-spring-petclinic', 'illustrations/logos/gitpod.svg'),
ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'), ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'),
ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'), ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'),
ProjectTemplate.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html', 'illustrations/logos/netlify.svg'), ProjectTemplate.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html', 'illustrations/logos/netlify.svg'),
......
...@@ -1182,6 +1182,9 @@ msgstr "" ...@@ -1182,6 +1182,9 @@ msgstr ""
msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "" msgstr ""
msgid "A Gitpod configured Webapplication in Spring and Java"
msgstr ""
msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr "" msgstr ""
...@@ -2165,7 +2168,7 @@ msgstr "" ...@@ -2165,7 +2168,7 @@ msgstr ""
msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered." msgid "AdminUsers|You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered."
msgstr "" msgstr ""
msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered." msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strongStart}block user%{strongEnd} feature instead. Once you %{strongStart}Delete user%{strongEnd}, it cannot be undone or recovered."
msgstr "" msgstr ""
msgid "AdminUsers|You cannot remove your own admin rights." msgid "AdminUsers|You cannot remove your own admin rights."
...@@ -15453,6 +15456,12 @@ msgstr "" ...@@ -15453,6 +15456,12 @@ msgstr ""
msgid "Mark as done" msgid "Mark as done"
msgstr "" msgstr ""
msgid "Mark as draft"
msgstr ""
msgid "Mark as ready"
msgstr ""
msgid "Mark as resolved" msgid "Mark as resolved"
msgstr "" msgstr ""
...@@ -20255,6 +20264,9 @@ msgstr "" ...@@ -20255,6 +20264,9 @@ msgstr ""
msgid "ProjectTemplates|GitLab Cluster Management" msgid "ProjectTemplates|GitLab Cluster Management"
msgstr "" msgstr ""
msgid "ProjectTemplates|Gitpod/Spring Petclinic"
msgstr ""
msgid "ProjectTemplates|Go Micro" msgid "ProjectTemplates|Go Micro"
msgstr "" msgstr ""
......
...@@ -206,7 +206,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -206,7 +206,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.5.0' do context 'on Knative 0.5.0' do
before do before do
prepare_knative_stubs(knative_05_service(knative_stub_options)) prepare_knative_stubs(knative_05_service(**knative_stub_options))
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
...@@ -214,7 +214,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -214,7 +214,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.6.0' do context 'on Knative 0.6.0' do
before do before do
prepare_knative_stubs(knative_06_service(knative_stub_options)) prepare_knative_stubs(knative_06_service(**knative_stub_options))
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
...@@ -222,7 +222,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -222,7 +222,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.7.0' do context 'on Knative 0.7.0' do
before do before do
prepare_knative_stubs(knative_07_service(knative_stub_options)) prepare_knative_stubs(knative_07_service(**knative_stub_options))
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
...@@ -230,7 +230,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -230,7 +230,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.9.0' do context 'on Knative 0.9.0' do
before do before do
prepare_knative_stubs(knative_09_service(knative_stub_options)) prepare_knative_stubs(knative_09_service(**knative_stub_options))
end end
include_examples 'GET #show with valid data' include_examples 'GET #show with valid data'
...@@ -275,7 +275,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -275,7 +275,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.5.0' do context 'on Knative 0.5.0' do
before do before do
prepare_knative_stubs(knative_05_service(knative_stub_options)) prepare_knative_stubs(knative_05_service(**knative_stub_options))
end end
include_examples 'GET #index with data' include_examples 'GET #index with data'
...@@ -283,7 +283,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -283,7 +283,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.6.0' do context 'on Knative 0.6.0' do
before do before do
prepare_knative_stubs(knative_06_service(knative_stub_options)) prepare_knative_stubs(knative_06_service(**knative_stub_options))
end end
include_examples 'GET #index with data' include_examples 'GET #index with data'
...@@ -291,7 +291,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -291,7 +291,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.7.0' do context 'on Knative 0.7.0' do
before do before do
prepare_knative_stubs(knative_07_service(knative_stub_options)) prepare_knative_stubs(knative_07_service(**knative_stub_options))
end end
include_examples 'GET #index with data' include_examples 'GET #index with data'
...@@ -299,7 +299,7 @@ RSpec.describe Projects::Serverless::FunctionsController do ...@@ -299,7 +299,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.9.0' do context 'on Knative 0.9.0' do
before do before do
prepare_knative_stubs(knative_09_service(knative_stub_options)) prepare_knative_stubs(knative_09_service(**knative_stub_options))
end end
include_examples 'GET #index with data' include_examples 'GET #index with data'
......
...@@ -23,7 +23,15 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do ...@@ -23,7 +23,15 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
expect(container).to have_content("Close #{human_model_name}") expect(container).to have_content("Close #{human_model_name}")
expect(container).to have_content('Report abuse') expect(container).to have_content('Report abuse')
expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.") expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.")
if issuable.is_a?(MergeRequest)
page.within('.js-issuable-close-dropdown') do
expect(page).to have_link('Close merge request')
end
else
expect(container).to have_selector('.close-item.droplab-item-selected') expect(container).to have_selector('.close-item.droplab-item-selected')
end
expect(container).to have_selector('.report-item') expect(container).to have_selector('.report-item')
expect(container).not_to have_selector('.report-item.droplab-item-selected') expect(container).not_to have_selector('.report-item.droplab-item-selected')
expect(container).not_to have_selector('.reopen-item') expect(container).not_to have_selector('.reopen-item')
...@@ -123,7 +131,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do ...@@ -123,7 +131,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
it 'shows only the `Edit` button' do it 'shows only the `Edit` button' do
expect(page).to have_link('Edit') expect(page).to have_link('Edit')
expect(page).not_to have_link('Report abuse') expect(page).to have_link('Report abuse')
expect(page).not_to have_button('Close merge request') expect(page).not_to have_button('Close merge request')
expect(page).not_to have_button('Reopen merge request') expect(page).not_to have_button('Reopen merge request')
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Merge request > User marks merge request as draft', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
project.add_maintainer(user)
sign_in(user)
visit project_merge_request_path(project, merge_request)
end
it 'toggles draft status' do
click_link 'Mark as draft'
expect(page).to have_content("Draft: #{merge_request.title}")
page.within('.detail-page-header-actions') do
click_link 'Mark as ready'
end
expect(page).to have_content(merge_request.title)
end
end
...@@ -15,7 +15,11 @@ RSpec.describe 'User reopens a merge requests', :js do ...@@ -15,7 +15,11 @@ RSpec.describe 'User reopens a merge requests', :js do
end end
it 'reopens a merge request' do it 'reopens a merge request' do
click_button('Reopen merge request', match: :first) find('.js-issuable-close-dropdown .dropdown-toggle').click
click_link('Reopen merge request', match: :first)
wait_for_requests
page.within('.status-box') do page.within('.status-box') do
expect(page).to have_content('Open') expect(page).to have_content('Open')
......
...@@ -35,7 +35,9 @@ RSpec.describe 'Merge request > User resolves Work in Progress', :js do ...@@ -35,7 +35,9 @@ RSpec.describe 'Merge request > User resolves Work in Progress', :js do
expect(page.find('.ci-widget-content')).to have_content("Pipeline ##{pipeline.id}") expect(page.find('.ci-widget-content')).to have_content("Pipeline ##{pipeline.id}")
expect(page).to have_content "This merge request is still a work in progress." expect(page).to have_content "This merge request is still a work in progress."
page.within('.mr-state-widget') do
click_button('Mark as ready') click_button('Mark as ready')
end
wait_for_requests wait_for_requests
......
{ {
"type": "object", "type": "object",
"properties" : { "properties" : {
"title": { "type": "string" },
"state": { "type": "string" }, "state": { "type": "string" },
"merge_status": { "type": "string" }, "merge_status": { "type": "string" },
"source_branch_exists": { "type": "boolean" }, "source_branch_exists": { "type": "boolean" },
......
...@@ -6,11 +6,13 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -6,11 +6,13 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
include ApiHelpers include ApiHelpers
include JavaScriptFixturesHelpers include JavaScriptFixturesHelpers
let_it_be(:admin) { create(:admin) } let_it_be(:admin) { create(:admin, username: 'administrator', email: 'admin@example.gitlab.com') }
let_it_be(:project) { create(:project, :repository, path: 'releases-project') } let_it_be(:namespace) { create(:namespace, path: 'releases-namespace') }
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'releases-project') }
let_it_be(:milestone_12_3) do let_it_be(:milestone_12_3) do
create(:milestone, create(:milestone,
id: 123,
project: project, project: project,
title: '12.3', title: '12.3',
description: 'The 12.3 milestone', description: 'The 12.3 milestone',
...@@ -20,6 +22,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -20,6 +22,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
let_it_be(:milestone_12_4) do let_it_be(:milestone_12_4) do
create(:milestone, create(:milestone,
id: 124,
project: project, project: project,
title: '12.4', title: '12.4',
description: 'The 12.4 milestone', description: 'The 12.4 milestone',
...@@ -45,18 +48,25 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -45,18 +48,25 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
let_it_be(:release) do let_it_be(:release) do
create(:release, create(:release,
:with_evidence,
milestones: [milestone_12_3, milestone_12_4], milestones: [milestone_12_3, milestone_12_4],
project: project, project: project,
tag: 'v1.1', tag: 'v1.1',
name: 'The first release', name: 'The first release',
author: admin,
description: 'Best. Release. **Ever.** :rocket:', description: 'Best. Release. **Ever.** :rocket:',
created_at: Time.zone.parse('2018-12-3'), created_at: Time.zone.parse('2018-12-3'),
released_at: Time.zone.parse('2018-12-10')) released_at: Time.zone.parse('2018-12-10'))
end end
let_it_be(:evidence) do
create(:evidence,
release: release,
collected_at: Time.zone.parse('2018-12-03'))
end
let_it_be(:other_link) do let_it_be(:other_link) do
create(:release_link, create(:release_link,
id: 10,
release: release, release: release,
name: 'linux-amd64 binaries', name: 'linux-amd64 binaries',
filepath: '/binaries/linux-amd64', filepath: '/binaries/linux-amd64',
...@@ -65,6 +75,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -65,6 +75,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
let_it_be(:runbook_link) do let_it_be(:runbook_link) do
create(:release_link, create(:release_link,
id: 11,
release: release, release: release,
name: 'Runbook', name: 'Runbook',
url: "#{release.project.web_url}/runbook", url: "#{release.project.web_url}/runbook",
...@@ -73,6 +84,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -73,6 +84,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
let_it_be(:package_link) do let_it_be(:package_link) do
create(:release_link, create(:release_link,
id: 12,
release: release, release: release,
name: 'Package', name: 'Package',
url: 'https://example.com/package', url: 'https://example.com/package',
...@@ -81,6 +93,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do ...@@ -81,6 +93,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
let_it_be(:image_link) do let_it_be(:image_link) do
create(:release_link, create(:release_link,
id: 13,
release: release, release: release,
name: 'Image', name: 'Image',
url: 'https://example.com/image', url: 'https://example.com/image',
......
...@@ -3,8 +3,6 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -3,8 +3,6 @@ import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import MergeRequest from '~/merge_request'; import MergeRequest from '~/merge_request';
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper';
describe('MergeRequest', () => { describe('MergeRequest', () => {
const test = {}; const test = {};
...@@ -112,66 +110,7 @@ describe('MergeRequest', () => { ...@@ -112,66 +110,7 @@ describe('MergeRequest', () => {
}); });
}); });
describe('class constructor', () => {
beforeEach(() => {
jest.spyOn($, 'ajax').mockImplementation();
});
it('calls .initCloseReopenReport', () => {
jest.spyOn(IssuablesHelper, 'initCloseReopenReport').mockImplementation(() => {});
new MergeRequest(); // eslint-disable-line no-new
expect(IssuablesHelper.initCloseReopenReport).toHaveBeenCalled();
});
it('calls .initDroplab', () => {
const container = {
querySelector: jest.fn().mockName('container.querySelector'),
};
const dropdownTrigger = {};
const dropdownList = {};
const button = {};
jest.spyOn(CloseReopenReportToggle.prototype, 'initDroplab').mockImplementation(() => {});
jest.spyOn(document, 'querySelector').mockReturnValue(container);
container.querySelector
.mockReturnValueOnce(dropdownTrigger)
.mockReturnValueOnce(dropdownList)
.mockReturnValueOnce(button);
new MergeRequest(); // eslint-disable-line no-new
expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
});
});
describe('hideCloseButton', () => { describe('hideCloseButton', () => {
describe('merge request of another user', () => {
beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html');
test.el = document.querySelector('.js-issuable-actions');
new MergeRequest(); // eslint-disable-line no-new
MergeRequest.hideCloseButton();
});
it('hides the dropdown close item and selects the next item', () => {
const closeItem = test.el.querySelector('li.close-item');
const smallCloseItem = test.el.querySelector('.js-close-item');
const reportItem = test.el.querySelector('li.report-item');
expect(closeItem).toHaveClass('hidden');
expect(smallCloseItem).toHaveClass('hidden');
expect(reportItem).toHaveClass('droplab-item-selected');
expect(reportItem).not.toHaveClass('hidden');
});
});
describe('merge request of current_user', () => { describe('merge request of current_user', () => {
beforeEach(() => { beforeEach(() => {
loadFixtures('merge_requests/merge_request_of_current_user.html'); loadFixtures('merge_requests/merge_request_of_current_user.html');
...@@ -180,10 +119,8 @@ describe('MergeRequest', () => { ...@@ -180,10 +119,8 @@ describe('MergeRequest', () => {
}); });
it('hides the close button', () => { it('hides the close button', () => {
const closeButton = test.el.querySelector('.btn-close');
const smallCloseItem = test.el.querySelector('.js-close-item'); const smallCloseItem = test.el.querySelector('.js-close-item');
expect(closeButton).toHaveClass('hidden');
expect(smallCloseItem).toHaveClass('hidden'); expect(smallCloseItem).toHaveClass('hidden');
}); });
}); });
......
...@@ -5,115 +5,123 @@ Object { ...@@ -5,115 +5,123 @@ Object {
"data": Array [ "data": Array [
Object { Object {
"_links": Object { "_links": Object {
"editUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit", "editUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/edit",
"issuesUrl": null, "issuesUrl": "http://localhost/releases-namespace/releases-project/-/issues?release_tag=v1.1&scope=all&state=opened",
"mergeRequestsUrl": null, "mergeRequestsUrl": "http://localhost/releases-namespace/releases-project/-/merge_requests?release_tag=v1.1&scope=all&state=opened",
"self": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10", "self": "http://localhost/releases-namespace/releases-project/-/releases/v1.1",
"selfUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10", "selfUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1",
}, },
"assets": Object { "assets": Object {
"count": 7, "count": 8,
"links": Array [ "links": Array [
Object { Object {
"directAssetUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook", "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-3",
"external": true, "external": true,
"id": "gid://gitlab/Releases::Link/69", "id": "gid://gitlab/Releases::Link/13",
"linkType": "other", "linkType": "image",
"name": "An example link", "name": "Image",
"url": "https://example.com/link", "url": "https://example.com/image",
}, },
Object { Object {
"directAssetUrl": "https://example.com/package", "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-2",
"external": true, "external": true,
"id": "gid://gitlab/Releases::Link/68", "id": "gid://gitlab/Releases::Link/12",
"linkType": "package", "linkType": "package",
"name": "An example package link", "name": "Package",
"url": "https://example.com/package", "url": "https://example.com/package",
}, },
Object { Object {
"directAssetUrl": "https://example.com/image", "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-1",
"external": false,
"id": "gid://gitlab/Releases::Link/11",
"linkType": "runbook",
"name": "Runbook",
"url": "http://localhost/releases-namespace/releases-project/runbook",
},
Object {
"directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/linux-amd64",
"external": true, "external": true,
"id": "gid://gitlab/Releases::Link/67", "id": "gid://gitlab/Releases::Link/10",
"linkType": "image", "linkType": "other",
"name": "An example image", "name": "linux-amd64 binaries",
"url": "https://example.com/image", "url": "https://downloads.example.com/bin/gitlab-linux-amd64",
}, },
], ],
"sources": Array [ "sources": Array [
Object { Object {
"format": "zip", "format": "zip",
"url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip", "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.zip",
}, },
Object { Object {
"format": "tar.gz", "format": "tar.gz",
"url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz", "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar.gz",
}, },
Object { Object {
"format": "tar.bz2", "format": "tar.bz2",
"url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2", "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar.bz2",
}, },
Object { Object {
"format": "tar", "format": "tar",
"url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar", "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar",
}, },
], ],
}, },
"author": Object { "author": Object {
"avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", "avatarUrl": "https://www.gravatar.com/avatar/16f8e2050ce10180ca571c2eb19cfce2?s=80&d=identicon",
"username": "root", "username": "administrator",
"webUrl": "http://0.0.0.0:3000/root", "webUrl": "http://localhost/administrator",
}, },
"commit": Object { "commit": Object {
"shortId": "92e7ea2e", "shortId": "b83d6e39",
"title": "Testing a change.", "title": "Merge branch 'branch-merged' into 'master'",
}, },
"commitPath": "http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7", "commitPath": "http://localhost/releases-namespace/releases-project/-/commit/b83d6e391c22777fca1ed3012fce84f633d7fed0",
"descriptionHtml": "<p data-sourcepos=\\"1:1-1:24\\" dir=\\"auto\\">This is version <strong>1.0</strong>!</p>", "descriptionHtml": "<p data-sourcepos=\\"1:1-1:33\\" dir=\\"auto\\">Best. Release. <strong>Ever.</strong> <gl-emoji title=\\"rocket\\" data-name=\\"rocket\\" data-unicode-version=\\"6.0\\">🚀</gl-emoji></p>",
"evidences": Array [ "evidences": Array [
Object { Object {
"collectedAt": "2020-08-21T20:15:19Z", "collectedAt": "2018-12-03T00:00:00Z",
"filepath": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json", "filepath": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/evidences/1.json",
"sha": "22bde8e8b93d870a29ddc339287a1fbb598f45d1396d", "sha": "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d",
}, },
], ],
"milestones": Array [ "milestones": Array [
Object { Object {
"description": "", "description": "The 12.4 milestone",
"id": "gid://gitlab/Milestone/60", "id": "gid://gitlab/Milestone/124",
"issueStats": Object { "issueStats": Object {
"closed": 0, "closed": 1,
"total": 0, "total": 4,
}, },
"stats": undefined, "stats": undefined,
"title": "12.4", "title": "12.4",
"webPath": undefined, "webPath": undefined,
"webUrl": "/root/release-test/-/milestones/2", "webUrl": "/releases-namespace/releases-project/-/milestones/2",
}, },
Object { Object {
"description": "Milestone 12.3", "description": "The 12.3 milestone",
"id": "gid://gitlab/Milestone/59", "id": "gid://gitlab/Milestone/123",
"issueStats": Object { "issueStats": Object {
"closed": 1, "closed": 3,
"total": 2, "total": 5,
}, },
"stats": undefined, "stats": undefined,
"title": "12.3", "title": "12.3",
"webPath": undefined, "webPath": undefined,
"webUrl": "/root/release-test/-/milestones/1", "webUrl": "/releases-namespace/releases-project/-/milestones/1",
}, },
], ],
"name": "Release 1.0", "name": "The first release",
"releasedAt": "2020-08-21T20:15:18Z", "releasedAt": "2018-12-10T00:00:00Z",
"tagName": "v5.10", "tagName": "v1.1",
"tagPath": "/root/release-test/-/tags/v5.10", "tagPath": "/releases-namespace/releases-project/-/tags/v1.1",
"upcomingRelease": false, "upcomingRelease": true,
}, },
], ],
"paginationInfo": Object { "paginationInfo": Object {
"endCursor": "eyJpZCI6IjMiLCJyZWxlYXNlZF9hdCI6IjIwMjAtMDctMDkgMjA6MTE6MzMuODA0OTYxMDAwIFVUQyJ9", "endCursor": "eyJpZCI6IjEiLCJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyJ9",
"hasNextPage": true, "hasNextPage": false,
"hasPreviousPage": false, "hasPreviousPage": false,
"startCursor": "eyJpZCI6IjQ0IiwicmVsZWFzZWRfYXQiOiIyMDMwLTAzLTE1IDA4OjAwOjAwLjAwMDAwMDAwMCBVVEMifQ", "startCursor": "eyJpZCI6IjEiLCJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyJ9",
}, },
} }
`; `;
...@@ -15,139 +15,3 @@ export const pageInfoHeadersWithPagination = { ...@@ -15,139 +15,3 @@ export const pageInfoHeadersWithPagination = {
'X-TOTAL': '21', 'X-TOTAL': '21',
'X-TOTAL-PAGES': '2', 'X-TOTAL-PAGES': '2',
}; };
export const graphqlReleasesResponse = {
data: {
project: {
releases: {
count: 39,
nodes: [
{
name: 'Release 1.0',
tagName: 'v5.10',
tagPath: '/root/release-test/-/tags/v5.10',
descriptionHtml:
'<p data-sourcepos="1:1-1:24" dir="auto">This is version <strong>1.0</strong>!</p>',
releasedAt: '2020-08-21T20:15:18Z',
upcomingRelease: false,
assets: {
count: 7,
sources: {
nodes: [
{
format: 'zip',
url:
'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip',
},
{
format: 'tar.gz',
url:
'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz',
},
{
format: 'tar.bz2',
url:
'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2',
},
{
format: 'tar',
url:
'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar',
},
],
},
links: {
nodes: [
{
id: 'gid://gitlab/Releases::Link/69',
name: 'An example link',
url: 'https://example.com/link',
directAssetUrl:
'http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook',
linkType: 'OTHER',
external: true,
},
{
id: 'gid://gitlab/Releases::Link/68',
name: 'An example package link',
url: 'https://example.com/package',
directAssetUrl: 'https://example.com/package',
linkType: 'PACKAGE',
external: true,
},
{
id: 'gid://gitlab/Releases::Link/67',
name: 'An example image',
url: 'https://example.com/image',
directAssetUrl: 'https://example.com/image',
linkType: 'IMAGE',
external: true,
},
],
},
},
evidences: {
nodes: [
{
filepath:
'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json',
collectedAt: '2020-08-21T20:15:19Z',
sha: '22bde8e8b93d870a29ddc339287a1fbb598f45d1396d',
},
],
},
links: {
editUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit',
issuesUrl: null,
mergeRequestsUrl: null,
selfUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10',
},
commit: {
sha: '92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7',
webUrl:
'http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7',
title: 'Testing a change.',
},
author: {
webUrl: 'http://0.0.0.0:3000/root',
avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png',
username: 'root',
},
milestones: {
nodes: [
{
id: 'gid://gitlab/Milestone/60',
title: '12.4',
description: '',
webPath: '/root/release-test/-/milestones/2',
stats: {
totalIssuesCount: 0,
closedIssuesCount: 0,
},
},
{
id: 'gid://gitlab/Milestone/59',
title: '12.3',
description: 'Milestone 12.3',
webPath: '/root/release-test/-/milestones/1',
stats: {
totalIssuesCount: 2,
closedIssuesCount: 1,
},
},
],
},
},
],
pageInfo: {
startCursor:
'eyJpZCI6IjQ0IiwicmVsZWFzZWRfYXQiOiIyMDMwLTAzLTE1IDA4OjAwOjAwLjAwMDAwMDAwMCBVVEMifQ',
hasPreviousPage: false,
hasNextPage: true,
endCursor:
'eyJpZCI6IjMiLCJyZWxlYXNlZF9hdCI6IjIwMjAtMDctMDkgMjA6MTE6MzMuODA0OTYxMDAwIFVUQyJ9',
},
},
},
},
};
...@@ -16,16 +16,17 @@ import { ...@@ -16,16 +16,17 @@ import {
parseIntPagination, parseIntPagination,
convertObjectPropsToCamelCase, convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils'; } from '~/lib/utils/common_utils';
import { import { pageInfoHeadersWithoutPagination } from '../../../mock_data';
pageInfoHeadersWithoutPagination,
graphqlReleasesResponse as originalGraphqlReleasesResponse,
} from '../../../mock_data';
import allReleasesQuery from '~/releases/queries/all_releases.query.graphql'; import allReleasesQuery from '~/releases/queries/all_releases.query.graphql';
import { PAGE_SIZE } from '~/releases/constants'; import { PAGE_SIZE } from '~/releases/constants';
const originalRelease = getJSONFixture('api/releases/release.json'); const originalRelease = getJSONFixture('api/releases/release.json');
const originalReleases = [originalRelease]; const originalReleases = [originalRelease];
const originalGraphqlReleasesResponse = getJSONFixture(
'graphql/releases/queries/all_releases.query.graphql.json',
);
describe('Releases State actions', () => { describe('Releases State actions', () => {
let mockedState; let mockedState;
let releases; let releases;
......
...@@ -3,12 +3,16 @@ import createState from '~/releases/stores/modules/list/state'; ...@@ -3,12 +3,16 @@ import createState from '~/releases/stores/modules/list/state';
import mutations from '~/releases/stores/modules/list/mutations'; import mutations from '~/releases/stores/modules/list/mutations';
import * as types from '~/releases/stores/modules/list/mutation_types'; import * as types from '~/releases/stores/modules/list/mutation_types';
import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { pageInfoHeadersWithoutPagination, graphqlReleasesResponse } from '../../../mock_data'; import { pageInfoHeadersWithoutPagination } from '../../../mock_data';
import { convertGraphQLResponse } from '~/releases/util'; import { convertGraphQLResponse } from '~/releases/util';
const originalRelease = getJSONFixture('api/releases/release.json'); const originalRelease = getJSONFixture('api/releases/release.json');
const originalReleases = [originalRelease]; const originalReleases = [originalRelease];
const graphqlReleasesResponse = getJSONFixture(
'graphql/releases/queries/all_releases.query.graphql.json',
);
describe('Releases Store Mutations', () => { describe('Releases Store Mutations', () => {
let stateCopy; let stateCopy;
let restPageInfo; let restPageInfo;
......
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { getJSONFixture } from 'helpers/fixtures';
import { releaseToApiJson, apiJsonToRelease, convertGraphQLResponse } from '~/releases/util'; import { releaseToApiJson, apiJsonToRelease, convertGraphQLResponse } from '~/releases/util';
import { graphqlReleasesResponse as originalGraphqlReleasesResponse } from './mock_data';
const originalGraphqlReleasesResponse = getJSONFixture(
'graphql/releases/queries/all_releases.query.graphql.json',
);
describe('releases/util.js', () => { describe('releases/util.js', () => {
describe('releaseToApiJson', () => { describe('releaseToApiJson', () => {
......
...@@ -492,19 +492,6 @@ describe('ReadyToMerge', () => { ...@@ -492,19 +492,6 @@ describe('ReadyToMerge', () => {
}); });
}); });
it('hides close button', done => {
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
vm.handleMergePolling(() => {}, () => {});
setImmediate(() => {
expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy();
done();
});
});
it('updates merge request count badge', done => { it('updates merge request count badge', done => {
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged')); jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
......
...@@ -120,7 +120,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do ...@@ -120,7 +120,7 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
described_class.new(merge_request.merge_request_diff, described_class.new(merge_request.merge_request_diff,
batch_page, batch_page,
batch_size, batch_size,
collection_default_args) **collection_default_args)
end end
end end
......
...@@ -8,6 +8,7 @@ RSpec.describe Gitlab::Lfs::Client do ...@@ -8,6 +8,7 @@ RSpec.describe Gitlab::Lfs::Client do
let(:password) { 'password' } let(:password) { 'password' }
let(:credentials) { { user: username, password: password, auth_method: 'password' } } let(:credentials) { { user: username, password: password, auth_method: 'password' } }
let(:git_lfs_content_type) { 'application/vnd.git-lfs+json' } let(:git_lfs_content_type) { 'application/vnd.git-lfs+json' }
let(:git_lfs_user_agent) { "GitLab #{Gitlab::VERSION} LFS client" }
let(:basic_auth_headers) do let(:basic_auth_headers) do
{ 'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}" } { 'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}" }
...@@ -91,7 +92,8 @@ RSpec.describe Gitlab::Lfs::Client do ...@@ -91,7 +92,8 @@ RSpec.describe Gitlab::Lfs::Client do
headers = { headers = {
'Accept' => git_lfs_content_type, 'Accept' => git_lfs_content_type,
'Content-Type' => git_lfs_content_type 'Content-Type' => git_lfs_content_type,
'User-Agent' => git_lfs_user_agent
}.merge(headers) }.merge(headers)
stub_request(:post, base_url + '/info/lfs/objects/batch').with(body: body, headers: headers) stub_request(:post, base_url + '/info/lfs/objects/batch').with(body: body, headers: headers)
...@@ -156,7 +158,8 @@ RSpec.describe Gitlab::Lfs::Client do ...@@ -156,7 +158,8 @@ RSpec.describe Gitlab::Lfs::Client do
def stub_upload(object:, headers:) def stub_upload(object:, headers:)
headers = { headers = {
'Content-Type' => 'application/octet-stream', 'Content-Type' => 'application/octet-stream',
'Content-Length' => object.size.to_s 'Content-Length' => object.size.to_s,
'User-Agent' => git_lfs_user_agent
}.merge(headers) }.merge(headers)
stub_request(:put, upload_action['href']).with( stub_request(:put, upload_action['href']).with(
...@@ -209,7 +212,8 @@ RSpec.describe Gitlab::Lfs::Client do ...@@ -209,7 +212,8 @@ RSpec.describe Gitlab::Lfs::Client do
def stub_verify(object:, headers:) def stub_verify(object:, headers:)
headers = { headers = {
'Accept' => git_lfs_content_type, 'Accept' => git_lfs_content_type,
'Content-Type' => git_lfs_content_type 'Content-Type' => git_lfs_content_type,
'User-Agent' => git_lfs_user_agent
}.merge(headers) }.merge(headers)
stub_request(:post, verify_action['href']).with( stub_request(:post, verify_action['href']).with(
......
...@@ -8,9 +8,9 @@ RSpec.describe Gitlab::ProjectTemplate do ...@@ -8,9 +8,9 @@ RSpec.describe Gitlab::ProjectTemplate do
expected = %w[ expected = %w[
rails spring express iosswift dotnetcore android rails spring express iosswift dotnetcore android
gomicro gatsby hugo jekyll plainhtml gitbook gomicro gatsby hugo jekyll plainhtml gitbook
hexo sse_middleman nfhugo nfjekyll nfplainhtml hexo sse_middleman gitpod_spring_petclinic nfhugo
nfgitbook nfhexo salesforcedx serverless_framework nfjekyll nfplainhtml nfgitbook nfhexo salesforcedx
jsonnet cluster_management serverless_framework jsonnet cluster_management
] ]
expect(described_class.all).to be_an(Array) expect(described_class.all).to be_an(Array)
......
...@@ -84,11 +84,9 @@ RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do ...@@ -84,11 +84,9 @@ RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do
before do before do
stub_cloud_platform_get_zone_cluster( stub_cloud_platform_get_zone_cluster(
provider.gcp_project_id, provider.zone, cluster.name, provider.gcp_project_id, provider.zone, cluster.name,
{
endpoint: endpoint, endpoint: endpoint,
username: username, username: username,
password: password password: password
}
) )
stub_kubeclient_discover(api_url) stub_kubeclient_discover(api_url)
...@@ -101,11 +99,9 @@ RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do ...@@ -101,11 +99,9 @@ RSpec.describe Clusters::Gcp::FinalizeCreationService, '#execute' do
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: secret_name, metadata_name: secret_name,
token: Base64.encode64(token), token: Base64.encode64(token),
namespace: 'default' namespace: 'default'
}
) )
stub_kubeclient_put_cluster_role_binding(api_url, 'gitlab-admin') stub_kubeclient_put_cluster_role_binding(api_url, 'gitlab-admin')
......
...@@ -26,27 +26,21 @@ RSpec.describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do ...@@ -26,27 +26,21 @@ RSpec.describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: "#{namespace}-token", metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'), token: Base64.encode64('sample-token'),
namespace: namespace namespace: namespace
}
) )
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: 'istio-ingressgateway-ca-certs', metadata_name: 'istio-ingressgateway-ca-certs',
namespace: 'istio-system' namespace: 'istio-system'
}
) )
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: 'istio-ingressgateway-certs', metadata_name: 'istio-ingressgateway-certs',
namespace: 'istio-system' namespace: 'istio-system'
}
) )
stub_kubeclient_put_secret(api_url, 'istio-ingressgateway-ca-certs', namespace: 'istio-system') stub_kubeclient_put_secret(api_url, 'istio-ingressgateway-ca-certs', namespace: 'istio-system')
......
...@@ -41,11 +41,9 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' ...@@ -41,11 +41,9 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute'
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: "#{namespace}-token", metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'), token: Base64.encode64('sample-token'),
namespace: namespace namespace: namespace
}
) )
end end
......
...@@ -31,11 +31,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do ...@@ -31,11 +31,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
before do before do
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: service_account_token_name, metadata_name: service_account_token_name,
namespace: namespace, namespace: namespace,
token: token token: token
}
) )
end end
...@@ -54,11 +52,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do ...@@ -54,11 +52,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
before do before do
stub_kubeclient_get_secret_not_found_then_found( stub_kubeclient_get_secret_not_found_then_found(
api_url, api_url,
{
metadata_name: service_account_token_name, metadata_name: service_account_token_name,
namespace: namespace, namespace: namespace,
token: token token: token
}
) )
end end
...@@ -96,11 +92,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do ...@@ -96,11 +92,9 @@ RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do
before do before do
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
{
metadata_name: service_account_token_name, metadata_name: service_account_token_name,
namespace: namespace, namespace: namespace,
token: nil token: nil
}
) )
end end
......
...@@ -24,7 +24,7 @@ module GoogleApi ...@@ -24,7 +24,7 @@ module GoogleApi
def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options) def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options)
WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)) WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id))
.to_return(cloud_platform_response(cloud_platform_cluster_body(options))) .to_return(cloud_platform_response(cloud_platform_cluster_body(**options)))
end end
def stub_cloud_platform_get_zone_cluster_error(project_id, zone, cluster_id) def stub_cloud_platform_get_zone_cluster_error(project_id, zone, cluster_id)
......
...@@ -153,7 +153,7 @@ module KubernetesHelpers ...@@ -153,7 +153,7 @@ module KubernetesHelpers
options[:name] ||= "kubetest" options[:name] ||= "kubetest"
options[:domain] ||= "example.com" options[:domain] ||= "example.com"
options[:response] ||= kube_response(kube_knative_services_body(options)) options[:response] ||= kube_response(kube_knative_services_body(**options))
stub_kubeclient_discover(service.api_url) stub_kubeclient_discover(service.api_url)
...@@ -167,7 +167,7 @@ module KubernetesHelpers ...@@ -167,7 +167,7 @@ module KubernetesHelpers
options[:namespace] ||= "default" options[:namespace] ||= "default"
WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}") WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
.to_return(kube_response(kube_v1_secret_body(options))) .to_return(kube_response(kube_v1_secret_body(**options)))
end end
def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default', status: 404) def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default', status: 404)
...@@ -517,7 +517,7 @@ module KubernetesHelpers ...@@ -517,7 +517,7 @@ module KubernetesHelpers
def kube_knative_services_body(**options) def kube_knative_services_body(**options)
{ {
"kind" => "List", "kind" => "List",
"items" => [knative_09_service(options)] "items" => [knative_09_service(**options)]
} }
end end
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'diff statistics' do |test_include_stats_flag: true| RSpec.shared_examples 'diff statistics' do |test_include_stats_flag: true|
subject { described_class.new(diffable, collection_default_args) } subject { described_class.new(diffable, **collection_default_args) }
def stub_stats_find_by_path(path, stats_mock) def stub_stats_find_by_path(path, stats_mock)
expect_next_instance_of(Gitlab::Git::DiffStatsCollection) do |collection| expect_next_instance_of(Gitlab::Git::DiffStatsCollection) do |collection|
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment