Group-level new issue & MR using previously selected project
This commit is contained in:
parent
5f30350c5b
commit
0532bff6d4
9 changed files with 252 additions and 19 deletions
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
|
||||
import Api from './api';
|
||||
import ProjectSelectComboButton from './project_select_combo_button';
|
||||
|
||||
(function() {
|
||||
this.ProjectSelect = (function() {
|
||||
|
@ -58,7 +59,8 @@ import Api from './api';
|
|||
if (this.includeGroups) {
|
||||
placeholder += " or group";
|
||||
}
|
||||
return $(select).select2({
|
||||
|
||||
$(select).select2({
|
||||
placeholder: placeholder,
|
||||
minimumInputLength: 0,
|
||||
query: (function(_this) {
|
||||
|
@ -96,21 +98,18 @@ import Api from './api';
|
|||
};
|
||||
})(this),
|
||||
id: function(project) {
|
||||
return project.web_url;
|
||||
return JSON.stringify({
|
||||
name: project.name,
|
||||
url: project.web_url,
|
||||
});
|
||||
},
|
||||
text: function(project) {
|
||||
return project.name_with_namespace || project.name;
|
||||
},
|
||||
dropdownCssClass: "ajax-project-dropdown"
|
||||
});
|
||||
});
|
||||
|
||||
$('.new-project-item-select-button').on('click', function() {
|
||||
$('.project-item-select', this.parentNode).select2('open');
|
||||
});
|
||||
|
||||
$('.project-item-select').on('click', function() {
|
||||
window.location = `${$(this).val()}/${this.dataset.relativePath}`;
|
||||
return new ProjectSelectComboButton(select);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
85
app/assets/javascripts/project_select_combo_button.js
Normal file
85
app/assets/javascripts/project_select_combo_button.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
import AccessorUtilities from './lib/utils/accessor';
|
||||
|
||||
export default class ProjectSelectComboButton {
|
||||
constructor(select) {
|
||||
this.projectSelectInput = $(select);
|
||||
this.newItemBtn = $('.new-project-item-link');
|
||||
this.newItemBtnBaseText = this.newItemBtn.data('label');
|
||||
this.itemType = this.deriveItemTypeFromLabel();
|
||||
this.groupId = this.projectSelectInput.data('groupId');
|
||||
|
||||
this.bindEvents();
|
||||
this.initLocalStorage();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.projectSelectInput.siblings('.new-project-item-select-button')
|
||||
.on('click', this.openDropdown);
|
||||
|
||||
this.projectSelectInput.on('change', () => this.selectProject());
|
||||
}
|
||||
|
||||
initLocalStorage() {
|
||||
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
|
||||
if (localStorageIsSafe) {
|
||||
const itemTypeKebabed = this.newItemBtnBaseText.toLowerCase().split(' ').join('-');
|
||||
|
||||
this.localStorageKey = ['group', this.groupId, itemTypeKebabed, 'recent-project'].join('-');
|
||||
this.setBtnTextFromLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
openDropdown() {
|
||||
$(this).siblings('.project-item-select').select2('open');
|
||||
}
|
||||
|
||||
selectProject() {
|
||||
const selectedProjectData = JSON.parse(this.projectSelectInput.val());
|
||||
const projectUrl = `${selectedProjectData.url}/${this.projectSelectInput.data('relativePath')}`;
|
||||
const projectName = selectedProjectData.name;
|
||||
|
||||
const projectMeta = {
|
||||
url: projectUrl,
|
||||
name: projectName,
|
||||
};
|
||||
|
||||
this.setNewItemBtnAttributes(projectMeta);
|
||||
this.setProjectInLocalStorage(projectMeta);
|
||||
}
|
||||
|
||||
setBtnTextFromLocalStorage() {
|
||||
const cachedProjectData = this.getProjectFromLocalStorage();
|
||||
|
||||
this.setNewItemBtnAttributes(cachedProjectData);
|
||||
}
|
||||
|
||||
setNewItemBtnAttributes(project) {
|
||||
if (project) {
|
||||
this.newItemBtn.attr('href', project.url);
|
||||
this.newItemBtn.text(`${this.newItemBtnBaseText} in ${project.name}`);
|
||||
this.newItemBtn.enable();
|
||||
} else {
|
||||
this.newItemBtn.text(`Select project to create ${this.itemType}`);
|
||||
this.newItemBtn.disable();
|
||||
}
|
||||
}
|
||||
|
||||
deriveItemTypeFromLabel() {
|
||||
// label is either 'New issue' or 'New merge request'
|
||||
return this.newItemBtnBaseText.split(' ').slice(1).join(' ');
|
||||
}
|
||||
|
||||
getProjectFromLocalStorage() {
|
||||
const projectString = localStorage.getItem(this.localStorageKey);
|
||||
|
||||
return JSON.parse(projectString);
|
||||
}
|
||||
|
||||
setProjectInLocalStorage(projectMeta) {
|
||||
const projectString = JSON.stringify(projectMeta);
|
||||
|
||||
localStorage.setItem(this.localStorageKey, projectString);
|
||||
}
|
||||
}
|
||||
|
|
@ -251,7 +251,6 @@
|
|||
|
||||
// Applies on /dashboard/issues
|
||||
.project-item-select-holder {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
@ -283,6 +282,31 @@
|
|||
}
|
||||
}
|
||||
|
||||
.project-item-select-holder.btn-group {
|
||||
display: flex;
|
||||
max-width: 350px;
|
||||
overflow: hidden;
|
||||
|
||||
@media(max-width: $screen-xs-max) {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.new-project-item-link {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.new-project-item-select-button {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-project-item-select-button .fa-caret-down {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
width: 100%;
|
||||
background: $gray-light;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
- if any_projects?(@projects)
|
||||
.project-item-select-holder
|
||||
.project-item-select-holder.btn-group.pull-right
|
||||
%a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label] } }
|
||||
= icon('spinner spin')
|
||||
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
|
||||
%a.btn.btn-new.new-project-item-select-button
|
||||
= local_assigns[:label]
|
||||
%button.btn.btn-new.new-project-item-select-button
|
||||
= icon('caret-down')
|
||||
|
|
4
changelogs/unreleased/group-new-issue.yml
Normal file
4
changelogs/unreleased/group-new-issue.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Cache recent projects for group-level new resource creation.
|
||||
merge_request: !13058
|
||||
author:
|
|
@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'shows the new issue page', :js do
|
||||
it 'shows the new issue page', js: true do
|
||||
find('.new-project-item-select-button').trigger('click')
|
||||
wait_for_requests
|
||||
find('.select2-results li').click
|
||||
|
||||
expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new")
|
||||
wait_for_requests
|
||||
|
||||
project_path = "/#{project.path_with_namespace}"
|
||||
project_json = { name: project.name_with_namespace, url: project_path }.to_json
|
||||
|
||||
# similate selection, and prevent overlap by dropdown menu
|
||||
execute_script("$('.project-item-select').val('#{project_json}').trigger('change');")
|
||||
execute_script("$('#select2-drop-mask').remove();")
|
||||
|
||||
find('.new-project-item-link').trigger('click')
|
||||
|
||||
expect(page).to have_current_path("#{project_path}/issues/new")
|
||||
|
||||
page.within('#content-body') do
|
||||
expect(page).to have_selector('.issue-form')
|
||||
|
|
|
@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do
|
|||
|
||||
it 'should show a new merge request button' do
|
||||
within '.empty-state' do
|
||||
expect(page).to have_content('New merge request')
|
||||
expect(page).to have_content('create merge request')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do
|
|||
|
||||
it 'should not show a new merge request button' do
|
||||
within '.empty-state' do
|
||||
expect(page).not_to have_link('New merge request')
|
||||
expect(page).not_to have_link('create merge request')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.project-item-select-holder
|
||||
%input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } }
|
||||
%a.new-project-item-link{ data: { label: 'New issue' }, href: ''}
|
||||
%i.fa.fa-spinner.spin
|
||||
%a.new-project-item-select-button
|
||||
%i.fa.fa-caret-down
|
105
spec/javascripts/project_select_combo_button_spec.js
Normal file
105
spec/javascripts/project_select_combo_button_spec.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
import ProjectSelectComboButton from '~/project_select_combo_button';
|
||||
|
||||
const fixturePath = 'static/project_select_combo_button.html.raw';
|
||||
|
||||
describe('Project Select Combo Button', function () {
|
||||
preloadFixtures(fixturePath);
|
||||
|
||||
beforeEach(function () {
|
||||
this.defaults = {
|
||||
label: 'Select project to create issue',
|
||||
groupId: 12345,
|
||||
projectMeta: {
|
||||
name: 'My Cool Project',
|
||||
url: 'http://mycoolproject.com',
|
||||
},
|
||||
newProjectMeta: {
|
||||
name: 'My Other Cool Project',
|
||||
url: 'http://myothercoolproject.com',
|
||||
},
|
||||
localStorageKey: 'group-12345-new-issue-recent-project',
|
||||
relativePath: 'issues/new',
|
||||
};
|
||||
|
||||
loadFixtures(fixturePath);
|
||||
|
||||
this.newItemBtn = document.querySelector('.new-project-item-link');
|
||||
this.projectSelectInput = document.querySelector('.project-item-select');
|
||||
});
|
||||
|
||||
describe('on page load when localStorage is empty', function () {
|
||||
beforeEach(function () {
|
||||
this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
|
||||
});
|
||||
|
||||
it('newItemBtn is disabled', function () {
|
||||
expect(this.newItemBtn.hasAttribute('disabled')).toBe(true);
|
||||
expect(this.newItemBtn.classList.contains('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('newItemBtn href is null', function () {
|
||||
expect(this.newItemBtn.getAttribute('href')).toBe('');
|
||||
});
|
||||
|
||||
it('newItemBtn text is the plain default label', function () {
|
||||
expect(this.newItemBtn.textContent).toBe(this.defaults.label);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on page load when localStorage is filled', function () {
|
||||
beforeEach(function () {
|
||||
window.localStorage
|
||||
.setItem(this.defaults.localStorageKey, JSON.stringify(this.defaults.projectMeta));
|
||||
this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
|
||||
});
|
||||
|
||||
it('newItemBtn is not disabled', function () {
|
||||
expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
|
||||
expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('newItemBtn href is correctly set', function () {
|
||||
expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url);
|
||||
});
|
||||
|
||||
it('newItemBtn text is the cached label', function () {
|
||||
expect(this.newItemBtn.textContent)
|
||||
.toBe(`New issue in ${this.defaults.projectMeta.name}`);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.localStorage.clear();
|
||||
});
|
||||
});
|
||||
|
||||
describe('after selecting a new project', function () {
|
||||
beforeEach(function () {
|
||||
this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
|
||||
|
||||
// mock the effect of selecting an item from the projects dropdown (select2)
|
||||
$('.project-item-select')
|
||||
.val(JSON.stringify(this.defaults.newProjectMeta))
|
||||
.trigger('change');
|
||||
});
|
||||
|
||||
it('newItemBtn is not disabled', function () {
|
||||
expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
|
||||
expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('newItemBtn href is correctly set', function () {
|
||||
expect(this.newItemBtn.getAttribute('href'))
|
||||
.toBe('http://myothercoolproject.com/issues/new');
|
||||
});
|
||||
|
||||
it('newItemBtn text is the selected project label', function () {
|
||||
expect(this.newItemBtn.textContent)
|
||||
.toBe(`New issue in ${this.defaults.newProjectMeta.name}`);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.localStorage.clear();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in a new issue