Skip to content

Commit

Permalink
[#53344] Navigate to the default query page when other users query ha…
Browse files Browse the repository at this point in the history
…s been made private

https://community.openproject.org/work_packages/53344

This covers an edge case, when an adminitrator makes another user's query private.
In this case, the query becomes accessible only by its creator. We will try to reload
the query after the save happens, but since the query is not accessible, a confusing error
message is rendered. In order to avoid this error message, we are redirecting the admin to
the default query page if the query becomes private and not accessible.
  • Loading branch information
dombesz committed Apr 15, 2024
1 parent f7df257 commit bb19231
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 21 deletions.
4 changes: 4 additions & 0 deletions frontend/src/app/features/hal/resources/query-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { QueryOrder } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-or
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { UserResource } from 'core-app/features/hal/resources/user-resource';
import { QuerySortByResource } from 'core-app/features/hal/resources/query-sort-by-resource';
import { QueryGroupByResource } from 'core-app/features/hal/resources/query-group-by-resource';

Expand All @@ -41,6 +42,7 @@ export interface QueryResourceEmbedded {
columns:QueryColumn[];
groupBy:QueryGroupByResource|undefined;
project:ProjectResource;
user:UserResource;
sortBy:QuerySortByResource[];
filters:QueryFilterInstanceResource[];
}
Expand Down Expand Up @@ -96,6 +98,8 @@ export class QueryResource extends HalResource {

public hidden:boolean;

public user:UserResource;

public project:ProjectResource;

public includeSubprojects:boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import { States } from 'core-app/core/states/states.service';
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
import { StateService } from '@uirouter/core';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource';
import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
Expand All @@ -43,6 +44,7 @@ import {
WorkPackageViewPaginationService,
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-pagination.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3QueriesPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-queries-paths';
import { ApiV3QueryPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-paths';
Expand All @@ -61,6 +63,8 @@ export interface QueryDefinition {

@Injectable()
export class WorkPackagesListService {
@InjectField() protected readonly currentUser:CurrentUserService;

// We remember the query requests coming in so we can ensure only the latest request is being tended to
private queryRequests = input<QueryDefinition>();

Expand All @@ -73,7 +77,7 @@ export class WorkPackagesListService {
// Map the observable from the stream to a new one that completes when states are loaded
mergeMap((query:QueryResource) => {
// load the form if needed
this.conditionallyLoadForm(query);
void this.conditionallyLoadForm(query);

// Project the loaded query into the table states and confirm the query is fully loaded
this.wpStatesInitialization.initialize(query, query.results);
Expand All @@ -85,6 +89,7 @@ export class WorkPackagesListService {
);

constructor(
readonly injector:Injector,
protected toastService:ToastService,
readonly I18n:I18nService,
protected UrlParamsHelper:UrlParamsHelperService,
Expand Down Expand Up @@ -282,21 +287,7 @@ export class WorkPackagesListService {
.then(() => {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_delete'));

if (this.$state.$current.data.hardReloadOnBaseRoute) {
const url = new URL(window.location.href);
url.search = '';
window.location.href = url.href;
} else {
let projectId;
if (query.project) {
projectId = query.project.href!.split('/').pop();
}

void this.loadDefaultQuery(projectId);

this.states.changes.queries.next(query.id!);
this.reloadSidemenu(null);
}
void this.navigateToDefaultQuery(query);
});

return promise;
Expand All @@ -317,10 +308,14 @@ export class WorkPackagesListService {
void promise
.then(() => {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_update'));

this.$state.go('.', { query_id: query!.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query!.id!);
this.reloadSidemenu(query.id);
const queryAccessibleByUser = query.public || query.user.id === this.currentUser.userId;
if (queryAccessibleByUser) {
void this.$state.go('.', { query_id: query.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query.id);
this.reloadSidemenu(query.id);
} else {
this.navigateToDefaultQuery(query);
}
})
.catch((error:ErrorResource) => {
this.toastService.addError(error.message);
Expand Down Expand Up @@ -435,6 +430,26 @@ export class WorkPackagesListService {
);
}

private navigateToDefaultQuery(query:QueryResource):void {
const { hardReloadOnBaseRoute } = this.$state.$current.data as { hardReloadOnBaseRoute?:boolean };

if (hardReloadOnBaseRoute) {
const url = new URL(window.location.href);
url.search = '';
window.location.href = url.href;
} else {
let projectId;
if (query.project.href) {
projectId = query.project.href.split('/').pop();
}

void this.loadDefaultQuery(projectId);

this.states.changes.queries.next(query.id);
this.reloadSidemenu(null);
}
}

private reloadSidemenu(selectedQueryId:string|null):void {
const menuIdentifier:string|undefined = this.$state.current.data.sidemenuId;

Expand Down

0 comments on commit bb19231

Please sign in to comment.