Skip to content

Commit

Permalink
Merge 9104308 into 83f518c
Browse files Browse the repository at this point in the history
  • Loading branch information
tdesvenain committed Nov 23, 2017
2 parents 83f518c + 9104308 commit c93c7a9
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

## New features

- Add plone-workflow component. [Thomas Desvenain]

- We can set a comment on workflow transition. [Thomas Desvenain]

- Add `workflow` method to get available transitions and history. [Thomas Desvenain]

- Add `username` to isAuthenticated behavior subject. [Thomas Desvenain]

- `login` method of `authentication` service now returns an observable. [Thomas Desvenain]
Expand Down
10 changes: 10 additions & 0 deletions docs/reference/components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ Comments

Display the existing comments and allow to add new ones.

Workflow
--------

.. code-block:: html

<plone-workflow [showHistory]="true" [haveCommentInput]="true"></plone-workflow>

Display workflow history and actionable list of available transitions.


Toolbar
-------

Expand Down
4 changes: 3 additions & 1 deletion docs/reference/services.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ Methods:

`navigation()`: get the global navigation links. Returns an observable.

`transition(path: string, transition: string)`: perform the transition on the resource. Returns an observable.
`transition(path: string, transition: string, options: WorkflowTransitionOptions)`: perform the transition on the resource. You can set a workflow comment. Returns an observable of the last action information.

`workflow(path: string)`: get the workflow history and the available transitions on the content. Returns an observable.

`update(path: string, model: any)`: update the resource. Returns an observable.

Expand Down
101 changes: 101 additions & 0 deletions src/components/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
Component, EventEmitter, Input, Output
} from '@angular/core';
import { Services } from '../services';
import { TraversingComponent } from '../traversing';
import { Target } from 'angular-traversal';
import { Error, WorkflowHistoryItem, WorkflowInformation, WorkflowTransitionItem } from '../interfaces';

@Component({
selector: 'plone-workflow',
template: `
<div class="workflow" *ngIf="!!workflowInformation">
<div class="workflow-history" *ngIf="showHistory">
<ul>
<li *ngFor="let item of workflowInformation?.history">
<strong>{{ item.title }}</strong>
by <strong>{{ item.actor }}</strong> &ndash;
<em>{{ displayTime(item.time) }}</em>
<em *ngIf="item.comments"> &ndash; {{ item.comments }}</em>
</li>
</ul>
</div>
<div class="workflow-actions">
<div class="workflow-comment" *ngIf="haveCommentInput && workflowInformation?.transitions.length">
<textarea
placeholder="Your action comment"
name="workflowComment"
[(ngModel)]="commentText"></textarea>
</div>
<ul>
<li class="workflow-current-state">State: <strong>{{ currentState() }}</strong></li>
<li *ngFor="let transitionItem of workflowInformation?.transitions">
<a href="#" (click)="processTransition($event, transitionItem)">{{ transitionItem.title }}</a>
</li>
</ul>
</div>
</div>`
})
export class Workflow extends TraversingComponent {

@Input() showHistory = true;
@Input() haveCommentInput = true;

contextPath: string;
commentText = '';
public workflowInformation: WorkflowInformation | null;

@Output() workflowStateChanged: EventEmitter<WorkflowHistoryItem> = new EventEmitter();

constructor(public services: Services) {
super(services);
}

onTraverse(target: Target) {
if (target.contextPath) {
this.contextPath = target.contextPath;
this.loadWorkflowInformation();
}
}

protected loadWorkflowInformation() {
const component = this;
this.services.resource.workflow(component.contextPath)
.subscribe((workflowInformation: WorkflowInformation) => {
component.workflowInformation = workflowInformation;
});
}

processTransition(event: Event, item: WorkflowTransitionItem) {
event.preventDefault();

const transitionId = <string>item['@id'].split('/').pop();
this.services.resource.transition(this.contextPath, transitionId, { comment: this.commentText || '' })
.subscribe((historyItem: WorkflowHistoryItem) => {
this.commentText = '';
this.workflowStateChanged.emit(historyItem);
this.loadWorkflowInformation();
}, (error: Error) => {
console.log(error);
if (error.type === 'WorkflowException' || error.response && error.response.status === 404) {
this.workflowInformation = null;
} else {
console.error(error);
}
});
}

currentState(): string | null {
if (this.workflowInformation) {
return this.workflowInformation.history[this.workflowInformation.history.length - 1].title;
} else {
return '';
}
}

displayTime(datestr: string) {
const date = new Date(datestr);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
}

}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export { Comments, Comment, CommentAdd } from './components/comments';
export { GlobalNavigation } from './components/global.navigation';
export { Navigation } from './components/navigation';
export { NavigationLevel } from './components/navigation.level';
export { Workflow } from './components/workflow';
export { Term, Vocabulary } from './vocabularies';
39 changes: 37 additions & 2 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Status
* STATUS
*
*/
import { HttpErrorResponse } from '@angular/common/http';

Expand All @@ -18,6 +19,7 @@ export interface Error {

/*
* NAVIGATION
*
*/


Expand All @@ -34,6 +36,7 @@ export interface NavLink {

/*
* Navigation tree
*
*/
export interface NavTree {
children: NavTree[];
Expand Down Expand Up @@ -120,7 +123,7 @@ export interface PasswordResetInfo {
}


/* File download */
/* FILE DOWNLOAD */

export interface NamedFile {
download: string; // download path
Expand All @@ -142,3 +145,35 @@ export interface DownloadFailedEvent {
error: Error;
namedFile: NamedFile;
}


/*
* WORKFLOW
*
*/

export interface WorkflowHistoryItem<S extends string = string, T extends string = string> {
action: T | null;
actor: string;
comments: string;
review_state: S;
time: string;
title: string;
}

export interface WorkflowTransitionItem {
'@id': string;
title: string;
}

export interface WorkflowInformation<S extends string = string, T extends string = string> {
'@id': string;
history: WorkflowHistoryItem<S, T>[];
transitions: WorkflowTransitionItem[];
}

export interface WorkflowTransitionOptions {
comment?: string;

[x: string]: any;
}
3 changes: 3 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { RequestPasswordResetView } from './views/request-password-reset';
import { PasswordResetView } from './views/password-reset';
import { DownloadDirective } from './directives/download.directive';
import { LoadingService, LoadingInterceptor } from './services/loading.service';
import { Workflow } from './components/workflow';

@NgModule({
declarations: [
Expand All @@ -80,6 +81,7 @@ import { LoadingService, LoadingInterceptor } from './services/loading.service';
GlobalNavigation,
Navigation,
NavigationLevel,
Workflow,
],
entryComponents: [
AddView,
Expand Down Expand Up @@ -132,6 +134,7 @@ import { LoadingService, LoadingInterceptor } from './services/loading.service';
NavigationLevel,
TraverserOutlet,
TraverserLink,
Workflow,
]
})
export class RESTAPIModule {}
21 changes: 12 additions & 9 deletions src/services/resource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Observable } from 'rxjs/Observable';

import { APIService } from './api.service';
import { ConfigurationService } from './configuration.service';
import { NavLink, SearchOptions, SearchResults } from '../interfaces';
import { NavLink, SearchOptions, SearchResults, WorkflowHistoryItem, WorkflowInformation, WorkflowTransitionOptions } from '../interfaces';
import { CacheService } from './cache.service';
import { Vocabulary } from '../vocabularies';

Expand Down Expand Up @@ -56,15 +56,13 @@ export class ResourceService {
);
}

find(
query: {[key: string]: any},
path: string = '/',
options: SearchOptions = {}
): Observable<SearchResults> {
find(query: { [key: string]: any },
path: string = '/',
options: SearchOptions = {}): Observable<SearchResults> {
if (!path.endsWith('/')) {
path += '/';
}
let params: string[] = [];
const params: string[] = [];
Object.keys(query).map(index => {
const criteria = query[index];
if (typeof criteria === 'boolean') {
Expand Down Expand Up @@ -123,12 +121,17 @@ export class ResourceService {
);
}

transition(contextPath: string, transition: string) {
transition<S extends string = string, T extends string = string>(
contextPath: string, transition: T, options: WorkflowTransitionOptions = {}): Observable<WorkflowHistoryItem<S, T>> {
return this.emittingModified(
this.api.post(contextPath + '/@workflow/' + transition, {}), contextPath
this.api.post(contextPath + '/@workflow/' + transition, options), contextPath
);
}

workflow<S extends string = string, T extends string = string>(contextPath: string): Observable<WorkflowInformation<S, T>> {
return this.cache.get(contextPath + '/@workflow');
}

update(path: string, model: any): Observable<any> {
return this.emittingModified(
this.api.patch(path, model), path
Expand Down
2 changes: 2 additions & 0 deletions tests/src/app/custom/view.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ <h3 *ngIf="context.subjects && context.subjects.length > 0">Subjects</h3>
</ul>
</div>
<plone-comments></plone-comments>
<h3>Workflow</h3>
<plone-workflow></plone-workflow>
</ng-container>
<ng-container *ngIf="mode === 'edit'">
<plone-edit></plone-edit>
Expand Down

0 comments on commit c93c7a9

Please sign in to comment.