[37729] Avoid manually serializing the filters object

https://community.openproject.org/wp/37729
pull/9544/head
Oliver Günther 3 years ago
parent cba5ee355e
commit 4667ba0aee
  1. 33
      frontend/src/app/shared/helpers/api-v3/api-v3-filter-builder.ts
  2. 29
      spec/features/search_spec.rb

@ -71,7 +71,7 @@ export class ApiV3FilterBuilder {
* Remove from the filter set
* @param name
*/
public remove(name:string) {
public remove(name:string):void {
delete this.filterMap[name];
}
@ -81,7 +81,7 @@ export class ApiV3FilterBuilder {
* @param filters APIv3 filter array [ {foo: { operator: '=', val: ['bar'] } }, ...]
* @return A map { foo: { operator: '=', val: ['bar'] } , ... }
*/
public toFilterObject(filters:ApiV3Filter[]):ApiV3FilterObject {
public static toFilterObject(filters:ApiV3Filter[]):ApiV3FilterObject {
const map:ApiV3FilterObject = {};
filters.forEach((item:ApiV3Filter) => {
@ -100,9 +100,9 @@ export class ApiV3FilterBuilder {
* @param filters
* @param only Only apply the given filters
*/
public merge(filters:ApiV3Filter[], ...only:string[]) {
public merge(filters:ApiV3Filter[], ...only:string[]):void {
const toAdd:ApiV3FilterObject = _.pickBy(
this.toFilterObject(filters),
ApiV3FilterBuilder.toFilterObject(filters),
(_, filter:string) => only.includes(filter),
);
@ -126,15 +126,11 @@ export class ApiV3FilterBuilder {
}
public toParams(mergeParams:{ [key:string]:string } = {}):string {
let transformedFilters:string[] = [];
transformedFilters = this.filters.map((filter:ApiV3Filter) => this.serializeFilter(filter));
const params = { filters: `[${transformedFilters.join(',')}]`, ...mergeParams };
const params = { filters: this.toJson(), ...mergeParams };
return new URLSearchParams(params).toString();
}
public clone() {
public clone():ApiV3FilterBuilder {
const newFilters = new ApiV3FilterBuilder();
this.filters.forEach((filter) => {
@ -145,23 +141,8 @@ export class ApiV3FilterBuilder {
return newFilters;
}
private serializeFilter(filter:ApiV3Filter) {
const keys:Array<string> = Object.keys(filter);
const typeName = keys[0];
const operatorAndValues = filter[typeName];
return `{"${typeName}":{"operator":"${operatorAndValues.operator}","values":[${operatorAndValues.values
.map((val) => this.serializeFilterValue(val))
.join(',')}]}}`;
}
private serializeFilterValue(filterValue:ApiV3FilterValueType) {
return `"${filterValue.toString()}"`;
}
}
export function buildApiV3Filter(name:string, operator:FilterOperator, values:any) {
export function buildApiV3Filter(name:string, operator:FilterOperator, values:ApiV3FilterValueType[]):ApiV3FilterBuilder {
return new ApiV3FilterBuilder().add(name, operator, values);
}

@ -134,26 +134,26 @@ describe 'Search', type: :feature, js: true, with_settings: { per_page_options:
expect(current_path).to eql project_work_package_path(target_work_package.project, target_work_package, state: 'activity')
first_wp = work_packages.first
search_target = work_packages.last
# Typing a work package id shall find that work package
global_search.search(first_wp.id.to_s, submit: false)
global_search.search(search_target.id.to_s, submit: false)
# And it shall be marked as the direct hit.
global_search.expect_work_package_marked(first_wp)
global_search.expect_work_package_marked(search_target)
# And the direct hit is opened when enter is pressed
global_search.submit_with_enter
expect(page)
.to have_selector('.subject', text: first_wp.subject)
.to have_selector('.subject', text: search_target.subject)
expect(current_path).to eql project_work_package_path(first_wp.project, first_wp, state: 'activity')
expect(current_path).to eql project_work_package_path(search_target.project, search_target, state: 'activity')
# Typing a hash sign before an ID shall only suggest that work package and (no hits within the subject)
global_search.search("##{first_wp.id}", submit: false)
global_search.search("##{search_target.id}", submit: false)
global_search.expect_work_package_marked(first_wp)
global_search.expect_work_package_marked(search_target)
# Expect to have 3 project scope selecting menu entries
global_search.expect_scope('In this project ↵')
@ -163,7 +163,7 @@ describe 'Search', type: :feature, js: true, with_settings: { per_page_options:
# Selection project scope 'In all projects' redirects away from current project.
global_search.submit_in_global_scope
expect(current_path).to match(/\/search/)
expect(current_url).to match(/\/search\?q=#{"%23#{first_wp.id}"}&work_packages=1&scope=all$/)
expect(current_url).to match(/\/search\?q=#{"%23#{search_target.id}"}&work_packages=1&scope=all$/)
end
end
@ -400,7 +400,8 @@ describe 'Search', type: :feature, js: true, with_settings: { per_page_options:
let(:wp_1) { FactoryBot.create :work_package, subject: "Foo && Bar", project: project }
let(:wp_2) { FactoryBot.create :work_package, subject: "Foo # Bar", project: project }
let(:wp_3) { FactoryBot.create :work_package, subject: "Foo &# Bar", project: project }
let!(:work_packages) { [wp_1, wp_2, wp_3] }
let(:wp_4) { FactoryBot.create :work_package, subject: %(Foo '' "" \(\) Bar), project: project }
let!(:work_packages) { [wp_1, wp_2, wp_3, wp_4] }
let(:table) { Pages::EmbeddedWorkPackagesTable.new(find('.work-packages-embedded-view--container')) }
let(:run_visit) { false }
@ -434,6 +435,16 @@ describe 'Search', type: :feature, js: true, with_settings: { per_page_options:
global_search.submit_in_global_scope
table.expect_work_package_listed(wp_1, wp_3)
table.ensure_work_package_not_listed! wp_2
global_search.search '""'
global_search.find_option wp_4.subject
global_search.submit_in_global_scope
table.expect_work_package_listed(wp_4)
global_search.search "'"
global_search.find_option wp_4.subject
global_search.submit_in_global_scope
table.expect_work_package_listed(wp_4)
end
end
end

Loading…
Cancel
Save