diff --git a/api/package-lock.json b/api/package-lock.json index fb2f5aefa..7660714de 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -2078,8 +2078,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2100,14 +2099,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2122,20 +2119,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2252,8 +2246,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2265,7 +2258,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2280,7 +2272,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2288,14 +2279,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2314,7 +2303,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2395,8 +2383,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2408,7 +2395,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2494,8 +2480,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2531,7 +2516,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2551,7 +2535,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2595,14 +2578,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -3501,6 +3482,11 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", diff --git a/api/package.json b/api/package.json index bfe84f5ac..8824e0166 100644 --- a/api/package.json +++ b/api/package.json @@ -77,6 +77,7 @@ "immer": "^2.1.3", "joi": "^14.3.1", "jsonwebtoken": "^8.5.0", + "lodash.clonedeep": "^4.5.0", "lodash.isequal": "^4.5.0", "pino": "^5.8.0", "pino-pretty": "^2.2.3", diff --git a/api/src/project_view_details.ts b/api/src/project_view_details.ts index d9bc4c939..cb5eca929 100644 --- a/api/src/project_view_details.ts +++ b/api/src/project_view_details.ts @@ -83,6 +83,7 @@ function mkSwaggerSchema(server: FastifyInstance) { entityType: { type: "string", example: "project" }, businessEvent: { type: "object", + additionalProperties: true, properties: { type: { type: "string" }, source: { type: "string" }, @@ -98,6 +99,7 @@ function mkSwaggerSchema(server: FastifyInstance) { }, snapshot: { type: "object", + additionalProperties: true, properties: { displayName: { type: "string", example: "Build a town-project" }, }, diff --git a/api/src/project_view_history.ts b/api/src/project_view_history.ts index bfe00e64d..f738b8dc7 100644 --- a/api/src/project_view_history.ts +++ b/api/src/project_view_history.ts @@ -12,28 +12,6 @@ import { ServiceUser } from "./service/domain/organization/service_user"; import * as Project from "./service/domain/workflow/project"; import * as Subproject from "./service/domain/workflow/subproject"; -interface RequestBodyV1 { - apiVersion: "1.0"; - data: { - projectId: Project.Id; - }; -} - -const requestBodyV1Schema = Joi.object({ - apiVersion: Joi.valid("1.0").required(), - data: Joi.object({ - projectId: Project.idSchema.required(), - }), -}); - -type RequestBody = RequestBodyV1; -const requestBodySchema = Joi.alternatives([requestBodyV1Schema]); - -function validateRequestBody(body: any): Result.Type { - const { error, value } = Joi.validate(body, requestBodySchema); - return !error ? value : error; -} - function mkSwaggerSchema(server: FastifyInstance) { return { beforeHandler: [(server as any).authenticate], @@ -83,25 +61,27 @@ function mkSwaggerSchema(server: FastifyInstance) { items: { type: "object", properties: { - key: { type: "string" }, - intent: { type: "string", example: "global.createProject" }, - createdBy: { type: "string", example: "aSmith" }, - createdAt: { type: "string", example: "2018-09-05T13:37:25.775Z" }, - dataVersion: { type: "string", example: "1" }, - data: { + entityId: { type: "string", example: "d0e8c69eg298c87e3899119e025eff1f" }, + entityType: { type: "string", example: "project" }, + businessEvent: { type: "object", additionalProperties: true, - example: { identity: "aSmith", intent: "subproject.viewDetails" }, properties: { - permissions: { - type: "object", - additionalProperties: true, - example: { "subproject.intent.listPermissions": ["aSmith", "jDoe"] }, - }, + type: { type: "string" }, + source: { type: "string" }, + time: { type: "string" }, + publisher: { type: "string" }, + }, + example: { + type: "project_closed", + source: "http", + time: "2018-09-05T13:37:25.775Z", + publisher: "jdoe", }, }, snapshot: { type: "object", + additionalProperties: true, properties: { displayName: { type: "string", example: "townproject" }, }, diff --git a/api/src/service/cache2.ts b/api/src/service/cache2.ts index 097dedb05..2015fd8af 100644 --- a/api/src/service/cache2.ts +++ b/api/src/service/cache2.ts @@ -1,6 +1,7 @@ import { Ctx } from "../lib/ctx"; -import logger from "../lib/logger"; import deepcopy from "../lib/deepcopy"; +import { isEmpty } from "../lib/emptyChecks"; +import logger from "../lib/logger"; import * as Result from "../result"; import { MultichainClient } from "./Client.h"; import { ConnToken } from "./conn"; @@ -443,20 +444,25 @@ function addEventsToCache(cache: Cache2, streamName: string, newEvents: Business break; default: - // Do nothing, becaue informations will be reflected in aggregates + // Do nothing, because informations will be reflected in aggregates break; } } export function updateAggregates(ctx: Ctx, cache: Cache2, newEvents: BusinessEvent[]) { - // we ignore the errors - const { projects } = sourceProjects(ctx, newEvents, cache.cachedProjects); + const { projects, errors: pErrors = [] } = sourceProjects(ctx, newEvents, cache.cachedProjects); + if (!isEmpty(pErrors)) logger.debug("sourceProject caused error: ", pErrors); for (const project of projects) { cache.cachedProjects.set(project.id, project); } - const { subprojects } = sourceSubprojects(ctx, newEvents, cache.cachedSubprojects); + const { subprojects, errors: spErrors = [] } = sourceSubprojects( + ctx, + newEvents, + cache.cachedSubprojects, + ); + if (!isEmpty(spErrors)) logger.debug("sourceSubproject caused error: ", spErrors); for (const subproject of subprojects) { cache.cachedSubprojects.set(subproject.id, subproject); @@ -467,7 +473,12 @@ export function updateAggregates(ctx: Ctx, cache: Cache2, newEvents: BusinessEve : lookUp.add(subproject.id); } - const { workflowitems, errors } = sourceWorkflowitems(ctx, newEvents, cache.cachedWorkflowItems); + const { workflowitems, errors: wErrors = [] } = sourceWorkflowitems( + ctx, + newEvents, + cache.cachedWorkflowItems, + ); + if (!isEmpty(wErrors)) logger.debug("sourceWorkflowitems caused error: ", wErrors); for (const workflowitem of workflowitems) { cache.cachedWorkflowItems.set(workflowitem.id, workflowitem); diff --git a/api/src/service/domain/workflow/subproject_eventsourcing.ts b/api/src/service/domain/workflow/subproject_eventsourcing.ts index c1ffdcfbf..601122d37 100644 --- a/api/src/service/domain/workflow/subproject_eventsourcing.ts +++ b/api/src/service/domain/workflow/subproject_eventsourcing.ts @@ -1,6 +1,7 @@ import { produce } from "immer"; import { Ctx } from "../../../lib/ctx"; +import logger from "../../../lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -31,7 +32,6 @@ export function sourceSubprojects( if (!event.type.startsWith("subproject_") && event.type !== "workflowitems_reordered") { continue; } - const result = applySubprojectEvent(ctx, subprojects, event); if (Result.isErr(result)) { errors.push(result); @@ -78,7 +78,7 @@ function applySubprojectEvent( return apply(ctx, event, subprojects, event.subprojectId, SubprojectProjectedBudgetDeleted); default: - throw Error(`not implemented: ${event.type}`); + return Error(`not implemented: ${event.type}`); } } diff --git a/api/src/service/domain/workflow/subproject_get.ts b/api/src/service/domain/workflow/subproject_get.ts index ca83de7f7..288530df5 100644 --- a/api/src/service/domain/workflow/subproject_get.ts +++ b/api/src/service/domain/workflow/subproject_get.ts @@ -18,12 +18,11 @@ export async function getSubproject( subprojectId: string, repository: Repository, ): Promise> { - const subprojectResult = await repository.getSubproject(); + const subproject = await repository.getSubproject(); - if (Result.isErr(subprojectResult)) { + if (Result.isErr(subproject)) { return new NotFound(ctx, "subproject", subprojectId); } - const subproject = subprojectResult; if (user.id !== "root") { const intents: Intent[] = ["subproject.viewSummary", "subproject.viewDetails"]; @@ -46,6 +45,7 @@ const requiredPermissions = new Map([ ["subproject_archived", ["subproject.viewSummary", "subproject.viewDetails"]], ["subproject_projected_budget_updated", ["subproject.viewDetails"]], ["subproject_projected_budget_deleted", ["subproject.viewDetails"]], + ["workflowitems_reordered", ["subproject.reorderWorkflowitems"]], ]); function dropHiddenHistoryEvents( diff --git a/api/src/service/domain/workflow/subproject_list.ts b/api/src/service/domain/workflow/subproject_list.ts index 4a53df5cc..6fd77fa56 100644 --- a/api/src/service/domain/workflow/subproject_list.ts +++ b/api/src/service/domain/workflow/subproject_list.ts @@ -49,6 +49,7 @@ const requiredPermissions = new Map([ ["subproject_archived", ["subproject.viewSummary", "subproject.viewDetails"]], ["subproject_projected_budget_updated", ["subproject.viewDetails"]], ["subproject_projected_budget_deleted", ["subproject.viewDetails"]], + ["workflowitems_reordered", ["subproject.reorderWorkflowitems"]], ]); function dropHiddenHistoryEvents( diff --git a/api/src/service/domain/workflow/subproject_permission_grant.ts b/api/src/service/domain/workflow/subproject_permission_grant.ts index 90f6fff57..10ec2bdab 100644 --- a/api/src/service/domain/workflow/subproject_permission_grant.ts +++ b/api/src/service/domain/workflow/subproject_permission_grant.ts @@ -1,8 +1,9 @@ +import { produce } from "immer"; import isEqual = require("lodash.isequal"); -import { produce } from "immer"; import Intent from "../../../authz/intents"; import { Ctx } from "../../../lib/ctx"; +import logger from "../../../lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; diff --git a/api/src/service/domain/workflow/workflowitem_assigned.ts b/api/src/service/domain/workflow/workflowitem_assigned.ts index 9473b5773..62dc77e91 100644 --- a/api/src/service/domain/workflow/workflowitem_assigned.ts +++ b/api/src/service/domain/workflow/workflowitem_assigned.ts @@ -75,12 +75,12 @@ export function apply( event: Event, workflowitem: Workflowitem.Workflowitem, ): Result.Type { - workflowitem.assignee = event.assignee; + const newState = { ...workflowitem, assignee: event.assignee }; - const result = Workflowitem.validate(workflowitem); + const result = Workflowitem.validate(newState); if (Result.isErr(result)) { - return new EventSourcingError(ctx, event, result.message, workflowitem.id); + return new EventSourcingError(ctx, event, result.message, newState.id); } - return workflowitem; + return newState; } diff --git a/api/src/service/subproject_get.ts b/api/src/service/subproject_get.ts index 647d096e0..2491c32c5 100644 --- a/api/src/service/subproject_get.ts +++ b/api/src/service/subproject_get.ts @@ -1,6 +1,7 @@ import { VError } from "verror"; import { Ctx } from "../lib/ctx"; +import logger from "../lib/logger"; import * as Result from "../result"; import * as Cache from "./cache2"; import { ConnToken } from "./conn"; @@ -23,7 +24,6 @@ export async function getSubproject( }, }), ); - // return subproject; return Result.mapErr( subprojectResult, err => new VError(err, `could not read subproject ${subprojectId} from chain`), diff --git a/api/src/service/subproject_list.ts b/api/src/service/subproject_list.ts index 4e6b9b502..c4bb5f778 100644 --- a/api/src/service/subproject_list.ts +++ b/api/src/service/subproject_list.ts @@ -15,7 +15,7 @@ export async function listSubprojects( const visibleSubprojects = await Cache.withCache(conn, ctx, async cache => SubprojectList.getAllVisible(ctx, serviceUser, { getAllSubprojects: async () => { - return cache.getSubprojects(projectId); + return await cache.getSubprojects(projectId); }, }), ); diff --git a/api/src/service/subproject_permission_grant.ts b/api/src/service/subproject_permission_grant.ts index a26c52acf..29f1b95a6 100644 --- a/api/src/service/subproject_permission_grant.ts +++ b/api/src/service/subproject_permission_grant.ts @@ -11,6 +11,7 @@ import * as ProjectPermissionGrant from "./domain/workflow/project_permission_gr import * as Subproject from "./domain/workflow/subproject"; import * as SubprojectPermissionGrant from "./domain/workflow/subproject_permission_grant"; import { store } from "./store"; +import logger from "../lib/logger"; export { RequestData } from "./domain/workflow/project_create"; diff --git a/api/src/subproject_permission_grant.ts b/api/src/subproject_permission_grant.ts index 56e635772..cf9427540 100644 --- a/api/src/subproject_permission_grant.ts +++ b/api/src/subproject_permission_grant.ts @@ -12,6 +12,7 @@ import { Identity } from "./service/domain/organization/identity"; import { ServiceUser } from "./service/domain/organization/service_user"; import * as Project from "./service/domain/workflow/project"; import * as Subproject from "./service/domain/workflow/subproject"; +import logger from "./lib/logger"; interface RequestBodyV1 { apiVersion: "1.0"; @@ -122,7 +123,6 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi } const { projectId, subprojectId, identity: grantee, intent } = bodyResult.data; - service .grantSubprojectPermission(ctx, user, projectId, subprojectId, grantee, intent) .then(() => { diff --git a/api/src/subproject_view_details.ts b/api/src/subproject_view_details.ts index 9b03ccfdc..665192a20 100644 --- a/api/src/subproject_view_details.ts +++ b/api/src/subproject_view_details.ts @@ -92,62 +92,29 @@ function mkSwaggerSchema(server: FastifyInstance) { items: { type: "object", properties: { - key: { type: "string" }, - intent: { type: "string", example: "global.createProject" }, - createdBy: { type: "string", example: "aSmith" }, - createdAt: { type: "string", example: "2018-09-05T13:37:25.775Z" }, - dataVersion: { type: "string", example: "1" }, - data: { + entityId: { type: "string", example: "d0e8c69eg298c87e3899119e025eff1f" }, + entityType: { type: "string", example: "subproject" }, + businessEvent: { type: "object", + additionalProperties: true, properties: { - subproject: { - type: "object", - properties: { - id: { - type: "string", - example: "d0e8c69eg298c87e3899119e025eff1f", - }, - creationUnixTs: { type: "string", example: "1536154645775" }, - status: { type: "string", example: "open" }, - displayName: { type: "string", example: "school" }, - description: { - type: "string", - example: "school should be built", - }, - billingDate: { - type: "string", - example: "2018-12-11T00:00:00.000Z", - }, - exchangeRate: { type: "string", example: "1.0" }, - assignee: { type: "string", example: "aSmith" }, - currency: { type: "string", example: "EUR" }, - projectedBudgets: { - type: "array", - items: { - type: "object", - properties: { - organization: { type: "string", example: "MyOrga" }, - value: { type: "string", example: "1234" }, - currencyCode: { type: "string", example: "EUR" }, - }, - }, - }, - thumbnail: { type: "string", example: "/Thumbnail_0001.jpg" }, - }, - }, - permissions: { - type: "object", - additionalProperties: true, - example: { - "subproject.intent.listPermissions": ["aSmith", "jDoe"], - }, - }, - snapshot: { - type: "object", - properties: { - displayName: { type: "string", example: "school" }, - }, - }, + type: { type: "string" }, + source: { type: "string" }, + time: { type: "string" }, + publisher: { type: "string" }, + }, + example: { + type: "subproject_closed", + source: "http", + time: "2018-09-05T13:37:25.775Z", + publisher: "jdoe", + }, + }, + snapshot: { + type: "object", + additionalProperties: true, + properties: { + displayName: { type: "string", example: "townproject" }, }, }, }, @@ -325,7 +292,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(workflowitemsResult)) { workflowitemsResult.message = `subproject.viewDetails failed: ${ workflowitemsResult.message - }`; + }`; throw workflowitemsResult; } diff --git a/api/src/subproject_view_history.ts b/api/src/subproject_view_history.ts index 7c454960e..7eac3b871 100644 --- a/api/src/subproject_view_history.ts +++ b/api/src/subproject_view_history.ts @@ -89,25 +89,27 @@ function mkSwaggerSchema(server: FastifyInstance) { items: { type: "object", properties: { - key: { type: "string" }, - intent: { type: "string", example: "global.createProject" }, - createdBy: { type: "string", example: "aSmith" }, - createdAt: { type: "string", example: "2018-09-05T13:37:25.775Z" }, - dataVersion: { type: "string", example: "1" }, - data: { + entityId: { type: "string", example: "d0e8c69eg298c87e3899119e025eff1f" }, + entityType: { type: "string", example: "subproject" }, + businessEvent: { type: "object", additionalProperties: true, - example: { identity: "aSmith", intent: "subproject.viewDetails" }, properties: { - permissions: { - type: "object", - additionalProperties: true, - example: { "subproject.intent.listPermissions": ["aSmith", "jDoe"] }, - }, + type: { type: "string" }, + source: { type: "string" }, + time: { type: "string" }, + publisher: { type: "string" }, + }, + example: { + type: "subproject_closed", + source: "http", + time: "2018-09-05T13:37:25.775Z", + publisher: "jdoe", }, }, snapshot: { type: "object", + additionalProperties: true, properties: { displayName: { type: "string", example: "townproject" }, }, @@ -223,7 +225,6 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi throw subprojectResult; } const subproject: Subproject.Subproject = subprojectResult; - // Add subprojects' logs to the project log and sort by creation time: const workflowitemsResult = await service.getWorkflowitems( ctx, diff --git a/e2e-test/cypress/screenshots/version_spec.js/Component Versions -- Shows frontend version (failed).png b/e2e-test/cypress/screenshots/version_spec.js/Component Versions -- Shows frontend version (failed).png new file mode 100644 index 000000000..7ba0f209d Binary files /dev/null and b/e2e-test/cypress/screenshots/version_spec.js/Component Versions -- Shows frontend version (failed).png differ diff --git a/frontend/src/languages/english.js b/frontend/src/languages/english.js index 5b8cd241c..a5b7f18e5 100644 --- a/frontend/src/languages/english.js +++ b/frontend/src/languages/english.js @@ -284,7 +284,6 @@ const en = { }, history: { - changed_by: "{0} changed by {1} ", created_project: "Project created ", created_subproject: "Subproject {0} created", created_workflow: "Workflow {0} created ", @@ -299,24 +298,34 @@ const en = { edit_workflowName: "Name of workflow item {0} changed to {1} ", first_sort: "Moved {0} to first position", project_assign: "{0} assigned project {1} to {2}", + project_close: "{0} closed project {1}", project_create: "{0} created project {1}", project_createSubproject: "{0} created subproject {1}", project_grantPermission: "{0} granted permission {1} to {2}", + project_grantPermission_details: "{0} granted permission {1} to {2} on {3}", project_revokePermission: "{0} revoked permission {1} from {2}", + project_revokePermission_details: "{0} revoked permission {1} from {2} on {3}", + project_update: "{0} changed project {1} ", sort: "Moved {0} after {1}", subproject_assign: "{0} assigned subproject {1} to {2}", - subproject_close: "{0} closed subproject {1}", + subproject_close: "{0} closed subproject {1}", + subproject_create: "{0} created subproject {1}", subproject_createWorkflowitem: "{0} created workflowitem {1}", subproject_grantPermission: "{0} granted permission {1} to {2}", subproject_grantPermission_details: "{0} granted permission {1} to {2} on {3}", - subproject_reorderWorkflowitems: "{0} changed the workflowitem ordering", + subproject_reorderWorkflowitems: "{0} changed the workflowitem ordering from {1}", subproject_revokePermission: "{0} revoked permission {1} from {2}", - subproject_revokePermission_details: "{0} revoked permission {1} of {3} from {2}", + subproject_revokePermission_details: "{0} revoked permission {1} from {2} on {3}", + subproject_update: "{0} changed subproject {1} ", to: "{0} to {1}", workflowitem_assign: "{0} assigned workflowitem {1} to {2}", workflowitem_close: "{0} closed workflowitem {1}", - workflowitem_grantPermission: "{0} granted permission {1} to {2} on {3}", - workflowitem_revokePermission: "{0} revoked permission {1} of {3} from {2}" + workflowitem_grantPermission: "{0} granted permission {1} to {2}", + workflowitem_grantPermission_details: "{0} granted permission {1} to {2} on {3}", + workflowitem_revokePermission: "{0} revoked permission {1} from {2}", + workflowitem_revokePermission_details: "{0} revoked permission {1} from {2} on {3}", + workflowitem_update: "{0} changed workflowitem {1} ", + workflowitem_update_docs: "{0} added documents to workflowitem {1} " }, permissions: { diff --git a/frontend/src/languages/french.js b/frontend/src/languages/french.js index ef81b04a2..7372a529e 100644 --- a/frontend/src/languages/french.js +++ b/frontend/src/languages/french.js @@ -286,7 +286,6 @@ const fr = { }, history: { - changed_by: "{0} changé by {1}: ", created_project: "Projet créé ", created_subproject: "Sous-projet {0} créé", created_workflow: "Workflow {0} créé ", @@ -300,25 +299,35 @@ const fr = { edit_subproject: "Montant de {0} augmenté à {1}", edit_workflowName: "Nom de l'élément de workflow {0} modifié à {1} ", first_sort: "Déplacé {0} au premier poste", - project_create: "{0} a crée project {1}", - project_createSubproject: "{0} a crée subproject {1}", + project_assign: "{0} a assigné le projet {1} à {2}", + project_close: "projet proche", + project_create: "{0} a crée projet {1}", + project_createSubproject: "{0} a crée sous-projet {1}", project_grantPermission: "{0} a accordé l'autorisation {1} à {2}", + project_grantPermission_details: "{0} a modifié l'autorisation {1} à {2} de {3}", project_revokePermission: "{0} a révoquer l'autorisation {1} de {2}", - project_assign: "{0} a assigné le projet {1} à {2}", + project_revokePermission_details: "{0} a revoqué l'autorisation {1} à {2} de {3}", + project_update: "{0} a modifié le projet {1} ", sort: "Déplacé {0} après {1}", subproject_assign: "{0} a assigné le projet {1} à {2}", - subproject_close: "{0} a terminé le sous-project {1}", + subproject_close: "{0} a terminé le sous-projet {1}", + subproject_create: "{0} a créé un projet {1}", subproject_createWorkflowitem: "{0} a crée l'élément de workflow {1}", subproject_grantPermission: "{0} a modifié l'autorisation {1} à {2}", - subproject_grantPermission_details: "{0} modifié l'autorisation {1} à {2} de {3}", + subproject_grantPermission_details: "{0} a modifié l'autorisation {1} à {2} de {3}", subproject_reorderWorkflowitems: "{0} a changé l'ordre des workflows", subproject_revokePermission: "{0} a revoqué l'autorisation {1} de {2}", subproject_revokePermission_details: "{0} a revoqué l'autorisation {1} à {3} de {2}", + subproject_update: "{0} a modifié le sous-projet {1} ", to: "{0} à {1}", workflowitem_assign: "{0} a assigné l'élément de workflow {1} à {2}", workflowitem_close: "{0} a terminé l'élément de workflow {1}", workflowitem_grantPermission: "{0} a modifié l'autorisation {1} à {2} de {3}", - workflowitem_revokePermission: "{0} a révoqué l'autorisation {1} à {3} de {2}" + workflowitem_grantPermission_details: "{0} a modifié l'autorisation {1} à {2} de {3}", + workflowitem_revokePermission: "{0} a révoqué l'autorisation {1} à {3} de {2}", + workflowitem_revokePermission_details: "{0} a revoqué l'autorisation {1} à {3} de {2}", + workflowitem_update: "{0} a modifié le workflow {1} ", + workflowitem_update_docs: "{0} a ajouté des documents au workflow {1} " }, permissions: { diff --git a/frontend/src/languages/german.js b/frontend/src/languages/german.js index 79935ed33..2fce429d6 100644 --- a/frontend/src/languages/german.js +++ b/frontend/src/languages/german.js @@ -284,7 +284,6 @@ const de = { }, history: { - changed_by: "German: {0} changed by {1}: ", created_project: "Project created ", created_subproject: "Subproject {0} created", created_workflow: "Workflow {0} created ", @@ -299,24 +298,34 @@ const de = { edit_workflowName: "Name of workflow item {0} changed to {1} ", first_sort: "Moved {0} to first position", project_assign: "German: {0} assigned project {1} to {2}", + project_close: "Schließe Project", project_create: "German: {0} created project {1}", project_createSubproject: "German: {0} created subproject {1}", project_grantPermission: "German: {0} granted permission {1} to {2}", + project_grantPermission_details: "{0} gab Rechte {1} an {2} für {3}", project_revokePermission: "German: {0} revoked permission {1} from {2}", + project_revokePermission_details: "{0} entzog Rechte {1} von {2} für {3}", + project_update: "{0} veränderte Projekt {1} ", sort: "Moved {0} after {1}", subproject_assign: "German: {0} assigned project {1} to {2}", subproject_close: "German: {0} closed subproject {1}", + subproject_create: "{0} erstellte Subprojekt {1}", subproject_createWorkflowitem: "German: {0} created workflowitem {1}", subproject_grantPermission: "German: {0} granted permission {1} to {2}", subproject_grantPermission_details: "German: {0} granted permission {1} to {2} on {3}", subproject_reorderWorkflowitems: "German: {0} changed the workflowitem ordering", subproject_revokePermission: "German: {0} revoked permission {1} from {2}", subproject_revokePermission_details: "German: {0} revoked permission {1} of {3} from {2}", + subproject_update: "{0} veränderte Subprojekt {1} ", to: "German: {0} to {1}", workflowitem_assign: "German: {0} assigned workflowitem {1} to {2}", workflowitem_close: "German: {0} closed workflowitem {1}", workflowitem_grantPermission: "German: {0} granted permission {1} to {2} on {3}", - workflowitem_revokePermission: "German: {0} revoked permission {1} of {3} from {2}" + workflowitem_grantPermission_details: "German: {0} granted permission {1} to {2} on {3}", + workflowitem_revokePermission: "German: {0} revoked permission {1} of {3} from {2}", + workflowitem_revokePermission_details: "German: {0} revoked permission {1} to {2} on {3}", + workflowitem_update: "{0} veränderte Workflowitem {1} ", + workflowitem_update_docs: "{0} fügte Dokumente zu Workflowitem {1} hinzu " }, permissions: { diff --git a/frontend/src/languages/portuguese.js b/frontend/src/languages/portuguese.js index 542c0d888..264e3dfbb 100644 --- a/frontend/src/languages/portuguese.js +++ b/frontend/src/languages/portuguese.js @@ -285,7 +285,6 @@ const pt = { }, history: { - changed_by: "Portuguese: {0} changed by {1}: ", created_project: "Projeto criado ", created_subproject: "Subprojeto {0} criado", created_workflow: "Workflow {0} criado ", @@ -300,24 +299,34 @@ const pt = { edit_workflowName: "Nome do workflow {0} mudou para {1} ", first_sort: "{0} foi movido para a primeira posição", project_assign: "Portuguese: {0} assigned project {1} to {2}", + project_close: "fechar projeto", project_create: "Portuguese: {0} created project {1}", project_createSubproject: "Portuguese: {0} created subproject {1}", project_grantPermission: "Portuguese: {0} granted permission {1} to {2}", + project_grantPermission_details: "Portuguese: {0} granted permission {1} to {2} on {3}", project_revokePermission: "Portuguese: {0} revoked permission {1} from {2}", + project_revokePermission_details: "Portuguese: {0} revoked permission {1} of {3} from {2}", + project_update: "{0} modificou o projeto {1} ", sort: "{0} foi movido após {1}", subproject_assign: "Portuguese: {0} assigned project {1} to {2}", subproject_close: "Portuguese: {0} closed subproject {1}", + subproject_create: "{0} criou o projeto {1}", subproject_createWorkflowitem: "Portuguese: {0} created workflowitem {1}", subproject_grantPermission: "Portuguese: {0} granted permission {1} to {2}", subproject_grantPermission_details: "Portuguese: {0} granted permission {1} to {2} on {3}", subproject_reorderWorkflowitems: "Portuguese: {0} changed the workflowitem ordering", subproject_revokePermission: "Portuguese: {0} revoked permission {1} from {2}", subproject_revokePermission_details: "Portuguese: {0} revoked permission {1} of {3} from {2}", + subproject_update: "{0} modificou o subprojeto {1} ", to: "Portuguese: {0} to {1}", workflowitem_assign: "Portuguese: {0} assigned workflowitem {1} to {2}", workflowitem_close: "Portuguese: {0} closed workflowitem {1}", workflowitem_grantPermission: "Portuguese: {0} granted permission {1} to {2} on {3}", - workflowitem_revokePermission: "Portuguese: {0} revoked permission {1} of {3} from {2}" + workflowitem_grantPermission_details: "Portuguese: {0} granted permission {1} to {2} on {3}", + workflowitem_revokePermission: "Portuguese: {0} revoked permission {1} of {3} from {2}", + workflowitem_revokePermission_details: "Portuguese: {0} revoked permission {1} of {3} from {2}", + workflowitem_update: "{0} modificou o workflow {1} ", + workflowitem_update_docs: "{0} adicionou documentos ao {1} " }, permissions: { diff --git a/frontend/src/pages/Common/History/ResourceHistory.js b/frontend/src/pages/Common/History/ResourceHistory.js index b83a4d7c2..695269c75 100644 --- a/frontend/src/pages/Common/History/ResourceHistory.js +++ b/frontend/src/pages/Common/History/ResourceHistory.js @@ -61,33 +61,32 @@ export default ({ show, close, resourceHistory, - mapIntent + mapIntent, + userDisplayNameMap }) => { let items = []; - resourceHistory.map((i, index) => - items.push( + resourceHistory.map((i, index) => { + return items.push( - ) - ); - const hasMore = offset + limit >= historyItemsCount && historyItemsCount !== 0 ? false : true; + ); + }); + // limit is set to 0 by saga when fetching the last items + const hasMore = limit !== 0 ? true : false; if (!hasMore && !isLoading) { items.push( diff --git a/frontend/src/pages/Login/reducer.js b/frontend/src/pages/Login/reducer.js index 3bf37a395..9cd892ac2 100644 --- a/frontend/src/pages/Login/reducer.js +++ b/frontend/src/pages/Login/reducer.js @@ -38,7 +38,8 @@ export const defaultState = fromJS({ jwt: "", adminLoginFailed: false, language: "en-gb", - user: [] + user: [], + userDisplayNameMap: {} }); export const changeLanguage = state => { @@ -54,7 +55,11 @@ export default function loginReducer(state = defaultState, action) { case STORE_PASSWORD: return state.set("password", action.password); case FETCH_USER_SUCCESS: - return state.set("user", fromJS(action.user)); + const userMap = {}; + action.user.forEach(user => { + userMap[user.id] = user.displayName; + }); + return state.merge({ user: fromJS(action.user), userDisplayNameMap: fromJS(userMap) }); case FETCH_ADMIN_USER_SUCCESS: return state.merge({ loggedInAdminUser: action.user, diff --git a/frontend/src/pages/SubProjects/ProjectHistoryContainer.js b/frontend/src/pages/SubProjects/ProjectHistoryContainer.js index 57846d06e..3c0483a7b 100644 --- a/frontend/src/pages/SubProjects/ProjectHistoryContainer.js +++ b/frontend/src/pages/SubProjects/ProjectHistoryContainer.js @@ -21,39 +21,47 @@ const calculateHistory = items => { const mapIntent = ({ createdBy, intent, data, snapshot }) => { switch (intent) { - // check old and new intents case "project_created": - case "global.createProject": return formatString(strings.history.project_create, createdBy, snapshot.displayName); - case "project_permission_granted": - case "project.intent.grantPermission": - return formatString(strings.history.project_grantPermission, createdBy, formatPermission(data), data.identity); - case "project_permission_revoked": - case "project.intent.revokePermission": - return formatString(strings.history.project_revokePermission, createdBy, formatPermission(data), data.identity); - case "project_createSubprojected": - case "project.createSubproject": - return formatString(strings.history.project_createSubproject, createdBy, snapshot.displayName); + case "project_updated": + return formatString(strings.history.project_update, createdBy, snapshot.displayName); case "project_assigned": - case "project.assign": return formatString(strings.history.project_assign, createdBy, snapshot.displayName, data.identity); + case "project_closed": + return formatString(strings.history.project_close, createdBy, snapshot.displayName); + case "project_permission_granted": + return formatString( + strings.history.project_grantPermission_details, + createdBy, + formatPermission(data), + data.identity, + snapshot.displayName + ); + case "project_permission_revoked": + return formatString( + strings.history.project_revokePermission_details, + createdBy, + formatPermission(data), + data.identity, + snapshot.displayName + ); + case "subproject_created": + return formatString(strings.history.subproject_create, createdBy, snapshot.displayName); + case "subproject_updated": + return formatString(strings.history.subproject_update, createdBy, snapshot.displayName); case "subproject_assigned": - case "subproject.assign": return formatString(strings.history.subproject_assign, createdBy, snapshot.displayName, data.identity); case "subproject_closed": - case "subproject.close": return formatString(strings.history.subproject_close, createdBy, snapshot.displayName); - case "subproject_intent.grantPermissioned": - case "subproject.intent.grantPermission": - return formatString(strings.history.project_grantPermission, createdBy, "", "subproject"); - case "project_updated": - case "project.update": - return strings.formatString(strings.history.changed_by, snapshot.displayName, createdBy); - case "subproject_updated": - case "subproject.update": - return strings.formatString(strings.history.changed_by, snapshot.displayName, createdBy); - case "subproject_intent.revokePermissioned": - case "subproject.intent.revokePermission": + case "subproject_permission_granted": + return formatString( + strings.history.subproject_grantPermission_details, + createdBy, + formatPermission(data), + data.identity, + snapshot.displayName + ); + case "subproject_permission_revoked": return formatString( strings.history.subproject_revokePermission_details, createdBy, @@ -61,6 +69,8 @@ const mapIntent = ({ createdBy, intent, data, snapshot }) => { data.identity, snapshot.displayName ); + case "workflowitems_reordered": + return formatString(strings.history.subproject_reorderWorkflowitems, createdBy, snapshot.displayName); default: console.log("WARN: Intent not defined:", intent); return intent; @@ -92,8 +102,7 @@ class ProjectHistoryContainer extends Component { } fetchNextHistoryItems = () => { - const newOffset = this.props.offset + this.props.limit; - this.props.fetchProjectHistory(this.props.projectId, newOffset, this.props.limit); + this.props.fetchProjectHistory(this.props.projectId, this.props.offset, this.props.limit); }; render() { @@ -103,6 +112,7 @@ class ProjectHistoryContainer extends Component { isLoading={this.state.isLoading} resourceHistory={this.state.resourceHistory} mapIntent={mapIntent} + userDisplayNameMap={this.state.userDisplayNameMap} {...this.props} /> ); @@ -114,7 +124,8 @@ const mapStateToProps = state => { items: state.getIn(["detailview", "historyItems"]), historyItemsCount: state.getIn(["detailview", "historyItemsCount"]), show: state.getIn(["notifications", "showHistory"]), - isLoading: state.getIn(["detailview", "isHistoryLoading"]) + isLoading: state.getIn(["detailview", "isHistoryLoading"]), + userDisplayNameMap: state.getIn(["login", "userDisplayNameMap"]) }; }; const mapDispatchToProps = dispatch => { diff --git a/frontend/src/pages/SubProjects/reducer.js b/frontend/src/pages/SubProjects/reducer.js index f2946af2a..3a7a3dd2c 100644 --- a/frontend/src/pages/SubProjects/reducer.js +++ b/frontend/src/pages/SubProjects/reducer.js @@ -137,10 +137,11 @@ export default function detailviewReducer(state = defaultState, action) { return state.set("isHistoryLoading", true); case FETCH_PROJECT_HISTORY_SUCCESS: return state.merge({ - historyItems: [...state.get("historyItems"), ...fromJS(action.events)], + historyItems: fromJS(action.events).concat(state.get("historyItems")), historyItemsCount: action.historyItemsCount, isHistoryLoading: false, - offset: action.offset + offset: action.offset, + limit: action.limit }); case SHOW_SUBPROJECT_EDIT: { return state.merge({ @@ -165,7 +166,8 @@ export default function detailviewReducer(state = defaultState, action) { case HIDE_HISTORY: return state.merge({ historyItems: fromJS([]), - offset: 0 + offset: defaultState.get("offset"), + limit: defaultState.get("limit") }); case LOGOUT: return defaultState; diff --git a/frontend/src/pages/Workflows/SubProjectHistoryContainer.js b/frontend/src/pages/Workflows/SubProjectHistoryContainer.js index db4a26312..028cc44dc 100644 --- a/frontend/src/pages/Workflows/SubProjectHistoryContainer.js +++ b/frontend/src/pages/Workflows/SubProjectHistoryContainer.js @@ -1,14 +1,14 @@ -import React, { Component } from "react"; -import { connect } from "react-redux"; import { fromJS } from "immutable"; - import sortBy from "lodash/sortBy"; +import isEmpty from "lodash/isEmpty"; +import React, { Component } from "react"; +import { connect } from "react-redux"; -import ResourceHistory from "../Common/History/ResourceHistory"; -import { hideHistory } from "../Notifications/actions"; +import { formatString, toJS } from "../../helper"; import strings from "../../localizeStrings"; -import { toJS, formatString, formatUpdateString } from "../../helper"; import { formatPermission } from "../Common/History/helper"; +import ResourceHistory from "../Common/History/ResourceHistory"; +import { hideHistory } from "../Notifications/actions"; import { fetchSubprojectHistory, setSubProjectHistoryOffset } from "./actions"; const calculateHistory = items => { @@ -22,56 +22,58 @@ const calculateHistory = items => { const mapIntent = ({ createdBy, intent, data, snapshot }) => { switch (intent) { - case "project.createSubproject": - return formatString(strings.history.project_createSubproject, createdBy, snapshot.displayName); - case "subproject.createWorkflowitem": - return formatString(strings.history.subproject_createWorkflowitem, createdBy, snapshot.displayName); - case "subproject.assign": + case "subproject_created": + return formatString(strings.history.subproject_create, createdBy, snapshot.displayName); + case "subproject_assigned": return formatString(strings.history.subproject_assign, createdBy, snapshot.displayName, data.identity); - case "workflowitem.close": - return formatString(strings.history.workflowitem_close, createdBy, snapshot.displayName); - case "subproject.close": + case "subproject_updated": + return formatString(strings.history.subproject_update, createdBy, snapshot.displayName); + case "subproject_closed": return formatString(strings.history.subproject_close, createdBy, snapshot.displayName); - case "subproject.intent.grantPermission": + case "workflowitems_reordered": + return formatString(strings.history.subproject_reorderWorkflowitems, createdBy, snapshot.displayName); + case "subproject_permission_granted": return formatString( - strings.history.subproject_grantPermission, + strings.history.subproject_grantPermission_details, createdBy, formatPermission(data), data.identity, snapshot.displayName ); - case "workflowitem.intent.grantPermission": + case "subproject_permission_revoked": return formatString( - strings.history.workflowitem_grantPermission, + strings.history.subproject_revokePermission_details, createdBy, formatPermission(data), data.identity, snapshot.displayName ); - case "subproject.intent.revokePermission": + case "workflowitem_created": + return formatString(strings.history.subproject_createWorkflowitem, createdBy, snapshot.displayName); + case "workflowitem_updated": + return isEmpty(data.update.documents) + ? formatString(strings.history.workflowitem_update, createdBy, snapshot.displayName) + : formatString(strings.history.workflowitem_update_docs, createdBy, snapshot.displayName); + case "workflowitem_assigned": + return formatString(strings.history.workflowitem_assign, createdBy, snapshot.displayName, data.identity); + case "workflowitem_closed": + return formatString(strings.history.workflowitem_close, createdBy, snapshot.displayName); + case "workflowitem_permission_granted": return formatString( - strings.history.subproject_revokePermission, + strings.history.workflowitem_grantPermission_details, createdBy, formatPermission(data), data.identity, snapshot.displayName ); - case "workflowitem.update": - return formatUpdateString(strings.common.workflowItem, createdBy, data); - case "subproject.update": - return formatUpdateString(strings.common.subproject, createdBy, data); - case "workflowitem.intent.revokePermission": + case "workflowitem_permission_revoked": return formatString( - strings.history.workflowitem_revokePermission, + strings.history.workflowitem_revokePermission_details, createdBy, formatPermission(data), data.identity, snapshot.displayName ); - case "workflowitem.assign": - return formatString(strings.history.workflowitem_assign, createdBy, snapshot.displayName, data.identity); - case "subproject.reorderWorkflowitems": - return formatString(strings.history.subproject_reorderWorkflowitems, createdBy); default: console.log("WARN: Intent not defined:", intent); return intent; @@ -103,12 +105,24 @@ class SubProjectHistoryContainer extends Component { } fetchNextHistoryItems = () => { - const newOffset = this.props.offset + this.props.limit; - this.props.fetchSubProjectHistory(this.props.projectId, this.props.subprojectId, newOffset, this.props.limit) + this.props.fetchSubProjectHistory( + this.props.projectId, + this.props.subprojectId, + this.props.offset, + this.props.limit + ); }; render() { - return ; + return ( + + ); } } @@ -116,14 +130,16 @@ const mapStateToProps = state => { return { items: state.getIn(["workflow", "historyItems"]), historyItemsCount: state.getIn(["workflow", "historyItemsCount"]), - show: state.getIn(["notifications", "showHistory"]) + show: state.getIn(["notifications", "showHistory"]), + userDisplayNameMap: state.getIn(["login", "userDisplayNameMap"]) }; }; const mapDispatchToProps = dispatch => { return { close: () => dispatch(hideHistory()), setSubProjectHistoryOffset: offset => dispatch(setSubProjectHistoryOffset(offset)), - fetchSubProjectHistory: (projectId, subprojectId, offset, limit) => dispatch(fetchSubprojectHistory(projectId, subprojectId, offset, limit, false)) + fetchSubProjectHistory: (projectId, subprojectId, offset, limit) => + dispatch(fetchSubprojectHistory(projectId, subprojectId, offset, limit, false)) }; }; diff --git a/frontend/src/pages/Workflows/reducer.js b/frontend/src/pages/Workflows/reducer.js index 16a35a297..b1d7dd7d2 100644 --- a/frontend/src/pages/Workflows/reducer.js +++ b/frontend/src/pages/Workflows/reducer.js @@ -287,15 +287,17 @@ export default function detailviewReducer(state = defaultState, action) { return state.set("isHistoryLoading", true); case FETCH_SUBPROJECT_HISTORY_SUCCESS: return state.merge({ - historyItems: [...state.get("historyItems"), ...fromJS(action.events)], + historyItems: fromJS(action.events).concat(state.get("historyItems")), historyItemsCount: action.historyItemsCount, isHistoryLoading: false, - offset: action.offset + offset: action.offset, + limit: action.limit }); case HIDE_HISTORY: return state.merge({ historyItems: fromJS([]), - offset: 0 + offset: defaultState.get("offset"), + limit: defaultState.get("limit") }); case STORE_WORKFLOWACTIONS: return state.set("workflowActions", fromJS(action.actions)); diff --git a/frontend/src/sagas.js b/frontend/src/sagas.js index 4944a92bf..25e0eb27a 100644 --- a/frontend/src/sagas.js +++ b/frontend/src/sagas.js @@ -749,10 +749,16 @@ export function* fetchAllProjectDetailsSaga({ projectId, showLoading }) { export function* fetchProjectHistorySaga({ projectId, offset, limit, showLoading }) { yield execute(function*() { - const { data } = yield callApi(api.viewProjectHistory, projectId, offset, limit); + if (limit <= 0) { + return; + } + const { data } = yield callApi(api.viewProjectHistory, projectId, offset - limit, limit); + const newOffset = offset === 0 ? data.historyItemsCount - limit : offset - limit; + const newLimit = newOffset > 0 ? Math.min(newOffset, limit) : 0; yield put({ type: FETCH_PROJECT_HISTORY_SUCCESS, - offset, + offset: newOffset, + limit: newLimit, ...data }); }, showLoading); @@ -770,10 +776,16 @@ export function* fetchAllSubprojectDetailsSaga({ projectId, subprojectId, showLo export function* fetchSubprojectHistorySaga({ projectId, subprojectId, offset, limit, showLoading }) { yield execute(function*() { - const { data } = yield callApi(api.viewSubProjectHistory, projectId, subprojectId, offset, limit); + if (limit <= 0) { + return; + } + const { data } = yield callApi(api.viewSubProjectHistory, projectId, subprojectId, offset - limit, limit); + const newOffset = offset === 0 ? data.historyItemsCount - limit : offset - limit; + const newLimit = newOffset > 0 ? Math.min(newOffset, limit) : 0; yield put({ type: FETCH_SUBPROJECT_HISTORY_SUCCESS, - offset, + offset: newOffset, + limit: newLimit, ...data }); }, showLoading);