- Added isTaskEditable, isTaskReassignable, isTaskClaimable and isTaskReleasable to WorkflowService

- Added isEditable, isReassignable, isClaimable and isReleasable flags to task model in REST API
- Added outcome property to task model in REST API (label instead of raw value)
- Change "definitionTypeTitle" property to "type" and fixed up fallout
- Changed UI to use the isXXX flags above rather than copious amount of repeated checks
- Updated workflow details page to use outcome label, isEditable flag and some changes following discussion with Linton
- Added tests

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21890 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gavin Cornwell
2010-08-20 09:07:02 +00:00
parent f17f156887
commit aa32a118ab
6 changed files with 110 additions and 38 deletions

View File

@@ -2,5 +2,6 @@
<#import "task.lib.ftl" as taskLib /> <#import "task.lib.ftl" as taskLib />
{ {
"data": <@taskLib.taskJSON task=workflowTask detailed=true/> "data":
<@taskLib.taskJSON task=workflowTask detailed=true/>
} }

View File

@@ -9,16 +9,21 @@
"name": "${task.name}", "name": "${task.name}",
"title": "${task.title}", "title": "${task.title}",
"description": "${task.description}", "description": "${task.description}",
"type": "${task.type}",
"state": "${task.state}", "state": "${task.state}",
"typeDefinitionTitle": "${task.typeDefinitionTitle}",
"path": "${task.path}", "path": "${task.path}",
"isPooled": ${task.isPooled?string}, "isPooled": ${task.isPooled?string},
"isEditable": ${task.isEditable?string},
"isReassignable": ${task.isReassignable?string},
"isClaimable": ${task.isClaimable?string},
"isReleasable": ${task.isReleasable?string},
"outcome": <#if task.outcome??>"${task.outcome}"<#else>null</#if>,
"owner": "owner":
<#if task.owner??> <#if task.owner??>
{ {
"userName": "${task.owner.userName}", "userName": "${task.owner.userName}"<#if task.owner.firstName??>,
"firstName": "${task.owner.firstName}", "firstName": "${task.owner.firstName}"</#if><#if task.owner.lastName??>,
"lastName": "${task.owner.lastName}" "lastName": "${task.owner.lastName}"</#if>
}, },
<#else> <#else>
null, null,
@@ -27,7 +32,6 @@
<@propertiesJSON properties=task.properties />, <@propertiesJSON properties=task.properties />,
"workflowInstance": "workflowInstance":
<@workflowInstanceJSON workflowInstance=task.workflowInstance/><#if detailed>, <@workflowInstanceJSON workflowInstance=task.workflowInstance/><#if detailed>,
"definition": "definition":
{ {
"id": "${task.definition.id}", "id": "${task.definition.id}",
@@ -109,9 +113,9 @@
"initiator": "initiator":
<#if workflowInstance.initiator??> <#if workflowInstance.initiator??>
{ {
"userName": "${workflowInstance.initiator.userName}", "userName": "${workflowInstance.initiator.userName}"<#if workflowInstance.initiator.firstName??>,
"firstName": "${workflowInstance.initiator.firstName}", "firstName": "${workflowInstance.initiator.firstName}"</#if><#if workflowInstance.initiator.lastName??>,
"lastName": "${workflowInstance.initiator.lastName}" "lastName": "${workflowInstance.initiator.lastName}"</#if>
}, },
<#else> <#else>
null, null,

View File

@@ -50,7 +50,8 @@ public abstract class AbstractWorkflowWebscript extends DeclarativeWebScript
@Override @Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{ {
WorkflowModelBuilder modelBuilder = new WorkflowModelBuilder(namespaceService, nodeService, personService, workflowService); WorkflowModelBuilder modelBuilder = new WorkflowModelBuilder(namespaceService, nodeService, authenticationService,
personService, workflowService);
return buildModel(modelBuilder, req, status, cache); return buildModel(modelBuilder, req, status, cache);
} }

View File

@@ -38,6 +38,7 @@ import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowInstance;
@@ -47,6 +48,7 @@ import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.cmr.workflow.WorkflowTransition; import org.alfresco.service.cmr.workflow.WorkflowTransition;
import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
@@ -65,16 +67,21 @@ public class WorkflowModelBuilder
public static final String TASK_PROPERTIES = "properties"; public static final String TASK_PROPERTIES = "properties";
public static final String TASK_OWNER = "owner"; public static final String TASK_OWNER = "owner";
public static final String TASK_TYPE_DEFINITION_TITLE = "typeDefinitionTitle";
public static final String TASK_STATE = "state"; public static final String TASK_STATE = "state";
public static final String TASK_DESCRIPTION = "description"; public static final String TASK_DESCRIPTION = "description";
public static final String TASK_TITLE = "title"; public static final String TASK_TITLE = "title";
public static final String TASK_NAME = "name"; public static final String TASK_NAME = "name";
public static final String TASK_TYPE = "type";
public static final String TASK_URL = "url"; public static final String TASK_URL = "url";
public static final String TASK_IS_POOLED = "isPooled"; public static final String TASK_IS_POOLED = "isPooled";
public static final String TASK_IS_EDITABLE = "isEditable";
public static final String TASK_IS_REASSIGNABLE = "isReassignable";
public static final String TASK_IS_CLAIMABLE = "isClaimable";
public static final String TASK_IS_RELEASABLE = "isReleasable";
public static final String TASK_ID = "id"; public static final String TASK_ID = "id";
public static final String TASK_PATH = "path"; public static final String TASK_PATH = "path";
public static final String TASK_DEFINITION = "definition"; public static final String TASK_DEFINITION = "definition";
public static final String TASK_OUTCOME = "outcome";
public static final String TASK_DEFINITION_ID = "id"; public static final String TASK_DEFINITION_ID = "id";
public static final String TASK_DEFINITION_URL = "url"; public static final String TASK_DEFINITION_URL = "url";
@@ -138,13 +145,17 @@ public class WorkflowModelBuilder
private final NodeService nodeService; private final NodeService nodeService;
private final PersonService personService; private final PersonService personService;
private final WorkflowService workflowService; private final WorkflowService workflowService;
private final AuthenticationService authenticationService;
public WorkflowModelBuilder(NamespaceService namespaceService, NodeService nodeService, PersonService personService, WorkflowService workflowService) public WorkflowModelBuilder(NamespaceService namespaceService, NodeService nodeService,
AuthenticationService authenticationService, PersonService personService,
WorkflowService workflowService)
{ {
this.namespaceService = namespaceService; this.namespaceService = namespaceService;
this.nodeService = nodeService; this.nodeService = nodeService;
this.personService = personService; this.personService = personService;
this.workflowService = workflowService; this.workflowService = workflowService;
this.authenticationService = authenticationService;
} }
/** /**
@@ -155,19 +166,27 @@ public class WorkflowModelBuilder
*/ */
public Map<String, Object> buildSimple(WorkflowTask task, Collection<String> propertyFilters) public Map<String, Object> buildSimple(WorkflowTask task, Collection<String> propertyFilters)
{ {
// get current username
String currentUser = this.authenticationService.getCurrentUserName();
HashMap<String, Object> model = new HashMap<String, Object>(); HashMap<String, Object> model = new HashMap<String, Object>();
model.put(TASK_ID, task.getId()); model.put(TASK_ID, task.getId());
model.put(TASK_URL, getUrl(task)); model.put(TASK_URL, getUrl(task));
model.put(TASK_NAME, task.getName()); model.put(TASK_NAME, task.getName());
model.put(TASK_TYPE, task.getDefinition().getMetadata().getName().toPrefixString(this.namespaceService));
model.put(TASK_TITLE, task.getTitle()); model.put(TASK_TITLE, task.getTitle());
model.put(TASK_DESCRIPTION, task.getDescription()); model.put(TASK_DESCRIPTION, task.getDescription());
model.put(TASK_STATE, task.getState().name()); model.put(TASK_STATE, task.getState().name());
model.put(TASK_TYPE_DEFINITION_TITLE, task.getDefinition().getMetadata().getTitle());
model.put(TASK_PATH, getUrl(task.getPath())); model.put(TASK_PATH, getUrl(task.getPath()));
model.put(TASK_OUTCOME, getOutcome(task));
model.put(TASK_IS_POOLED, isPooled(task.getProperties())); model.put(TASK_IS_POOLED, isPooled(task.getProperties()));
model.put(TASK_IS_EDITABLE, this.workflowService.isTaskEditable(task, currentUser));
model.put(TASK_IS_REASSIGNABLE, this.workflowService.isTaskReassignable(task, currentUser));
model.put(TASK_IS_CLAIMABLE, this.workflowService.isTaskClaimable(task, currentUser));
model.put(TASK_IS_RELEASABLE, this.workflowService.isTaskReleasable(task, currentUser));
Serializable owner = task.getProperties().get(ContentModel.PROP_OWNER); Serializable owner = task.getProperties().get(ContentModel.PROP_OWNER);
model.put(TASK_OWNER, getPersonModel( owner)); model.put(TASK_OWNER, getPersonModel(owner));
// task properties // task properties
model.put(TASK_PROPERTIES, buildProperties(task, propertyFilters)); model.put(TASK_PROPERTIES, buildProperties(task, propertyFilters));
@@ -357,7 +376,7 @@ public class WorkflowModelBuilder
return model; return model;
} }
private Object isPooled(Map<QName, Serializable> properties) private boolean isPooled(Map<QName, Serializable> properties)
{ {
Collection<?> actors = (Collection<?>) properties.get(WorkflowModel.ASSOC_POOLED_ACTORS); Collection<?> actors = (Collection<?>) properties.get(WorkflowModel.ASSOC_POOLED_ACTORS);
return actors != null && !actors.isEmpty(); return actors != null && !actors.isEmpty();
@@ -442,14 +461,19 @@ public class WorkflowModelBuilder
return null; return null;
String name = (String) nameSer; String name = (String) nameSer;
NodeRef person = personService.getPerson(name);
Map<QName, Serializable> properties = nodeService.getProperties(person);
// TODO Person URL? // TODO Person URL?
Map<String, Object> model = new HashMap<String, Object>(); Map<String, Object> model = new HashMap<String, Object>();
model.put(PERSON_USER_NAME, name); model.put(PERSON_USER_NAME, name);
model.put(PERSON_FIRST_NAME, properties.get(ContentModel.PROP_FIRSTNAME));
model.put(PERSON_LAST_NAME, properties.get(ContentModel.PROP_LASTNAME)); if (personService.personExists(name))
{
NodeRef person = personService.getPerson(name);
Map<QName, Serializable> properties = nodeService.getProperties(person);
model.put(PERSON_FIRST_NAME, properties.get(ContentModel.PROP_FIRSTNAME));
model.put(PERSON_LAST_NAME, properties.get(ContentModel.PROP_LASTNAME));
}
return model; return model;
} }
@@ -490,13 +514,25 @@ public class WorkflowModelBuilder
List<?> hiddenTransitions = getHiddenTransitions(workflowTask.getProperties()); List<?> hiddenTransitions = getHiddenTransitions(workflowTask.getProperties());
for (WorkflowTransition workflowTransition : workflowNode.getTransitions()) for (WorkflowTransition workflowTransition : workflowNode.getTransitions())
{ {
Map<String, Object> transitionModel = build(workflowTransition, hiddenTransitions); Map<String, Object> transitionModel = buildTransition(workflowTransition, hiddenTransitions);
transitions.add(transitionModel); transitions.add(transitionModel);
} }
model.put(WORKFLOW_NODE_TRANSITIONS, transitions); model.put(WORKFLOW_NODE_TRANSITIONS, transitions);
return model; return model;
} }
private Map<String, Object> buildTransition(WorkflowTransition workflowTransition, List<?> hiddenTransitions)
{
Map<String, Object> model = new HashMap<String, Object>();
String id = workflowTransition.getId();
model.put(WORKFLOW_NODE_TRANSITION_ID, id);
model.put(WORKFLOW_NODE_TRANSITION_TITLE, workflowTransition.getTitle());
model.put(WORKFLOW_NODE_TRANSITION_DESCRIPTION, workflowTransition.getDescription());
model.put(WORKFLOW_NODE_TRANSITION_IS_DEFAULT, workflowTransition.isDefault());
model.put(WORKFLOW_NODE_TRANSITION_IS_HIDDEN, isHiddenTransition(id, hiddenTransitions));
return model;
}
/** /**
* @param properties * @param properties
* @return * @return
@@ -515,19 +551,7 @@ public class WorkflowModelBuilder
} }
return null; return null;
} }
public Map<String, Object> build(WorkflowTransition workflowTransition, List<?> hiddenTransitions)
{
Map<String, Object> model = new HashMap<String, Object>();
String id = workflowTransition.getId();
model.put(WORKFLOW_NODE_TRANSITION_ID, id);
model.put(WORKFLOW_NODE_TRANSITION_TITLE, workflowTransition.getTitle());
model.put(WORKFLOW_NODE_TRANSITION_DESCRIPTION, workflowTransition.getDescription());
model.put(WORKFLOW_NODE_TRANSITION_IS_DEFAULT, workflowTransition.isDefault());
model.put(WORKFLOW_NODE_TRANSITION_IS_HIDDEN, isHiddenTransition(id, hiddenTransitions));
return model;
}
private boolean isHiddenTransition(String transitionId, List<?> hiddenTransitions) private boolean isHiddenTransition(String transitionId, List<?> hiddenTransitions)
{ {
if (hiddenTransitions == null) if (hiddenTransitions == null)
@@ -554,7 +578,32 @@ public class WorkflowModelBuilder
{ {
return null; return null;
} }
}
private String getOutcome(WorkflowTask task)
{
String outcomeLabel = null;
// there will only be an outcome if the task is completed
if (task.getState().equals(WorkflowTaskState.COMPLETED))
{
String outcomeId = (String)task.getProperties().get(WorkflowModel.PROP_OUTCOME);
if (outcomeId != null)
{
// find the transition with the matching id and get the label
WorkflowTransition[] transitions = task.getDefinition().getNode().getTransitions();
for (WorkflowTransition transition : transitions)
{
if (transition.getId().equals(outcomeId))
{
outcomeLabel = transition.getTitle();
break;
}
}
}
}
return outcomeLabel;
} }
private String getUrl(WorkflowTask task) private String getUrl(WorkflowTask task)

View File

@@ -37,6 +37,7 @@ import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowInstance;
@@ -69,6 +70,7 @@ public class WorkflowModelBuilderTest extends TestCase
private PersonService personService; private PersonService personService;
private NodeService nodeService; private NodeService nodeService;
private WorkflowService workflowService; private WorkflowService workflowService;
private AuthenticationService authenticationService;
private WorkflowModelBuilder builder; private WorkflowModelBuilder builder;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -86,8 +88,14 @@ public class WorkflowModelBuilderTest extends TestCase
assertEquals(task.getTitle(), model.get(WorkflowModelBuilder.TASK_TITLE)); assertEquals(task.getTitle(), model.get(WorkflowModelBuilder.TASK_TITLE));
assertEquals(task.getDescription(), model.get(WorkflowModelBuilder.TASK_DESCRIPTION)); assertEquals(task.getDescription(), model.get(WorkflowModelBuilder.TASK_DESCRIPTION));
assertEquals(task.getState().name(), model.get(WorkflowModelBuilder.TASK_STATE)); assertEquals(task.getState().name(), model.get(WorkflowModelBuilder.TASK_STATE));
assertEquals(task.getDefinition().getMetadata().getTitle(), model.get(WorkflowModelBuilder.TASK_TYPE_DEFINITION_TITLE)); assertEquals(task.getDefinition().getMetadata().getName().toPrefixString(this.namespaceService),
model.get(WorkflowModelBuilder.TASK_TYPE));
assertNull(model.get(WorkflowModelBuilder.TASK_OUTCOME));
assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_POOLED)); assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_POOLED));
assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_EDITABLE));
assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_REASSIGNABLE));
assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_CLAIMABLE));
assertEquals(false, model.get(WorkflowModelBuilder.TASK_IS_RELEASABLE));
Map<String, Object> owner = (Map<String, Object>) model.get(WorkflowModelBuilder.TASK_OWNER); Map<String, Object> owner = (Map<String, Object>) model.get(WorkflowModelBuilder.TASK_OWNER);
assertEquals(userName, owner.get(WorkflowModelBuilder.PERSON_USER_NAME)); assertEquals(userName, owner.get(WorkflowModelBuilder.PERSON_USER_NAME));
@@ -319,7 +327,7 @@ public class WorkflowModelBuilderTest extends TestCase
private TypeDefinition makeTypeDefinition() private TypeDefinition makeTypeDefinition()
{ {
TypeDefinition typeDef = mock(TypeDefinition.class); TypeDefinition typeDef = mock(TypeDefinition.class);
when(typeDef.getName()).thenReturn(QName.createQName("The Type Name")); when(typeDef.getName()).thenReturn(QName.createQName(URI, "The Type Name"));
when(typeDef.getTitle()).thenReturn("The Type Title"); when(typeDef.getTitle()).thenReturn("The Type Title");
when(typeDef.getDescription()).thenReturn("The Type Description"); when(typeDef.getDescription()).thenReturn("The Type Description");
return typeDef; return typeDef;
@@ -393,6 +401,7 @@ public class WorkflowModelBuilderTest extends TestCase
personService = mock(PersonService.class); personService = mock(PersonService.class);
when(personService.getPerson(userName)).thenReturn(person); when(personService.getPerson(userName)).thenReturn(person);
when(personService.personExists(userName)).thenReturn(true);
nodeService = mock(NodeService.class); nodeService = mock(NodeService.class);
Map<QName, Serializable> personProps = new HashMap<QName, Serializable>(); Map<QName, Serializable> personProps = new HashMap<QName, Serializable>();
@@ -401,9 +410,12 @@ public class WorkflowModelBuilderTest extends TestCase
personProps.put(ContentModel.PROP_LASTNAME, lastName); personProps.put(ContentModel.PROP_LASTNAME, lastName);
when(nodeService.getProperties(person)).thenReturn(personProps); when(nodeService.getProperties(person)).thenReturn(personProps);
when(nodeService.getProperty(person, ContentModel.PROP_USERNAME)).thenReturn(userName); when(nodeService.getProperty(person, ContentModel.PROP_USERNAME)).thenReturn(userName);
when(nodeService.getProperty(person, ContentModel.PROP_FIRSTNAME)).thenReturn(firstName);
when(nodeService.getProperty(person, ContentModel.PROP_LASTNAME)).thenReturn(lastName);
workflowService = mock(WorkflowService.class); workflowService = mock(WorkflowService.class);
authenticationService = mock(AuthenticationService.class);
builder = new WorkflowModelBuilder(namespaceService, nodeService, personService, workflowService); builder = new WorkflowModelBuilder(namespaceService, nodeService, authenticationService, personService, workflowService);
} }
} }

View File

@@ -176,8 +176,13 @@ public class WorkflowRestApiTest extends BaseWebScriptTest
assertEquals(startTask.title, result.getString("title")); assertEquals(startTask.title, result.getString("title"));
assertEquals(startTask.description, result.getString("description")); assertEquals(startTask.description, result.getString("description"));
assertEquals(startTask.state.name(), result.getString("state")); assertEquals(startTask.state.name(), result.getString("state"));
assertEquals(startTask.name, result.getString("type"));
assertEquals( "api/workflow-paths/" + adhocPath.getId(), result.getString("path")); assertEquals( "api/workflow-paths/" + adhocPath.getId(), result.getString("path"));
assertEquals(false, result.getBoolean("isPooled")); assertFalse(result.getBoolean("isPooled"));
assertTrue(result.getBoolean("isEditable"));
assertTrue(result.getBoolean("isReassignable"));
assertFalse(result.getBoolean("isClaimable"));
assertFalse(result.getBoolean("isReleasable"));
JSONObject owner = result.getJSONObject("owner"); JSONObject owner = result.getJSONObject("owner");
assertEquals(USER1, owner.getString("userName")); assertEquals(USER1, owner.getString("userName"));