Merge pull request #8073 from opf/bim/feature/31979-Allow-filtering-WPs-within-IFC-Models-module
[31979] Allow filtering WPs within IFC Models module [ci skip]pull/8086/head
commit
041dc3fbdd
@ -0,0 +1,74 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 the OpenProject GmbH
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See docs/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {Injectable, Injector} from '@angular/core'; |
||||||
|
import {WorkPackagesListChecksumService} from "core-components/wp-list/wp-list-checksum.service"; |
||||||
|
import {WorkPackagesListService} from "core-components/wp-list/wp-list.service"; |
||||||
|
import {TransitionService} from "@uirouter/core"; |
||||||
|
import {Subject} from "rxjs"; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class QueryParamListenerService { |
||||||
|
readonly wpListChecksumService:WorkPackagesListChecksumService = this.injector.get(WorkPackagesListChecksumService); |
||||||
|
readonly wpListService:WorkPackagesListService = this.injector.get(WorkPackagesListService); |
||||||
|
readonly $transitions:TransitionService = this.injector.get(TransitionService); |
||||||
|
|
||||||
|
public observe$ = new Subject<any>(); |
||||||
|
public queryChangeListener:Function; |
||||||
|
|
||||||
|
constructor(readonly injector:Injector) { |
||||||
|
this.listenForQueryParamsChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
public listenForQueryParamsChanged():any { |
||||||
|
// Listen for param changes
|
||||||
|
return this.queryChangeListener = this.$transitions.onSuccess({}, (transition):any => { |
||||||
|
let options = transition.options(); |
||||||
|
const params = transition.params('to'); |
||||||
|
|
||||||
|
let newChecksum = this.wpListService.getCurrentQueryProps(params); |
||||||
|
let newId:string = params.query_id ? params.query_id.toString() : null; |
||||||
|
|
||||||
|
// Avoid performing any changes when we're going to reload
|
||||||
|
if (options.reload || (options.custom && options.custom.notify === false)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return this.wpListChecksumService |
||||||
|
.executeIfOutdated(newId, |
||||||
|
newChecksum, |
||||||
|
() => { |
||||||
|
this.observe$.next(newChecksum); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public removeQueryChangeListener() { |
||||||
|
this.queryChangeListener(); |
||||||
|
} |
||||||
|
} |
@ -1,51 +1,84 @@ |
|||||||
import {Component} from "@angular/core"; |
import {ChangeDetectorRef, Component, Injector, OnDestroy, OnInit} from "@angular/core"; |
||||||
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service"; |
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service"; |
||||||
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
||||||
import {WorkPackageTableConfigurationObject} from "core-components/wp-table/wp-table-configuration"; |
import {WorkPackageTableConfigurationObject} from "core-components/wp-table/wp-table-configuration"; |
||||||
import { StateService } from '@uirouter/core'; |
import {StateService} from '@uirouter/core'; |
||||||
import {GonService} from "core-app/modules/common/gon/gon.service"; |
import {GonService} from "core-app/modules/common/gon/gon.service"; |
||||||
|
import {QueryParamListenerService} from "core-components/wp-query/query-param-listener.service"; |
||||||
|
import {InjectField} from "core-app/helpers/angular/inject-field.decorator"; |
||||||
|
import {WorkPackagesListService} from "core-components/wp-list/wp-list.service"; |
||||||
|
import {UrlParamsHelperService} from "core-components/wp-query/url-params-helper"; |
||||||
|
import {untilComponentDestroyed} from "ng2-rx-componentdestroyed"; |
||||||
|
|
||||||
@Component({ |
@Component({ |
||||||
templateUrl: './bcf-container.component.html' |
templateUrl: './bcf-container.component.html', |
||||||
|
providers: [ |
||||||
|
QueryParamListenerService |
||||||
|
] |
||||||
}) |
}) |
||||||
export class BCFContainerComponent { |
export class BCFContainerComponent implements OnInit, OnDestroy { |
||||||
|
@InjectField() public queryParamListener:QueryParamListenerService; |
||||||
|
@InjectField() public wpListService:WorkPackagesListService; |
||||||
|
@InjectField() public urlParamsHelper:UrlParamsHelperService; |
||||||
|
|
||||||
public queryProps:{ [key:string]:any }; |
public queryProps:{ [key:string]:any }; |
||||||
|
|
||||||
public configuration:WorkPackageTableConfigurationObject = { |
public configuration:WorkPackageTableConfigurationObject = { |
||||||
actionsColumnEnabled: false, |
actionsColumnEnabled: false, |
||||||
columnMenuEnabled: false, |
columnMenuEnabled: false, |
||||||
contextMenuEnabled: false, |
contextMenuEnabled: false, |
||||||
inlineCreateEnabled: false, |
inlineCreateEnabled: false, |
||||||
withFilters: true, |
withFilters: false, |
||||||
showFilterButton: false, |
showFilterButton: false, |
||||||
isCardView: true |
isCardView: true |
||||||
}; |
}; |
||||||
|
|
||||||
private filters:any[] = []; |
|
||||||
|
|
||||||
constructor(readonly state:StateService, |
constructor(readonly state:StateService, |
||||||
readonly i18n:I18nService, |
readonly i18n:I18nService, |
||||||
readonly paths:PathHelperService, |
readonly paths:PathHelperService, |
||||||
readonly currentProject:CurrentProjectService, |
readonly currentProject:CurrentProjectService, |
||||||
readonly gon:GonService) { |
readonly gon:GonService, |
||||||
|
readonly injector:Injector, |
||||||
|
readonly cdRef:ChangeDetectorRef) { |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit():void { |
||||||
|
this.refresh(); |
||||||
|
|
||||||
this.applyFilters(); |
this.queryParamListener |
||||||
|
.observe$ |
||||||
|
.pipe( |
||||||
|
untilComponentDestroyed(this) |
||||||
|
).subscribe((queryProps) => { |
||||||
|
this.refresh(this.urlParamsHelper.buildV3GetQueryFromJsonParams(queryProps)); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
ngOnDestroy():void { |
||||||
|
this.queryParamListener.removeQueryChangeListener(); |
||||||
} |
} |
||||||
|
|
||||||
private applyFilters() { |
private defaultQueryProps() { |
||||||
// TODO: Limit to project
|
let filters = []; |
||||||
this.filters.push({ |
filters.push({ |
||||||
status: { |
status: { |
||||||
operator: 'o', |
operator: 'o', |
||||||
values: [] |
values: [] |
||||||
} |
} |
||||||
}); |
}); |
||||||
|
|
||||||
this.queryProps = { |
return { |
||||||
'columns[]': ['id', 'subject'], |
'columns[]': ['id', 'subject'], |
||||||
filters: JSON.stringify(this.filters), |
filters: JSON.stringify(filters), |
||||||
sortBy: JSON.stringify([['updatedAt', 'desc']]), |
sortBy: JSON.stringify([['updatedAt', 'desc']]), |
||||||
showHierarchies: false |
showHierarchies: false |
||||||
}; |
}; |
||||||
} |
} |
||||||
|
|
||||||
|
public refresh(queryProps:{ [key:string]:any }|undefined = undefined) { |
||||||
|
this.wpListService.loadCurrentQueryFromParams(this.currentProject.identifier!); |
||||||
|
this.queryProps = queryProps || this.state.params.query_props || this.defaultQueryProps(); |
||||||
|
this.cdRef.detectChanges(); |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,133 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License version 3. |
||||||
|
# |
||||||
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||||
|
# Copyright (C) 2010-2013 the ChiliProject Team |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the License, or (at your option) any later version. |
||||||
|
# |
||||||
|
# This program is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with this program; if not, write to the Free Software |
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
# See docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
require_relative '../support/pages/ifc_models/show' |
||||||
|
require_relative '../support/pages/ifc_models/show_default' |
||||||
|
|
||||||
|
describe 'BIM filter spec', type: :feature, js: true do |
||||||
|
let(:project) { FactoryBot.create :project, enabled_module_names: %w(bim work_package_tracking) } |
||||||
|
let(:open_status) { FactoryBot.create(:status, is_closed: false) } |
||||||
|
let(:closed_status) { FactoryBot.create(:status, is_closed: true) } |
||||||
|
|
||||||
|
let(:wp1) { FactoryBot.create(:work_package, project: project, status: open_status) } |
||||||
|
let(:wp2) { FactoryBot.create(:work_package, project: project, status: closed_status) } |
||||||
|
|
||||||
|
let(:admin) { FactoryBot.create :admin } |
||||||
|
|
||||||
|
let!(:model) do |
||||||
|
FactoryBot.create(:ifc_model_converted, |
||||||
|
project: project, |
||||||
|
uploader: admin) |
||||||
|
end |
||||||
|
|
||||||
|
let(:card_view) { ::Pages::WorkPackageCards.new(project) } |
||||||
|
let(:filters) { ::Components::WorkPackages::Filters.new } |
||||||
|
let(:model_page) { ::Pages::IfcModels::ShowDefault.new project } |
||||||
|
|
||||||
|
before do |
||||||
|
wp1 |
||||||
|
wp2 |
||||||
|
|
||||||
|
login_as(admin) |
||||||
|
model_page.visit! |
||||||
|
model_page.finished_loading |
||||||
|
end |
||||||
|
|
||||||
|
context 'on default page' do |
||||||
|
before do |
||||||
|
# Per default all open work packages are shown |
||||||
|
filters.expect_loaded |
||||||
|
filters.expect_filter_count 1 |
||||||
|
filters.open |
||||||
|
filters.expect_filter_by('Status', 'open', nil) |
||||||
|
|
||||||
|
card_view.expect_work_package_listed wp1 |
||||||
|
card_view.expect_work_package_not_listed wp2 |
||||||
|
end |
||||||
|
|
||||||
|
it 'shows a filter button when there is a list shown' do |
||||||
|
model_page.page_shows_a_filter_button true |
||||||
|
|
||||||
|
model_page.switch_view 'Viewer only' |
||||||
|
model_page.page_shows_a_filter_button false |
||||||
|
end |
||||||
|
|
||||||
|
it 'the filter is applied even after browser back' do |
||||||
|
# Change filter |
||||||
|
filters.set_operator('Status', 'closed', nil) |
||||||
|
filters.expect_filter_count 1 |
||||||
|
|
||||||
|
# Otherwise the check for the loading indicator is done |
||||||
|
# before it is even shown and the next steps will fail |
||||||
|
sleep 0.5 |
||||||
|
loading_indicator_saveguard |
||||||
|
|
||||||
|
card_view.expect_work_package_not_listed wp1 |
||||||
|
card_view.expect_work_package_listed wp2 |
||||||
|
|
||||||
|
# Using the browser back will reload the filter and the work packages |
||||||
|
page.go_back |
||||||
|
loading_indicator_saveguard |
||||||
|
|
||||||
|
filters.expect_loaded |
||||||
|
filters.expect_filter_count 1 |
||||||
|
filters.expect_filter_by('Status', 'open', nil) |
||||||
|
|
||||||
|
card_view.expect_work_package_listed wp1 |
||||||
|
card_view.expect_work_package_not_listed wp2 |
||||||
|
end |
||||||
|
|
||||||
|
it 'the filter is applied even after reload' do |
||||||
|
# Change filter |
||||||
|
filters.set_operator('Status', 'closed', nil) |
||||||
|
filters.expect_filter_count 1 |
||||||
|
|
||||||
|
# Otherwise the check for the loading indicator is done |
||||||
|
# before it is even shown and the next steps will fail |
||||||
|
sleep 0.5 |
||||||
|
loading_indicator_saveguard |
||||||
|
|
||||||
|
card_view.expect_work_package_not_listed wp1 |
||||||
|
card_view.expect_work_package_listed wp2 |
||||||
|
|
||||||
|
# Reload and the filter is still correctly applied |
||||||
|
page.driver.browser.navigate.refresh |
||||||
|
loading_indicator_saveguard |
||||||
|
|
||||||
|
filters.expect_loaded |
||||||
|
filters.expect_filter_count 1 |
||||||
|
filters.open |
||||||
|
filters.expect_filter_by('Status', 'closed', nil) |
||||||
|
|
||||||
|
card_view.expect_work_package_not_listed wp1 |
||||||
|
card_view.expect_work_package_listed wp2 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue