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 interface ApiV3FilterValue {
operator:FilterOperator;
values:any;
}
export interface ApiV3Filter {
[filter:string]:{ operator:FilterOperator, values:any };
[filter:string]:ApiV3FilterValue;
}
export type ApiV3FilterObject = { [filter:string]:ApiV3FilterValue };
export class ApiV3FilterBuilder {
public filters:ApiV3Filter[] = [];
private filterMap:ApiV3FilterObject = {};
public add(name:string, operator:FilterOperator, values:any):this {
let newFilter:ApiV3Filter = {};
newFilter[name] = {
this.filterMap[name] = {
operator: operator,
values: values
};
this.filters.push(newFilter);
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 {
return JSON.stringify(this.filters);
}
public toParams(mergeParams:{[key:string]:string} = {}):string {
public toParams(mergeParams:{ [key:string]:string } = {}):string {
let transformedFilters:string[] = [];
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 {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 {UrlParamsHelperService} from "core-components/wp-query/url-params-helper";
@Component({
selector: 'board-inline-add-autocompleter',
@ -86,6 +87,7 @@ export class BoardInlineAddAutocompleterComponent implements AfterViewInit {
constructor(private readonly querySpace:IsolatedQuerySpace,
private readonly pathHelper:PathHelperService,
private readonly urlParamsHelper:UrlParamsHelperService,
private readonly notificationService:WorkPackageNotificationService,
private readonly CurrentProject:CurrentProjectService,
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
if (query.length === 0) {
if (searchString.length === 0) {
this.isLoading = false;
return of([]);
}
const path = this.pathHelper.api.v3.withOptionalProject(this.CurrentProject.id).work_packages;
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) {
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
.get<WorkPackageCollectionResource>(path.filtered(filters))
.pipe(

@ -38,7 +38,7 @@ describe 'Board reference work package spec', type: :feature, js: true do
end
let(:project) { FactoryBot.create(:project, enabled_module_names: %i[work_package_tracking board_view]) }
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(:filters) { ::Components::WorkPackages::Filters.new }
@ -93,4 +93,47 @@ describe 'Board reference work package spec', type: :feature, js: true do
work_package.reload
expect(work_package.fixed_version_id).to eq version.id
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

@ -109,6 +109,20 @@ module Pages
expect_card(list_name, work_package.subject)
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)
def expect_card(list_name, card_title, present: true)
@ -123,7 +137,7 @@ module Pages
def expect_cards_in_order(list_name, *card_titles)
within_list(list_name) do
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 }
expect(found)

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

Loading…
Cancel
Save