Merge pull request #8185 from opf/fix/31613/add-existing-filtered-wps-to-board

[31613] Add the subproject filter to the inline add autocompleter spec
pull/8192/head
ulferts 5 years ago committed by GitHub
commit 984e4d5c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 71
      frontend/src/app/components/api/api-v3/api-v3-filter-builder.ts
  2. 17
      frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts
  3. 45
      modules/boards/spec/features/board_reference_work_package_spec.rb
  4. 16
      modules/boards/spec/features/support/board_page.rb
  5. 2
      spec/support/components/work_packages/filters.rb

@ -28,30 +28,89 @@
export type FilterOperator = '='|'!*'|'!'|'~'|'o'|'>t-'|'<>d'|'**'|'ow' ; export type FilterOperator = '='|'!*'|'!'|'~'|'o'|'>t-'|'<>d'|'**'|'ow' ;
export interface ApiV3FilterValue {
operator:FilterOperator;
values:any;
}
export interface ApiV3Filter { export interface ApiV3Filter {
[filter:string]:{ operator:FilterOperator, values:any }; [filter:string]:ApiV3FilterValue;
} }
export type ApiV3FilterObject = { [filter:string]:ApiV3FilterValue };
export class ApiV3FilterBuilder { export class ApiV3FilterBuilder {
public filters:ApiV3Filter[] = []; private filterMap:ApiV3FilterObject = {};
public add(name:string, operator:FilterOperator, values:any):this { public add(name:string, operator:FilterOperator, values:any):this {
let newFilter:ApiV3Filter = {}; this.filterMap[name] = {
newFilter[name] = {
operator: operator, operator: operator,
values: values values: values
}; };
this.filters.push(newFilter);
return this; return this;
} }
/**
* Remove from the filter set
* @param name
*/
public remove(name:string) {
delete this.filterMap[name];
}
/**
* Turns the array-map style of query filters to an actual object
*
* @param filters APIv3 filter array [ {foo: { operator: '=', val: ['bar'] } }, ...]
* @return A map { foo: { operator: '=', val: ['bar'] } , ... }
*/
public toFilterObject(filters:ApiV3Filter[]):ApiV3FilterObject {
let map:ApiV3FilterObject = {};
filters.forEach((item:ApiV3Filter) => {
_.each(item, (val:ApiV3FilterValue, filter:string) => {
map[filter] = val;
});
});
return map;
}
/**
* Merges the other filters into the current set,
* replacing them if the are duplicated.
*
* @param filters
* @param only Only apply the given filters
*/
public merge(filters:ApiV3Filter[], ...only:string[]) {
const toAdd:ApiV3FilterObject = _.pickBy(
this.toFilterObject(filters),
(_, filter:string) => only.includes(filter)
);
this.filterMap = {
...this.filterMap,
...toAdd
};
}
public get filters():ApiV3Filter[] {
let filters:ApiV3Filter[] = [];
_.each(this.filterMap, (val:ApiV3FilterValue, filter:string) => {
filters.push({ [filter]: val });
});
return filters;
}
public toJson():string { public toJson():string {
return JSON.stringify(this.filters); return JSON.stringify(this.filters);
} }
public toParams(mergeParams:{[key:string]:string} = {}):string { public toParams(mergeParams:{ [key:string]:string } = {}):string {
let transformedFilters:string[] = []; let transformedFilters:string[] = [];
transformedFilters = this.filters.map((filter:ApiV3Filter) => { transformedFilters = this.filters.map((filter:ApiV3Filter) => {

@ -50,6 +50,7 @@ import {HalResourceService} from "core-app/modules/hal/services/hal-resource.ser
import {SchemaCacheService} from "core-components/schemas/schema-cache.service"; import {SchemaCacheService} from "core-components/schemas/schema-cache.service";
import {WorkPackageCardDragAndDropService} from "core-components/wp-card-view/services/wp-card-drag-and-drop.service"; import {WorkPackageCardDragAndDropService} from "core-components/wp-card-view/services/wp-card-drag-and-drop.service";
import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service"; import {WorkPackageNotificationService} from "core-app/modules/work_packages/notifications/work-package-notification.service";
import {UrlParamsHelperService} from "core-components/wp-query/url-params-helper";
@Component({ @Component({
selector: 'board-inline-add-autocompleter', selector: 'board-inline-add-autocompleter',
@ -86,6 +87,7 @@ export class BoardInlineAddAutocompleterComponent implements AfterViewInit {
constructor(private readonly querySpace:IsolatedQuerySpace, constructor(private readonly querySpace:IsolatedQuerySpace,
private readonly pathHelper:PathHelperService, private readonly pathHelper:PathHelperService,
private readonly urlParamsHelper:UrlParamsHelperService,
private readonly notificationService:WorkPackageNotificationService, private readonly notificationService:WorkPackageNotificationService,
private readonly CurrentProject:CurrentProjectService, private readonly CurrentProject:CurrentProjectService,
private readonly halResourceService:HalResourceService, private readonly halResourceService:HalResourceService,
@ -123,23 +125,30 @@ export class BoardInlineAddAutocompleterComponent implements AfterViewInit {
} }
} }
private autocompleteWorkPackages(query:string):Observable<WorkPackageResource[]> { private autocompleteWorkPackages(searchString:string):Observable<WorkPackageResource[]> {
// Return when the search string is empty // Return when the search string is empty
if (query.length === 0) { if (searchString.length === 0) {
this.isLoading = false; this.isLoading = false;
return of([]); return of([]);
} }
const path = this.pathHelper.api.v3.withOptionalProject(this.CurrentProject.id).work_packages; const path = this.pathHelper.api.v3.withOptionalProject(this.CurrentProject.id).work_packages;
const filters:ApiV3FilterBuilder = new ApiV3FilterBuilder(); const filters:ApiV3FilterBuilder = new ApiV3FilterBuilder();
const results = this.querySpace.results.value const results = this.querySpace.results.value;
filters.add('subjectOrId', '**', [query]); filters.add('subjectOrId', '**', [searchString]);
if (results && results.elements.length > 0) { if (results && results.elements.length > 0) {
filters.add('id', '!', results.elements.map((wp:WorkPackageResource) => wp.id!)); filters.add('id', '!', results.elements.map((wp:WorkPackageResource) => wp.id!));
} }
// Add the subproject filter, if any
const query = this.querySpace.query.value;
if (query?.filters) {
const currentFilters = this.urlParamsHelper.buildV3GetFilters(query.filters);
filters.merge(currentFilters, 'subprojectId');
}
return this.halResourceService return this.halResourceService
.get<WorkPackageCollectionResource>(path.filtered(filters)) .get<WorkPackageCollectionResource>(path.filtered(filters))
.pipe( .pipe(

@ -38,7 +38,7 @@ describe 'Board reference work package spec', type: :feature, js: true do
end end
let(:project) { FactoryBot.create(:project, enabled_module_names: %i[work_package_tracking board_view]) } let(:project) { FactoryBot.create(:project, enabled_module_names: %i[work_package_tracking board_view]) }
let(:role) { FactoryBot.create(:role, permissions: permissions) } let(:role) { FactoryBot.create(:role, permissions: permissions) }
let!(:work_package) { FactoryBot.create :work_package, subject: 'Foo', project: project } let!(:work_package) { FactoryBot.create :work_package, fixed_version: version, subject: 'Foo', project: project }
let(:board_index) { Pages::BoardIndex.new(project) } let(:board_index) { Pages::BoardIndex.new(project) }
let(:filters) { ::Components::WorkPackages::Filters.new } let(:filters) { ::Components::WorkPackages::Filters.new }
@ -93,4 +93,47 @@ describe 'Board reference work package spec', type: :feature, js: true do
work_package.reload work_package.reload
expect(work_package.fixed_version_id).to eq version.id expect(work_package.fixed_version_id).to eq version.id
end end
context 'with a subproject and work packages within it (Regression #31613)' do
let!(:child_project) { FactoryBot.create(:project, parent: project) }
let!(:work_package) { FactoryBot.create(:work_package, subject: 'WP SUB', project: child_project) }
let(:user) do
FactoryBot.create(:user,
member_in_projects: [project, child_project],
member_through_role: role)
end
it 'returns the work package when subproject filters is added' do
board_view
board_index.visit!
# Create new board
board_page = board_index.create_board action: nil
board_page.rename_list 'Unnamed list', 'First'
# Reference an existing work package
board_page.expect_not_referencable('First', work_package)
sleep 2
board_page.expect_card('First', work_package.subject, present: false)
# Add subproject filter
filters.open
filters.add_filter_by('Subproject', 'all', nil, 'subprojectId')
sleep 2
# Reference an existing work package
board_page.reference('First', work_package)
sleep 2
board_page.expect_card('First', work_package.subject)
queries = board_page.board(reload: true).contained_queries
first = queries.find_by(name: 'First')
ids = first.ordered_work_packages.pluck(:work_package_id)
expect(ids).to match_array [work_package.id]
work_package.reload
expect(work_package.project).to eq(child_project)
end
end
end end

@ -109,6 +109,20 @@ module Pages
expect_card(list_name, work_package.subject) expect_card(list_name, work_package.subject)
end end
def expect_not_referencable(list_name, work_package)
within_list(list_name) do
page.find('.board-list--card-dropdown-button').click
end
page.find('.menu-item', text: 'Add existing').click
target_dropdown = search_autocomplete(page.find('.wp-inline-create--reference-autocompleter'),
query: work_package.subject,
results_selector: '.board--container')
expect(target_dropdown).to have_no_selector('.ui-menu-item', text: work_package.subject)
end
## ##
# Expect the given titled card in the list name to be present (expect=true) or not (expect=false) # Expect the given titled card in the list name to be present (expect=true) or not (expect=false)
def expect_card(list_name, card_title, present: true) def expect_card(list_name, card_title, present: true)
@ -123,7 +137,7 @@ module Pages
def expect_cards_in_order(list_name, *card_titles) def expect_cards_in_order(list_name, *card_titles)
within_list(list_name) do within_list(list_name) do
found = all('.wp-card .wp-card--subject') found = all('.wp-card .wp-card--subject')
.map(&:text) .map(&:text)
expected = card_titles.map { |title| title.is_a?(WorkPackage) ? title.subject : title.to_s } expected = card_titles.map { |title| title.is_a?(WorkPackage) ? title.subject : title.to_s }
expect(found) expect(found)

@ -97,7 +97,7 @@ module Components
set_operator(name, operator, selector) set_operator(name, operator, selector)
set_value(id, value) set_value(id, value) unless value.nil?
end end
def expect_filter_by(name, operator, value, selector = nil) def expect_filter_by(name, operator, value, selector = nil)

Loading…
Cancel
Save