diff --git a/waltz-data/src/main/java/org/finos/waltz/data/logical_flow/LogicalFlowDao.java b/waltz-data/src/main/java/org/finos/waltz/data/logical_flow/LogicalFlowDao.java index 530b296d58..20904d94a7 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/logical_flow/LogicalFlowDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/logical_flow/LogicalFlowDao.java @@ -346,6 +346,16 @@ public LogicalFlow getByFlowId(long dataFlowId) { .fetchOne(TO_DOMAIN_MAPPER); } + public long updateReadOnly(long flowId, boolean isReadOnly, String user) { + return dsl + .update(LOGICAL_FLOW) + .set(LOGICAL_FLOW.IS_READONLY, isReadOnly) + .set(LOGICAL_FLOW.LAST_UPDATED_AT, Timestamp.valueOf(nowUtc())) + .set(LOGICAL_FLOW.LAST_UPDATED_BY, user) + .where(LOGICAL_FLOW.ID.eq(flowId)) + .execute(); + } + public List findAllActive() { return baseQuery() diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/LogicalFlowServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/LogicalFlowServiceTest.java index acadcd9ee8..33013269b5 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/LogicalFlowServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/LogicalFlowServiceTest.java @@ -430,4 +430,27 @@ public void findUpstreamFlowsForEntityReferences() { assertEquals(3, allUpstreams.size(), "Returns all upstreams but not downstreams"); } + @Test + public void updateReadOnlyTest() { + helper.clearAllFlows(); + + EntityReference a = appHelper.createNewApp("xyz-app", ouIds.a); + EntityReference b = appHelper.createNewApp("xyz-app-2", ouIds.a); + + LogicalFlow logicalFlow = helper.createLogicalFlow(a, b); + + LogicalFlow updatedFlow = lfSvc.updateReadOnly(logicalFlow.id().get(), true, "updateTestUser"); + assertTrue(updatedFlow.isReadOnly()); + + updatedFlow = lfSvc.updateReadOnly(logicalFlow.id().get(), false, "updateTestUser"); + assertFalse(updatedFlow.isReadOnly()); + + updatedFlow = lfSvc.updateReadOnly(122, true, "updateTestUser"); + assertNull(updatedFlow); + + + updatedFlow = lfSvc.updateReadOnly(logicalFlow.id().get(), false, "updateTestUser"); + assertFalse(updatedFlow.isReadOnly()); + } + } \ No newline at end of file diff --git a/waltz-model/src/main/java/org/finos/waltz/model/logical_flow/UpdateReadOnlyCommand.java b/waltz-model/src/main/java/org/finos/waltz/model/logical_flow/UpdateReadOnlyCommand.java new file mode 100644 index 0000000000..0c3b23e0fd --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/logical_flow/UpdateReadOnlyCommand.java @@ -0,0 +1,13 @@ +package org.finos.waltz.model.logical_flow; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.finos.waltz.model.command.Command; +import org.immutables.value.Value; + +@Value.Immutable +@JsonSerialize(as = ImmutableUpdateReadOnlyCommand.class) +@JsonDeserialize(as = ImmutableUpdateReadOnlyCommand.class) +public interface UpdateReadOnlyCommand extends Command { + boolean readOnly(); +} diff --git a/waltz-ng/client/logical-flow/pages/view/logical-flow-view.html b/waltz-ng/client/logical-flow/pages/view/logical-flow-view.html index 3bcbf98547..e09d3049dd 100644 --- a/waltz-ng/client/logical-flow/pages/view/logical-flow-view.html +++ b/waltz-ng/client/logical-flow/pages/view/logical-flow-view.html @@ -128,6 +128,19 @@ +
+
+
+ Toggle Read Only +
+
+ +
+
+
diff --git a/waltz-ng/client/logical-flow/pages/view/logical-flow-view.js b/waltz-ng/client/logical-flow/pages/view/logical-flow-view.js index aca1eb9f43..fa75192efd 100644 --- a/waltz-ng/client/logical-flow/pages/view/logical-flow-view.js +++ b/waltz-ng/client/logical-flow/pages/view/logical-flow-view.js @@ -35,10 +35,12 @@ const initialState = { canEdit: false, canRestore: false, canRemove: false, + updateCommand: { + readOnly: false, + }, AlignedDataTypesList }; - function controller($q, $state, $stateParams, @@ -91,6 +93,22 @@ function controller($q, }; + const onToggleReadOnly = () => { + const changedField = !vm.logicalFlow.isReadOnly; + vm.updateCommand.readOnly = changedField; + return serviceBroker + .execute(CORE_API.LogicalFlowStore.updateReadOnly, [vm.updateCommand, vm.logicalFlow.id]) + .then(() => { + toasts.success("Successfully made the flow " + (changedField ? `read only` : `editable`) + '.'); + setTimeout(() => { + $window.location.reload(); + }, 600); + }) + .catch(e => { + toasts.error(e.data.message); + }); + } + const removeLogicalFlow = () => { return serviceBroker .execute(CORE_API.LogicalFlowStore.removeFlow, [vm.logicalFlow.id]) @@ -134,6 +152,10 @@ function controller($q, } }; + vm.onToggleReadOnly = () => { + onToggleReadOnly(); + } + vm.restoreFlow = () => { if (confirm("Are you sure you want to restore this flow ?")) { console.log("restoring", vm.logicalFlow); diff --git a/waltz-ng/client/logical-flow/services/logical-flow-store.js b/waltz-ng/client/logical-flow/services/logical-flow-store.js index ed5b3145ad..0d1794ae13 100644 --- a/waltz-ng/client/logical-flow/services/logical-flow-store.js +++ b/waltz-ng/client/logical-flow/services/logical-flow-store.js @@ -126,6 +126,10 @@ export function store($http, BaseApiUrl) { .get(`${BASE}/cleanup-self-references`) .then(r => r.data); + const updateReadOnly = (updateReadOnlyCmd, id) => $http + .post(`${BASE}/update/read-only/${id}`, updateReadOnlyCmd) + .then(r => r.data); + return { findBySelector, findByIds, @@ -144,7 +148,8 @@ export function store($http, BaseApiUrl) { findPermissionsForParentRef, findPermissionsForFlow, cleanupOrphans, - cleanupSelfReferences + cleanupSelfReferences, + updateReadOnly }; } @@ -252,6 +257,11 @@ export const LogicalFlowStore_API = { serviceFnName: "cleanupSelfReferences", description: "mark flows as removed where the flow source and target are the same" }, + updateReadOnly: { + serviceName, + serviceFnName: "updateReadOnly", + description: "update whether a logical flow is read only or editable" + }, }; diff --git a/waltz-service/src/main/java/org/finos/waltz/service/logical_flow/LogicalFlowService.java b/waltz-service/src/main/java/org/finos/waltz/service/logical_flow/LogicalFlowService.java index 0750c76a1f..db8e1e6521 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/logical_flow/LogicalFlowService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/logical_flow/LogicalFlowService.java @@ -222,6 +222,7 @@ public LogicalFlow getByExternalId(String externalId) { *
  • DATA_TYPE
  • *
  • APPLICATION
  • * + * * @param options given to logical flow selector factory to determine in-scope flows * @return a list of logical flows matching the given options */ @@ -233,10 +234,10 @@ public List findBySelector(IdSelectionOptions options) { /** * Creates a logical flow and creates a default, 'UNKNOWN' data type decoration * if possible. - * + *

    * If the flow already exists, but is inactive, the flow will be re-activated. * - * @param addCmd Command object containing flow details + * @param addCmd Command object containing flow details * @param username who is creating the flow * @return the newly created logical flow * @throws IllegalArgumentException if a flow already exists @@ -316,22 +317,54 @@ private void rejectIfSelfLoop(AddLogicalFlowCommand addCmd) { } } + public LogicalFlow updateReadOnly(long flowId, boolean isReadOnly, String user) { + LogicalFlow logicalFlow = getById(flowId); + LocalDateTime now = nowUtc(); + + if (logicalFlow != null) { + // if the flag is being set to what it already was -> should not happen but just in case + if (isReadOnly == logicalFlow.isReadOnly()) { + return logicalFlow; + } else { + // update the read only flag to what you want + logicalFlowDao.updateReadOnly(flowId, isReadOnly, user); + LogicalFlow updatedFlow = getById(logicalFlow.id().get()); + + ChangeLog changeLog = ImmutableChangeLog + .builder() + .parentReference(EntityReference.mkRef(LOGICAL_DATA_FLOW, logicalFlow.id().get())) + .operation(Operation.UPDATE) + .createdAt(now) + .userId(user) + .message(isReadOnly + ? format("Set to read only by waltz_support.") + : format("Set to editable by waltz_support.")) + .build(); + changeLogService.write(changeLog); + return updatedFlow; + } + } + + // return null if the flow was not found + return null; + } + /** * Removes the given logical flow and creates an audit log entry. * The removal is a soft removal. After the removal usage stats are recalculated - * + *

    * todo: #WALTZ-1894 for cleanupOrphans task * - * @param flowId identifier of flow to be removed - * @param username who initiated the removal + * @param flowId identifier of flow to be removed + * @param username who initiated the removal * @return number of flows removed */ public int removeFlow(Long flowId, String username) { LogicalFlow logicalFlow = logicalFlowDao.getByFlowId(flowId); - if(logicalFlow == null){ + if (logicalFlow == null) { LOG.warn("Logical flow cannot be found, no flows will be updated"); throw new IllegalArgumentException(format("Cannot find flow with id: %d, no logical flow removed", flowId)); } else { @@ -352,6 +385,7 @@ public int removeFlow(Long flowId, String username) { /** * Calculate Stats by selector + * * @param options determines which flows are in-scope for this calculation * @return statistics about the in-scope flows */ @@ -367,7 +401,7 @@ public LogicalFlowStatistics calculateStats(IdSelectionOptions options) { case DATA_TYPE: return calculateStatsForAppIdSelector(options); default: - throw new UnsupportedOperationException("Cannot calculate stats for selector kind: "+ options.entityReference().kind()); + throw new UnsupportedOperationException("Cannot calculate stats for selector kind: " + options.entityReference().kind()); } } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/taxonomy_management/BulkTaxonomyChangeService.java b/waltz-service/src/main/java/org/finos/waltz/service/taxonomy_management/BulkTaxonomyChangeService.java index 36820ca62d..a5b2189dfd 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/taxonomy_management/BulkTaxonomyChangeService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/taxonomy_management/BulkTaxonomyChangeService.java @@ -48,6 +48,7 @@ import org.finos.waltz.service.taxonomy_management.BulkTaxonomyItemParser.InputFormat; import org.finos.waltz.service.user.UserRoleService; import org.jooq.DSLContext; +import org.jooq.DeleteConditionStep; import org.jooq.UpdateConditionStep; import org.jooq.UpdateSetStep; import org.jooq.impl.DSL; @@ -280,6 +281,17 @@ public BulkTaxonomyApplyResult applyBulk(EntityReference taxonomyRef, }) .collect(Collectors.toSet()); + Set> toRemove = bulkRequest + .plannedRemovals() + .stream() + .map(r -> { + UpdateSetStep updRemove = DSL.update(org.finos.waltz.schema.tables.Measurable.MEASURABLE); + return updRemove + .set(org.finos.waltz.schema.tables.Measurable.MEASURABLE.ENTITY_LIFECYCLE_STATUS, EntityLifecycleStatus.REMOVED.name()) + .where(org.finos.waltz.schema.tables.Measurable.MEASURABLE.ID.eq(r.id().get())); + }) + .collect(Collectors.toSet()); + boolean requiresRebuild = requiresHierarchyRebuild(bulkRequest.validatedItems()); @@ -289,6 +301,7 @@ public BulkTaxonomyApplyResult applyBulk(EntityReference taxonomyRef, int insertCount = summarizeResults(tx.batchInsert(toAdd).execute()); int restoreCount = summarizeResults(tx.batch(toRestore).execute()); int updateCount = summarizeResults(tx.batch(toUpdate).execute()); + int removalCount = summarizeResults(tx.batch(toRemove).execute()); Set changeRecords = mkTaxonomyChangeRecords(tx, taxonomyRef, bulkRequest, userId); @@ -306,7 +319,7 @@ public BulkTaxonomyApplyResult applyBulk(EntityReference taxonomyRef, .recordsAdded(insertCount) .recordsUpdated(updateCount) .recordsRestored(restoreCount) - .recordsRemoved(0)//TODO: add removeCount + .recordsRemoved(removalCount) .hierarchyRebuilt(requiresRebuild) .build(); }); diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowEndpoint.java index c095b79bd8..23e9aafc7e 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowEndpoint.java @@ -18,6 +18,7 @@ package org.finos.waltz.web.endpoints.api; +import org.eclipse.jetty.util.IO; import org.finos.waltz.common.exception.InsufficientPrivelegeException; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.IdSelectionOptions; @@ -35,6 +36,7 @@ import org.finos.waltz.model.logical_flow.LogicalFlow; import org.finos.waltz.model.logical_flow.LogicalFlowGraphSummary; import org.finos.waltz.model.logical_flow.LogicalFlowStatistics; +import org.finos.waltz.model.logical_flow.UpdateReadOnlyCommand; import org.finos.waltz.model.user.SystemRole; import org.jooq.lambda.tuple.Tuple; import org.jooq.lambda.tuple.Tuple2; @@ -108,6 +110,7 @@ public void register() { String addFlowsPath = mkPath(BASE_URL, "list"); String getFlowGraphSummaryPath = mkPath(BASE_URL, "entity", ":kind", ":id", "data-type", ":dtId", "graph-summary"); String getFlowViewPath = mkPath(BASE_URL, "view"); + String updateReadOnlyPath = mkPath(BASE_URL, "update", "read-only", ":id"); ListRoute getByEntityRef = (request, response) -> logicalFlowService.findByEntityReference(getEntityReference(request)); @@ -177,6 +180,7 @@ public void register() { postForList(addFlowsPath, this::addFlowsRoute); putForDatum(restoreFlowPath, this::restoreFlowRoute); postForDatum(getFlowViewPath, getFlowViewRoute); + postForDatum(updateReadOnlyPath, this::updateReadOnly); } @@ -286,4 +290,18 @@ private void ensureUserHasEditRights(EntityReference source, EntityReference tar private void ensureUserHasAdminRights(Request request) { requireRole(userRoleService, request, SystemRole.ADMIN); } + + private LogicalFlow updateReadOnly(Request request, Response response) throws IOException { + long flowId = getId(request); + String user = getUsername(request); + UpdateReadOnlyCommand cmd = readBody(request, UpdateReadOnlyCommand.class); + + ensureUserHasAdminRights(request); + LogicalFlow resp = logicalFlowService.updateReadOnly(flowId, cmd.readOnly(), user); + if(resp == null) { + throw new IllegalArgumentException("No such Logical Flow exists"); + } + + return resp; + } }