Backport canEdit changes for conditional remove button
Backport Fix locked milestone in boards being remove-able. See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1963 Fix https://gitlab.com/gitlab-org/gitlab-ee/issues/2433 Conflicts: app/assets/javascripts/boards/boards_bundle.js app/assets/javascripts/boards/filtered_search_boards.js app/assets/javascripts/filtered_search/filtered_search_manager.js spec/features/boards/boards_spec.rb
This commit is contained in:
parent
8e2fefc6c4
commit
733eec88e4
|
@ -70,6 +70,7 @@ $(() => {
|
|||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
||||
|
||||
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
||||
this.filterManager.setup();
|
||||
|
||||
// Listen for updateTokens event
|
||||
eventHub.$on('updateTokens', this.updateTokens);
|
||||
|
|
|
@ -13,6 +13,7 @@ export default {
|
|||
FilteredSearchContainer.container = this.$el;
|
||||
|
||||
this.filteredSearch = new FilteredSearchBoards(this.store);
|
||||
this.filteredSearch.setup();
|
||||
this.filteredSearch.removeTokens();
|
||||
this.filteredSearch.handleInputPlaceholder();
|
||||
this.filteredSearch.toggleClearSearchButton();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import FilteredSearchContainer from '../filtered_search/container';
|
||||
|
||||
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
||||
constructor(store, updateUrl = false) {
|
||||
constructor(store, updateUrl = false, cantEdit = []) {
|
||||
super('boards');
|
||||
|
||||
this.store = store;
|
||||
|
@ -11,6 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
// Issue boards is slightly different, we handle all the requests async
|
||||
// instead or reloading the page, we just re-fire the list ajax requests
|
||||
this.isHandledAsync = true;
|
||||
|
||||
this.cantEdit = cantEdit;
|
||||
}
|
||||
|
||||
updateObject(path) {
|
||||
|
@ -40,4 +42,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
// Get the placeholder back if search is empty
|
||||
this.filteredSearchInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
canEdit(tokenName) {
|
||||
return this.cantEdit.indexOf(tokenName) === -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,8 @@ import ShortcutsBlob from './shortcuts_blob';
|
|||
case 'projects:merge_requests:index':
|
||||
case 'projects:issues:index':
|
||||
if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
|
||||
new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
|
||||
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
|
||||
filteredSearchManager.setup();
|
||||
}
|
||||
Issuable.init();
|
||||
new gl.IssuableBulkActions({
|
||||
|
|
|
@ -2,9 +2,9 @@ import './dropdown_hint';
|
|||
import './dropdown_non_user';
|
||||
import './dropdown_user';
|
||||
import './dropdown_utils';
|
||||
import './filtered_search_token_keys';
|
||||
import './filtered_search_dropdown_manager';
|
||||
import './filtered_search_dropdown';
|
||||
import './filtered_search_manager';
|
||||
import './filtered_search_token_keys';
|
||||
import './filtered_search_tokenizer';
|
||||
import './filtered_search_visual_tokens';
|
||||
|
|
|
@ -6,6 +6,7 @@ import eventHub from './event_hub';
|
|||
|
||||
class FilteredSearchManager {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.container = FilteredSearchContainer.container;
|
||||
this.filteredSearchInput = this.container.querySelector('.filtered-search');
|
||||
this.filteredSearchInputForm = this.filteredSearchInput.form;
|
||||
|
@ -17,16 +18,18 @@ class FilteredSearchManager {
|
|||
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
|
||||
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
|
||||
});
|
||||
const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
|
||||
const projectPath = searchHistoryDropdownElement ?
|
||||
searchHistoryDropdownElement.dataset.projectFullPath : 'project';
|
||||
this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
|
||||
const projectPath = this.searchHistoryDropdownElement ?
|
||||
this.searchHistoryDropdownElement.dataset.projectFullPath : 'project';
|
||||
let recentSearchesPagePrefix = 'issue-recent-searches';
|
||||
if (page === 'merge_requests') {
|
||||
if (this.page === 'merge_requests') {
|
||||
recentSearchesPagePrefix = 'merge-request-recent-searches';
|
||||
}
|
||||
const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`;
|
||||
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
|
||||
}
|
||||
|
||||
setup() {
|
||||
// Fetch recent searches from localStorage
|
||||
this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
|
||||
.catch((error) => {
|
||||
|
@ -47,12 +50,12 @@ class FilteredSearchManager {
|
|||
|
||||
if (this.filteredSearchInput) {
|
||||
this.tokenizer = gl.FilteredSearchTokenizer;
|
||||
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, page);
|
||||
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, this.page);
|
||||
|
||||
this.recentSearchesRoot = new RecentSearchesRoot(
|
||||
this.recentSearchesStore,
|
||||
this.recentSearchesService,
|
||||
searchHistoryDropdownElement,
|
||||
this.searchHistoryDropdownElement,
|
||||
);
|
||||
this.recentSearchesRoot.init();
|
||||
|
||||
|
@ -141,7 +144,9 @@ class FilteredSearchManager {
|
|||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken) {
|
||||
const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
|
||||
const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
}
|
||||
|
@ -240,8 +245,10 @@ class FilteredSearchManager {
|
|||
|
||||
editToken(e) {
|
||||
const token = e.target.closest('.js-visual-token');
|
||||
const sanitizedTokenName = token.querySelector('.name').textContent.trim();
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedTokenName);
|
||||
|
||||
if (token) {
|
||||
if (token && canEdit) {
|
||||
gl.FilteredSearchVisualTokens.editToken(token);
|
||||
this.tokenChange();
|
||||
}
|
||||
|
@ -391,7 +398,12 @@ class FilteredSearchManager {
|
|||
|
||||
if (condition) {
|
||||
hasFilteredSearch = true;
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
|
||||
const canEdit = this.canEdit && this.canEdit(condition.tokenKey);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
condition.tokenKey,
|
||||
condition.value,
|
||||
canEdit,
|
||||
);
|
||||
} else {
|
||||
// Sanitize value since URL converts spaces into +
|
||||
// Replace before decode so that we know what was originally + versus the encoded +
|
||||
|
@ -410,18 +422,27 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
hasFilteredSearch = true;
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
sanitizedKey,
|
||||
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
|
||||
canEdit,
|
||||
);
|
||||
} else if (!match && keyParam === 'assignee_id') {
|
||||
const id = parseInt(value, 10);
|
||||
if (usernameParams[id]) {
|
||||
hasFilteredSearch = true;
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
|
||||
const tokenName = 'assignee';
|
||||
const canEdit = this.canEdit && this.canEdit(tokenName);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
}
|
||||
} else if (!match && keyParam === 'author_id') {
|
||||
const id = parseInt(value, 10);
|
||||
if (usernameParams[id]) {
|
||||
hasFilteredSearch = true;
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
|
||||
const tokenName = 'author';
|
||||
const canEdit = this.canEdit && this.canEdit(tokenName);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, `@${usernameParams[id]}`, canEdit);
|
||||
}
|
||||
} else if (!match && keyParam === 'search') {
|
||||
hasFilteredSearch = true;
|
||||
|
@ -516,6 +537,11 @@ class FilteredSearchManager {
|
|||
this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
|
||||
this.search();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
canEdit() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
|
|
@ -36,15 +36,22 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
}
|
||||
|
||||
static createVisualTokenElementHTML() {
|
||||
static createVisualTokenElementHTML(canEdit = true) {
|
||||
let removeTokenMarkup = '';
|
||||
if (canEdit) {
|
||||
removeTokenMarkup = `
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="selectable" role="button">
|
||||
<div class="name"></div>
|
||||
<div class="value-container">
|
||||
<div class="value"></div>
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
${removeTokenMarkup}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -84,13 +91,13 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
}
|
||||
|
||||
static addVisualTokenElement(name, value, isSearchTerm) {
|
||||
static addVisualTokenElement(name, value, isSearchTerm, canEdit) {
|
||||
const li = document.createElement('li');
|
||||
li.classList.add('js-visual-token');
|
||||
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
|
||||
|
||||
if (value) {
|
||||
li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML();
|
||||
li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML(canEdit);
|
||||
FilteredSearchVisualTokens.renderVisualTokenValue(li, name, value);
|
||||
} else {
|
||||
li.innerHTML = '<div class="name"></div>';
|
||||
|
@ -114,20 +121,20 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
}
|
||||
|
||||
static addFilterVisualToken(tokenName, tokenValue) {
|
||||
static addFilterVisualToken(tokenName, tokenValue, canEdit) {
|
||||
const { lastVisualToken, isLastVisualTokenValid }
|
||||
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
|
||||
|
||||
if (isLastVisualTokenValid) {
|
||||
addVisualTokenElement(tokenName, tokenValue, false);
|
||||
addVisualTokenElement(tokenName, tokenValue, false, canEdit);
|
||||
} else {
|
||||
const previousTokenName = lastVisualToken.querySelector('.name').innerText;
|
||||
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
|
||||
tokensContainer.removeChild(lastVisualToken);
|
||||
|
||||
const value = tokenValue || tokenName;
|
||||
addVisualTokenElement(previousTokenName, value, false);
|
||||
addVisualTokenElement(previousTokenName, value, false, canEdit);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,22 @@
|
|||
padding: 2px 7px;
|
||||
}
|
||||
|
||||
.name {
|
||||
background-color: $filter-name-resting-color;
|
||||
color: $filter-name-text-color;
|
||||
border-radius: 2px 0 0 2px;
|
||||
margin-right: 1px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.value-container {
|
||||
background-color: $white-normal;
|
||||
color: $filter-value-text-color;
|
||||
border-radius: 0 2px 2px 0;
|
||||
margin-right: 5px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
@ -111,7 +127,7 @@
|
|||
.remove-token {
|
||||
display: inline-block;
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
padding-right: 0;
|
||||
|
||||
.fa-close {
|
||||
color: $gl-text-color-secondary;
|
||||
|
@ -132,21 +148,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
background-color: $filter-name-resting-color;
|
||||
color: $filter-name-text-color;
|
||||
border-radius: 2px 0 0 2px;
|
||||
margin-right: 1px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.value-container {
|
||||
background-color: $white-normal;
|
||||
color: $filter-value-text-color;
|
||||
border-radius: 0 2px 2px 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
.name {
|
||||
background-color: $filter-name-selected-color;
|
||||
|
|
|
@ -157,7 +157,8 @@
|
|||
|
||||
$(document).off('page:restore').on('page:restore', function (event) {
|
||||
if (gl.FilteredSearchManager) {
|
||||
new gl.FilteredSearchManager();
|
||||
const filteredSearchManager = new gl.FilteredSearchManager();
|
||||
filteredSearchManager.setup();
|
||||
}
|
||||
Issuable.init();
|
||||
new gl.IssuableBulkActions({
|
||||
|
|
|
@ -57,6 +57,7 @@ describe('Filtered Search Manager', () => {
|
|||
input = document.querySelector('.filtered-search');
|
||||
tokensContainer = document.querySelector('.tokens-container');
|
||||
manager = new gl.FilteredSearchManager();
|
||||
manager.setup();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -72,6 +73,7 @@ describe('Filtered Search Manager', () => {
|
|||
spyOn(recentSearchesStoreSrc, 'default');
|
||||
|
||||
filteredSearchManager = new gl.FilteredSearchManager();
|
||||
filteredSearchManager.setup();
|
||||
|
||||
return filteredSearchManager;
|
||||
});
|
||||
|
@ -89,6 +91,7 @@ describe('Filtered Search Manager', () => {
|
|||
spyOn(window, 'Flash');
|
||||
|
||||
filteredSearchManager = new gl.FilteredSearchManager();
|
||||
filteredSearchManager.setup();
|
||||
|
||||
expect(window.Flash).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue