Commit 0532bff6 authored by Bryce Johnson's avatar Bryce Johnson Committed by Clement Ho

Group-level new issue & MR using previously selected project

parent 5f30350c
/* 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 */ /* 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 Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
(function() { (function() {
this.ProjectSelect = (function() { this.ProjectSelect = (function() {
...@@ -58,7 +59,8 @@ import Api from './api'; ...@@ -58,7 +59,8 @@ import Api from './api';
if (this.includeGroups) { if (this.includeGroups) {
placeholder += " or group"; placeholder += " or group";
} }
return $(select).select2({
$(select).select2({
placeholder: placeholder, placeholder: placeholder,
minimumInputLength: 0, minimumInputLength: 0,
query: (function(_this) { query: (function(_this) {
...@@ -96,21 +98,18 @@ import Api from './api'; ...@@ -96,21 +98,18 @@ import Api from './api';
}; };
})(this), })(this),
id: function(project) { id: function(project) {
return project.web_url; return JSON.stringify({
name: project.name,
url: project.web_url,
});
}, },
text: function(project) { text: function(project) {
return project.name_with_namespace || project.name; return project.name_with_namespace || project.name;
}, },
dropdownCssClass: "ajax-project-dropdown" 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() { return new ProjectSelectComboButton(select);
window.location = `${$(this).val()}/${this.dataset.relativePath}`;
}); });
} }
......
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 @@ ...@@ -251,7 +251,6 @@
// Applies on /dashboard/issues // Applies on /dashboard/issues
.project-item-select-holder { .project-item-select-holder {
display: block;
margin: 0; margin: 0;
} }
} }
...@@ -283,6 +282,31 @@ ...@@ -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 { .layout-nav {
width: 100%; width: 100%;
background: $gray-light; background: $gray-light;
......
- if any_projects?(@projects) - 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] = 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 %button.btn.btn-new.new-project-item-select-button
= local_assigns[:label]
= icon('caret-down') = icon('caret-down')
---
title: Cache recent projects for group-level new resource creation.
merge_request: !13058
author:
...@@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do ...@@ -79,12 +79,21 @@ RSpec.describe 'Dashboard Issues' do
end end
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') find('.new-project-item-select-button').trigger('click')
wait_for_requests wait_for_requests
find('.select2-results li').click
expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new") 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 page.within('#content-body') do
expect(page).to have_selector('.issue-form') expect(page).to have_selector('.issue-form')
......
...@@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do ...@@ -38,7 +38,7 @@ feature 'Groups Merge Requests Empty States' do
it 'should show a new merge request button' do it 'should show a new merge request button' do
within '.empty-state' do within '.empty-state' do
expect(page).to have_content('New merge request') expect(page).to have_content('create merge request')
end end
end end
...@@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do ...@@ -63,7 +63,7 @@ feature 'Groups Merge Requests Empty States' do
it 'should not show a new merge request button' do it 'should not show a new merge request button' do
within '.empty-state' 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 end
end end
......
.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
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();
});
});
});
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