diff --git a/.github/workflows/meetings.yml b/.github/workflows/meetings.yml deleted file mode 100644 index f7ab3b6c92..0000000000 --- a/.github/workflows/meetings.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: meeting-minutes-action - -# This GitHub action is used to manage meeting minutes via GitHub Issues: -# - The issue description contains minutes and other meeting meta info (see .github/ISSUE_TEMPLATE/Meeting.md) -# - Issue commenters are tracked as meetings attendants, and (eventually, not real time) published on metrics.finos.org -# -# When an issue (with "meeting" label) is closed, this action -# collects the issue commenters and uses FINOS metadata-tool to generate a CSV -# file with meeting attendance, which can be submitted for later ingestion and -# final publication into metrics.finos.org . After successful submission, -# the "indexed" label will be added to the issue. -# -# When the "indexed" label is removed, entries will be removed from the index, -# allowing to amend attendance after the meeting; by re-adding the "indexed", entries will be added again to the index. -# -# The date of the meeting is extracted from the date that the issue was closed, therefore: -# - Do not apply the "indexed" label if the issue is not closed -# - Only close the issue once, during (or right after) the meeting, regardless of issue contents/comments; use the "indexed" label to trigger a reindexing later on -# -# To run this action, you'll need the following secrets defined in https://github.com/finos//settings/secrets : -# - FINOS_TOKEN -# - GIT_CSV_TOKEN -# -# Email help@finos.org to setup the secrets in your repository. -# -# Note. There's a thread regarding org level secrets in GitHub, which may avoid the secret configuration step - https://github.community/t5/GitHub-Actions/Secrets-on-Team-and-Organization-level/td-p/29745 -on: - issues: - types: [ closed,labeled,unlabeled ] - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FINOS_TOKEN: ${{ secrets.FINOS_TOKEN }} - GIT_CSV_USER_EMAIL: infra@finos.org - GIT_CSV_USER_NAME: "FINOS Admin" - GIT_CSV_USER: "finos-admin" - GIT_CSV_TOKEN: ${{ secrets.GIT_CSV_TOKEN }} - GIT_CSV_HOST: "gitlab.com" - GIT_CSV_ORG: "finos-admin" - GIT_CSV_REPO: sources - GIT_CSV_BRANCH: master - REPO_NAME: ${{ github.event.repository.name }} - ORG_NAME: ${{ github.event.repository.owner.login }} - MEETING_DATE: ${{ github.event.issue.closed_at }} - ASSIGNEES: ${{ join(github.event.issue.assignees.*.login, ', ') }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - ACTION: ${{ github.event.action }} - -jobs: - submit-meeting-attendance: - if: contains(github.event.issue.labels.*.name, 'meeting') - runs-on: ubuntu-20.04 - steps: - - name: Checking out metadata-tool - uses: actions/checkout@v2 - with: - repository: 'finos/metadata-tool' - path: 'metadata-tool' - - name: Checking out FINOS metadata - run: git clone https://finos-admin:$FINOS_TOKEN@github.com/finos-admin/metadata.git >/dev/null - - name: Downloading github-finos-meetings.csv - run: curl -s https://raw.githubusercontent.com/finos/open-developer-platform/master/scripts/checkout-meeting-attendance.sh | bash - - name: Checkout metadata dependencies - run: cd metadata-tool ; lein deps - - name: Generating a new github-finos-meetings.csv - run: curl -s https://raw.githubusercontent.com/finos/open-developer-platform/master/scripts/generate-meeting-attendance.sh | bash - - name: Pushing github-finos-meetings.csv changes to Git - run: curl -s https://raw.githubusercontent.com/finos/open-developer-platform/master/scripts/submit-meeting-attendance.sh | bash - - name: Check unknowns - if: github.event.action == 'closed' || (github.event.action == 'labeled' && github.event.label.name == 'indexed') - run: | - if [ -f "metadata-tool/github-finos-meetings-unknowns.txt" ]; then - UNKNOWNS=`cat metadata-tool/github-finos-meetings-unknowns.txt` - ISSUE_CONTENT="Couldn't find the following GitHub usernames on file: ${UNKNOWNS} . /CC @aitana16 @maoo @mcleo-d" - echo "UNKNOWNS_COMMENT=${ISSUE_CONTENT}" >> $GITHUB_ENV - echo "Posting message as comment: ${UNKNOWNS_COMMENT}" - fi - - name: Report unknowns on issue comment - if: (github.event.action == 'closed' || (github.event.action == 'labeled' && github.event.label.name == 'indexed')) && env.UNKNOWNS_COMMENT != '' - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - body: "${{ env.UNKNOWNS_COMMENT }}" - - name: Add label 'indexed' to issue - if: github.event.action == 'closed' - run: | - curl -v -u admin:${{ secrets.GITHUB_TOKEN }} -H "Accept: application/vnd.github.antiope-preview+json" -d '{"labels": ["indexed"]}' ${{ github.event.issue.url }}/labels diff --git a/waltz-common/src/main/java/org/finos/waltz/common/CollectionUtilities.java b/waltz-common/src/main/java/org/finos/waltz/common/CollectionUtilities.java index 0d3676e0ee..f5dc07a510 100644 --- a/waltz-common/src/main/java/org/finos/waltz/common/CollectionUtilities.java +++ b/waltz-common/src/main/java/org/finos/waltz/common/CollectionUtilities.java @@ -18,14 +18,17 @@ package org.finos.waltz.common; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import static org.finos.waltz.common.Checks.checkNotEmpty; - public class CollectionUtilities { @@ -242,5 +245,4 @@ public static Long sumInts(Collection values) { return acc; } - } diff --git a/waltz-common/src/main/java/org/finos/waltz/common/ListUtilities.java b/waltz-common/src/main/java/org/finos/waltz/common/ListUtilities.java index f60efc1c80..17353ab7da 100644 --- a/waltz-common/src/main/java/org/finos/waltz/common/ListUtilities.java +++ b/waltz-common/src/main/java/org/finos/waltz/common/ListUtilities.java @@ -24,6 +24,7 @@ import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static org.finos.waltz.common.Checks.checkNotNull; @@ -195,6 +196,7 @@ public static Optional maybeGet(List xs, int idx) { } } + /** * Given a list, index and default value returns the element at that index or the default value if the index is out of bounds. */ @@ -202,4 +204,26 @@ public static T getOrDefault(List xs, int idx, T defaultValue) { return maybeGet(xs, idx) .orElse(defaultValue); } + + + public static List take(Collection xs, + int amount) { + return ListUtilities + .ensureNotNull(xs) + .stream() + .limit(amount) + .collect(Collectors.toList()); + } + + + + /** + * Returns a list of distinct values from a given list + * @param ts a list of T's with possible duplicates + * @return distinct values in ts + * @param + */ + public static List distinct(List ts) { + return ts.stream().distinct().collect(toList()); + } } diff --git a/waltz-common/src/test/java/org/finos/waltz/common/ListUtilities_distinct.java b/waltz-common/src/test/java/org/finos/waltz/common/ListUtilities_distinct.java new file mode 100644 index 0000000000..f5108a7637 --- /dev/null +++ b/waltz-common/src/test/java/org/finos/waltz/common/ListUtilities_distinct.java @@ -0,0 +1,20 @@ +package org.finos.waltz.common; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.finos.waltz.common.ListUtilities.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListUtilities_distinct { + + @Test + public void distinctDedupesListsButPreservesInitialOrder() { + List xs = asList("a", "b", "c", "c", "b", "c", "d"); + List uniq = ListUtilities.distinct(xs); + assertEquals(asList("a", "b", "c", "d"), uniq); + } + + +} diff --git a/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java b/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java index 5627319211..8a0152bd60 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/flow_classification_rule/FlowClassificationRuleDao.java @@ -21,6 +21,7 @@ import org.finos.waltz.data.InlineSelectFieldFactory; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.FlowDirection; import org.finos.waltz.model.ImmutableEntityReference; import org.finos.waltz.model.Severity; import org.finos.waltz.model.flow_classification_rule.DiscouragedSource; @@ -372,7 +373,12 @@ public List findExpandedFlowClassificationRu } - public List findFlowClassificationRuleVantagePoints() { + public List findFlowClassificationRuleVantagePoints(FlowDirection direction) { + return findFlowClassificationRuleVantagePoints(FLOW_CLASSIFICATION.DIRECTION.eq(direction.name())); + } + + + private List findFlowClassificationRuleVantagePoints(Condition condition) { SelectSeekStep4, Integer, Integer, Long, Long> select = dsl .select(targetOrgUnitId, declaredOrgUnitLevel, @@ -394,6 +400,7 @@ public List findFlowClassificationRuleVantag .and(ehDataType.KIND.eq(EntityKind.DATA_TYPE.name())) .and(ehDataType.ID.eq(ehDataType.ANCESTOR_ID))) .innerJoin(FLOW_CLASSIFICATION).on(FLOW_CLASSIFICATION_RULE.FLOW_CLASSIFICATION_ID.eq(FLOW_CLASSIFICATION.ID)) + .where(condition) .orderBy( ehOrgUnit.LEVEL.desc(), ehDataType.LEVEL.desc(), @@ -533,7 +540,7 @@ private SelectOnConditionStep baseSelect() { } - public int updatePointToPointFlowClassificationRules() { + public int updatePointToPointFlowClassificationRules(FlowDirection direction) { Condition logicalFlowTargetIsAuthSourceParent = FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_ID.eq(LOGICAL_FLOW.SOURCE_ENTITY_ID) .and(LOGICAL_FLOW.SOURCE_ENTITY_KIND.eq(FLOW_CLASSIFICATION_RULE.SUBJECT_ENTITY_KIND) @@ -558,7 +565,7 @@ public int updatePointToPointFlowClassificationRules() { .and(level.ID.eq(level.ANCESTOR_ID) .and(level.KIND.eq(EntityKind.DATA_TYPE.name())))) .innerJoin(FLOW_CLASSIFICATION).on(FLOW_CLASSIFICATION_RULE.FLOW_CLASSIFICATION_ID.eq(FLOW_CLASSIFICATION.ID)) - .where(FLOW_CLASSIFICATION.CODE.ne(LOGICAL_FLOW_DECORATOR.RATING)) + .where(FLOW_CLASSIFICATION.CODE.ne(LOGICAL_FLOW_DECORATOR.RATING).and(FLOW_CLASSIFICATION.DIRECTION.eq(direction.name()))) .orderBy(level.LEVEL) .fetch() .stream() diff --git a/waltz-data/src/main/java/org/finos/waltz/data/licence/search/LicenceSearchDao.java b/waltz-data/src/main/java/org/finos/waltz/data/licence/search/LicenceSearchDao.java new file mode 100644 index 0000000000..e81fdf1de3 --- /dev/null +++ b/waltz-data/src/main/java/org/finos/waltz/data/licence/search/LicenceSearchDao.java @@ -0,0 +1,66 @@ +package org.finos.waltz.data.licence.search; + +import org.finos.waltz.common.ListUtilities; +import org.finos.waltz.data.SearchDao; +import org.finos.waltz.data.licence.LicenceDao; +import org.finos.waltz.model.entity_search.EntitySearchOptions; +import org.finos.waltz.model.licence.Licence; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.finos.waltz.common.ListUtilities.concat; +import static org.finos.waltz.data.JooqUtilities.mkBasicTermSearch; +import static org.finos.waltz.data.JooqUtilities.mkStartsWithTermSearch; +import static org.finos.waltz.data.SearchUtilities.mkTerms; +import static org.finos.waltz.schema.Tables.LICENCE; + +@Repository +public class LicenceSearchDao implements SearchDao { + + private final DSLContext dsl; + + + @Autowired + public LicenceSearchDao(DSLContext dsl) { + this.dsl = dsl; + } + + + /** + * Searches by name and external_id + * @param options + * @return List of matching legal entities, + * matches on name are given precedence over external_id matches + */ + @Override + public List search(EntitySearchOptions options) { + List terms = mkTerms(options.searchQuery()); + if (terms.isEmpty()) { + return emptyList(); + } + + Condition nameCondition = mkBasicTermSearch(LICENCE.NAME, terms); + Condition externalIdCondition = mkStartsWithTermSearch(LICENCE.EXTERNAL_ID, terms); + + return ListUtilities.distinct(concat( + mkQuery(nameCondition, options), + mkQuery(externalIdCondition, options))); + } + + + private List mkQuery(Condition nameCondition, EntitySearchOptions options) { + return dsl + .select(LICENCE.fields()) + .from(LICENCE) + .where(nameCondition) + .orderBy(LICENCE.NAME) + .limit(options.limit()) + .fetch(LicenceDao.TO_DOMAIN_MAPPER); + } + +} diff --git a/waltz-data/src/main/java/org/finos/waltz/data/report_grid/ReportGridDao.java b/waltz-data/src/main/java/org/finos/waltz/data/report_grid/ReportGridDao.java index d8af5b5605..9ce30b20ee 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/report_grid/ReportGridDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/report_grid/ReportGridDao.java @@ -2284,18 +2284,18 @@ private Set fetchEntityStatisticData(GenericSelector selector, if (isEmpty(cols)) { return emptySet(); } else { - Map statisticDefinitionIdToColIdMap = indexBy( + Map statisticDefinitionIdToColMap = indexBy( cols, - ReportGridFixedColumnDefinition::columnEntityId, - ReportGridFixedColumnDefinition::gridColumnId); + ReportGridFixedColumnDefinition::columnEntityId); return dsl .select(esv.ENTITY_ID, esv.STATISTIC_ID, esv.OUTCOME, + esv.VALUE, esv.REASON) .from(esv) - .where(esv.STATISTIC_ID.in(statisticDefinitionIdToColIdMap.keySet()) + .where(esv.STATISTIC_ID.in(statisticDefinitionIdToColMap.keySet()) .and(esv.CURRENT.eq(true)) .and(esv.ENTITY_KIND.eq(selector.kind().name())) .and(esv.ENTITY_ID.in(selector.selector()))) @@ -2303,31 +2303,43 @@ private Set fetchEntityStatisticData(GenericSelector selector, .fetchGroups( r -> tuple( r.get(esv.ENTITY_ID), - statisticDefinitionIdToColIdMap.get(r.get(esv.STATISTIC_ID))), + statisticDefinitionIdToColMap.get(r.get(esv.STATISTIC_ID))), r -> tuple( r.get(esv.OUTCOME), + r.get(esv.VALUE), r.get(esv.REASON))) .entrySet() .stream() .map(kv -> { + Tuple2 cellKey = kv.getKey(); + List> cellValues = kv.getValue(); - String outcomeString = kv.getValue() + String outcomeString = cellValues .stream() - .map(t -> t.v1) - .sorted(comparing(StringUtilities::lower)) + .sorted(comparing(t -> StringUtilities.lower(t.v1))) + .map(t -> { + AdditionalColumnOptions opts = cellKey.v2.additionalColumnOptions(); + if (opts == AdditionalColumnOptions.VALUES_ONLY) { + return t.v2; + } else if (opts == AdditionalColumnOptions.VALUES_AND_OUTCOMES) { + return String.format("%s = %s", t.v1, t.v2); + } else { + return t.v1; + } + }) .collect(joining("; ")); - List> rowsInComment = CollectionUtilities.sort( - kv.getValue(), + List> rowsInComment = CollectionUtilities.sort( + cellValues, comparing(t -> lower(safeTrim(t.v1)))); return ImmutableReportGridCell .builder() - .subjectId(kv.getKey().v1) - .columnDefinitionId(kv.getKey().v2) + .subjectId(cellKey.v1) + .columnDefinitionId(cellKey.v2.gridColumnId()) .textValue(outcomeString) .comment(toHtmlTable( - asList("Outcome", "Reason"), + asList("Outcome", "Value", "Reason"), rowsInComment)) .build(); }) diff --git a/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java new file mode 100644 index 0000000000..1bfbc4aca7 --- /dev/null +++ b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/FlowClassificationRule2Harness.java @@ -0,0 +1,269 @@ +/* + * Waltz - Enterprise Architecture + * Copyright (C) 2016, 2017, 2018, 2019 Waltz open source project + * See README.md for more information + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific + * + */ + +package org.finos.waltz.jobs.harness; + +import org.finos.waltz.common.FunctionUtilities; +import org.finos.waltz.common.LoggingUtilities; +import org.finos.waltz.common.MapUtilities; +import org.finos.waltz.common.SetUtilities; +import org.finos.waltz.data.flow_classification_rule.FlowClassificationRuleDao; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityLifecycleStatus; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.FlowDirection; +import org.finos.waltz.model.Nullable; +import org.finos.waltz.model.flow_classification_rule.FlowClassificationRuleVantagePoint; +import org.finos.waltz.schema.Tables; +import org.finos.waltz.schema.tables.Application; +import org.finos.waltz.schema.tables.EntityHierarchy; +import org.finos.waltz.schema.tables.LogicalFlow; +import org.finos.waltz.schema.tables.LogicalFlowDecorator; +import org.finos.waltz.service.DIConfiguration; +import org.finos.waltz.service.flow_classification_rule.FlowClassificationRuleService; +import org.immutables.value.Value; +import org.jooq.DSLContext; +import org.jooq.lambda.function.Function4; +import org.jooq.lambda.tuple.Tuple2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.finos.waltz.data.JooqUtilities.readRef; +import static org.jooq.lambda.tuple.Tuple.tuple; + + +public class FlowClassificationRule2Harness { + + public static final Logger LOG = LoggerFactory.getLogger(FlowClassificationRule2Harness.class); + + + @Value.Immutable + interface FlowDataType { + EntityReference source(); + EntityReference target(); + @Nullable Long sourceOuId(); + @Nullable Long targetOuId(); + long lfId(); + long lfdId(); + long dtId(); + @Nullable Long outboundRuleId(); + @Nullable Long inboundRuleId(); + } + + + private static final LogicalFlow lf = Tables.LOGICAL_FLOW; + private static final LogicalFlowDecorator lfd = Tables.LOGICAL_FLOW_DECORATOR; + private static final Application srcApp = Tables.APPLICATION.as("srcApp"); + private static final Application targetApp = Tables.APPLICATION.as("targetApp"); + private static final EntityHierarchy eh = Tables.ENTITY_HIERARCHY; + + + public static void main(String[] args) { + LoggingUtilities.configureLogging(); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DIConfiguration.class); + DSLContext dsl = ctx.getBean(DSLContext.class); + + FlowClassificationRuleService svc = ctx.getBean(FlowClassificationRuleService.class); + FlowClassificationRuleDao dao = ctx.getBean(FlowClassificationRuleDao.class); + + FunctionUtilities.time("doIt", () -> doIt(dsl, dao)); + +// System.exit(-1); + } + + + private static void doIt(DSLContext dsl, + FlowClassificationRuleDao dao) { + LOG.debug("Loading rule vantage points"); + + List inboundRuleVantagePoints = dao.findFlowClassificationRuleVantagePoints(FlowDirection.INBOUND); + List outboundRuleVantagePoints = dao.findFlowClassificationRuleVantagePoints(FlowDirection.OUTBOUND); + + +// take(allRuleVantagePoints, 10).forEach(System.out::println); + + Set population = fetchFlowDataTypePopulation(dsl); + + LOG.debug( + "Loaded: {} inbound and {} outbound vantage point rules, and a population of: {} flows with datatypes", + inboundRuleVantagePoints.size(), + outboundRuleVantagePoints.size(), + population.size()); + + // take(population, 10).forEach(System.out::println); + + LOG.debug("Loading hierarchies"); + List> ouHierarchy = fetchHierarchy(dsl, EntityKind.ORG_UNIT); + List> dtHierarchy = fetchHierarchy(dsl, EntityKind.DATA_TYPE); +// +// System.out.println(findChildren(ouHierarchy, 10186L)); +// System.out.println(findChildren(dtHierarchy, 33100L)); + + LOG.debug("Applying rules to population"); + Map> lfdIdToOutboundRuleIdMap = applyVantagePoints(FlowDirection.OUTBOUND, outboundRuleVantagePoints, population, ouHierarchy, dtHierarchy); + Map> lfdIdToInboundRuleIdMap = applyVantagePoints(FlowDirection.INBOUND, inboundRuleVantagePoints, population, ouHierarchy, dtHierarchy); + + System.out.println("Curr"); + MapUtilities.countBy(FlowDataType::outboundRuleId, SetUtilities.filter(population, p -> p.outboundRuleId() != null)).entrySet().stream().sorted(Map.Entry.comparingByKey()).limit(20).forEach(System.out::println); + System.out.println("Future"); + MapUtilities.countBy(Map.Entry::getValue, lfdIdToOutboundRuleIdMap.entrySet()).entrySet().stream().sorted(Map.Entry.comparingByKey()).limit(20).forEach(System.out::println); + + } + + + private static Map> applyVantagePoints(FlowDirection direction, + List ruleVantagePoints, + Set population, + List> ouHierarchy, + List> dtHierarchy) { + + Function4, Set, FlowDataType, MatchOutcome> matcher = determineMatcherFn(direction); + + Map> lfdIdToRuleAndOutcomeMap = new HashMap<>(); + ruleVantagePoints + .stream() + .filter(rvp -> rvp.vantagePoint().kind() == EntityKind.ORG_UNIT) + .forEach(rvp -> { + Set childOUs = findChildren(ouHierarchy, rvp.vantagePoint().id()); + Set childDTs = findChildren(dtHierarchy, rvp.dataType().id()); + population.forEach(p -> { + Tuple2 currentRuleAndOutcome = lfdIdToRuleAndOutcomeMap.get(p.lfdId()); + if (currentRuleAndOutcome != null && currentRuleAndOutcome.v2 == MatchOutcome.POSITIVE_MATCH) { + return; // skip, already got a good match + } + MatchOutcome outcome = matcher.apply(rvp, childOUs, childDTs, p); + if (outcome == MatchOutcome.NOT_APPLICABLE) { + // skip + } else if (currentRuleAndOutcome == null) { + lfdIdToRuleAndOutcomeMap.put(p.lfdId(), tuple(rvp.ruleId(), outcome)); + } else if (currentRuleAndOutcome.v2 == MatchOutcome.NEGATIVE_MATCH && outcome == MatchOutcome.POSITIVE_MATCH) { + // override result as we have a positive match + lfdIdToRuleAndOutcomeMap.put(p.lfdId(), tuple(rvp.ruleId(), MatchOutcome.POSITIVE_MATCH)); + } else { + // skip, leave the map alone as a more specific negative rule id already exists + } + }); + }); + + LOG.debug( + "finished processing {} {} rules, {} decorators have outcomes", + ruleVantagePoints.size(), + direction, + lfdIdToRuleAndOutcomeMap.size()); + + return lfdIdToRuleAndOutcomeMap; + } + + enum MatchOutcome { + NOT_APPLICABLE, + NEGATIVE_MATCH, + POSITIVE_MATCH + + } + + private static Function4, Set, FlowDataType, MatchOutcome> determineMatcherFn(FlowDirection direction) { + Function4, Set, FlowDataType, MatchOutcome> inboundMatcher = + (rvp, childOUs, childDTs, p) -> { + boolean subjectMatches = p.target().equals(rvp.subjectReference()); + boolean dtAndOuMatches = childDTs.contains(p.dtId()) && rvp.vantagePoint().kind() == EntityKind.ORG_UNIT && p.sourceOuId() != null && childOUs.contains(p.sourceOuId()); + return determineOutcome(subjectMatches, dtAndOuMatches); + }; + + Function4, Set, FlowDataType, MatchOutcome> outboundMatcher = + (rvp, childOUs, childDTs, p) -> { + boolean subjectMatches = p.source().equals(rvp.subjectReference()); + boolean dtAndOuMatches = childDTs.contains(p.dtId()) && rvp.vantagePoint().kind() == EntityKind.ORG_UNIT && p.targetOuId() != null && childOUs.contains(p.targetOuId()); + return determineOutcome(subjectMatches, dtAndOuMatches); + }; + + return direction == FlowDirection.INBOUND + ? inboundMatcher + : outboundMatcher; + } + + + private static MatchOutcome determineOutcome(boolean subjectMatches, + boolean dtAndOuMatches) { + if (subjectMatches && dtAndOuMatches) { + return MatchOutcome.POSITIVE_MATCH; + } else if (dtAndOuMatches) { + return MatchOutcome.NEGATIVE_MATCH; + } else { + return MatchOutcome.NOT_APPLICABLE; + } + } + + + + private static Set fetchFlowDataTypePopulation(DSLContext dsl) { + LOG.debug("Loading population"); + return dsl + .select(lf.ID, + lfd.ID, lfd.DECORATOR_ENTITY_ID, lfd.INBOUND_FLOW_CLASSIFICATION_RULE_ID, lfd.FLOW_CLASSIFICATION_RULE_ID, + lf.SOURCE_ENTITY_ID, lf.SOURCE_ENTITY_KIND, lf.TARGET_ENTITY_ID, lf.TARGET_ENTITY_KIND, + srcApp.ORGANISATIONAL_UNIT_ID, + targetApp.ORGANISATIONAL_UNIT_ID) + .from(lf) + .innerJoin(lfd).on(lfd.LOGICAL_FLOW_ID.eq(lf.ID).and(lfd.DECORATOR_ENTITY_KIND.eq(EntityKind.DATA_TYPE.name()))) + .leftJoin(srcApp).on(srcApp.ID.eq(lf.SOURCE_ENTITY_ID).and(lf.SOURCE_ENTITY_KIND.eq(EntityKind.APPLICATION.name()))) + .leftJoin(targetApp).on(targetApp.ID.eq(lf.TARGET_ENTITY_ID).and(lf.TARGET_ENTITY_KIND.eq(EntityKind.APPLICATION.name()))) + .where(lf.IS_REMOVED.isFalse() + .and(lf.ENTITY_LIFECYCLE_STATUS.eq(EntityLifecycleStatus.ACTIVE.name()))) + .fetchSet(r -> ImmutableFlowDataType + .builder() + .lfdId(r.get(lfd.ID)) + .dtId(r.get(lfd.DECORATOR_ENTITY_ID)) + .lfId(r.get(lf.ID)) + .source(readRef(r, lf.SOURCE_ENTITY_KIND, lf.SOURCE_ENTITY_ID)) + .target(readRef(r, lf.TARGET_ENTITY_KIND, lf.TARGET_ENTITY_ID)) + .inboundRuleId(r.get(lfd.INBOUND_FLOW_CLASSIFICATION_RULE_ID)) + .outboundRuleId(r.get(lfd.FLOW_CLASSIFICATION_RULE_ID)) + .sourceOuId(r.get(srcApp.ORGANISATIONAL_UNIT_ID)) + .targetOuId(r.get(targetApp.ORGANISATIONAL_UNIT_ID)) + .build()); + } + + + private static List> fetchHierarchy(DSLContext dsl, + EntityKind kind) { + return dsl + .select(eh.ID, eh.ANCESTOR_ID) + .from(eh) + .where(eh.KIND.eq(kind.name())) + .fetch(r -> tuple(r.get(eh.ID), r.get(eh.ANCESTOR_ID))); + } + + + private static Set findChildren(List> hierarchy, + long parentId) { + return hierarchy + .stream() + .filter(t -> t.v2 == parentId) + .map(t -> t.v1) + .collect(Collectors.toSet()); + } + + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/licence/Licence.java b/waltz-model/src/main/java/org/finos/waltz/model/licence/Licence.java index b94d4a09f5..20fae24f61 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/licence/Licence.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/licence/Licence.java @@ -34,5 +34,16 @@ public abstract class Licence implements ExternalIdProvider, CreatedUserTimestampProvider, LastUpdatedUserTimestampProvider, - ProvenanceProvider { + ProvenanceProvider, + WaltzEntity { + + @Override + public EntityReference entityReference() { + return EntityReference.mkRef( + EntityKind.LICENCE, + id().get(), + name(), + description(), + externalId().orElse("")); + } } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/report_grid/AdditionalColumnOptions.java b/waltz-model/src/main/java/org/finos/waltz/model/report_grid/AdditionalColumnOptions.java index 355d8958b6..7085884a50 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/report_grid/AdditionalColumnOptions.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/report_grid/AdditionalColumnOptions.java @@ -18,7 +18,10 @@ public enum AdditionalColumnOptions { ROLLUP(asSet(EntityKind.DATA_TYPE)), COMPLETED_AND_APPROVED_ONLY(asSet(EntityKind.SURVEY_INSTANCE, EntityKind.SURVEY_QUESTION)), EXCLUDE_WITHDRAWN(asSet(EntityKind.SURVEY_INSTANCE, EntityKind.SURVEY_QUESTION)), - PRIMARY(asSet(EntityKind.MEASURABLE)); + PRIMARY(asSet(EntityKind.MEASURABLE)), + VALUES_ONLY(asSet(EntityKind.ENTITY_STATISTIC)), + VALUES_AND_OUTCOMES(asSet(EntityKind.ENTITY_STATISTIC)); + private final Set allowedKinds; diff --git a/waltz-ng/client/common/svelte/info-panels/SurveyInstanceInfoPanel.svelte b/waltz-ng/client/common/svelte/info-panels/SurveyInstanceInfoPanel.svelte index 4b184beb92..34baa9fa0f 100644 --- a/waltz-ng/client/common/svelte/info-panels/SurveyInstanceInfoPanel.svelte +++ b/waltz-ng/client/common/svelte/info-panels/SurveyInstanceInfoPanel.svelte @@ -81,6 +81,10 @@ Run Name {survey.surveyRun?.name} + + Survey Contact + {survey.surveyRun?.contactEmail} + Subject diff --git a/waltz-ng/client/navbar/components/nav-search-overlay/nav-search-overlay.js b/waltz-ng/client/navbar/components/nav-search-overlay/nav-search-overlay.js index 0cd8f18560..450b6d662d 100644 --- a/waltz-ng/client/navbar/components/nav-search-overlay/nav-search-overlay.js +++ b/waltz-ng/client/navbar/components/nav-search-overlay/nav-search-overlay.js @@ -52,7 +52,8 @@ const initialState = { entity.DATABASE.key, entity.SOFTWARE.key, entity.ROADMAP.key, - entity.LOGICAL_DATA_ELEMENT.key + entity.LOGICAL_DATA_ELEMENT.key, + entity.LICENCE.key ], selectedCategory: null, showActiveOnly: true, @@ -149,7 +150,7 @@ function controller($element, .then(() => handleSearch(query, [entity.APP_GROUP.key, entity.CHANGE_INITIATIVE.key, entity.ORG_UNIT.key])) .then(() => handleSearch(query, [entity.ACTOR.key, entity.MEASURABLE.key, entity.LEGAL_ENTITY.key])) .then(() => handleSearch(query, [entity.PHYSICAL_SPECIFICATION.key, entity.DATA_TYPE.key, entity.SERVER.key, entity.DATABASE.key])) - .then(() => handleSearch(query, [entity.SOFTWARE.key, entity.ROADMAP.key, entity.LOGICAL_DATA_ELEMENT.key])) + .then(() => handleSearch(query, [entity.SOFTWARE.key, entity.ROADMAP.key, entity.LOGICAL_DATA_ELEMENT.key, entity.LICENCE.key])) .catch(e => displayError("Failed to search")) .finally(() => vm.searching = false); }; diff --git a/waltz-ng/client/report-grid/components/svelte/ReportGridFilters.svelte b/waltz-ng/client/report-grid/components/svelte/ReportGridFilters.svelte index 7b3818e052..3ccf17cff9 100644 --- a/waltz-ng/client/report-grid/components/svelte/ReportGridFilters.svelte +++ b/waltz-ng/client/report-grid/components/svelte/ReportGridFilters.svelte @@ -199,12 +199,12 @@ class="clickable waltz-visibility-parent" class:isActiveFilter={isActive($activeSummaries, summary)}> - - - + + + - {getDisplayNameForColumn(summary.column)} - + {getDisplayNameForColumn(summary.column)} +
    {#each summary.optionSummaries as option} diff --git a/waltz-ng/client/report-grid/components/svelte/report-grid-utils.js b/waltz-ng/client/report-grid/components/svelte/report-grid-utils.js index 65ef49c4a6..37f0a88209 100644 --- a/waltz-ng/client/report-grid/components/svelte/report-grid-utils.js +++ b/waltz-ng/client/report-grid/components/svelte/report-grid-utils.js @@ -63,6 +63,14 @@ export const additionalColumnOptions = { PRIMARY: { key: "PRIMARY", name: "Primary", + }, + VALUES_ONLY: { + key: "VALUES_ONLY", + name: "Values Only", + }, + VALUES_AND_OUTCOMES: { + key: "VALUES_AND_OUTCOMES", + name: "Values and Outcomes", } } diff --git a/waltz-ng/client/survey/components/svelte/inline-panel/SurveyViewer.svelte b/waltz-ng/client/survey/components/svelte/inline-panel/SurveyViewer.svelte index 3cc2705112..bba5588a0d 100644 --- a/waltz-ng/client/survey/components/svelte/inline-panel/SurveyViewer.svelte +++ b/waltz-ng/client/survey/components/svelte/inline-panel/SurveyViewer.svelte @@ -143,6 +143,8 @@ border-bottom: 1px solid #ddd; background-color: #fafafa; background: linear-gradient(90deg, #fafafa 0%, rgba(255,255,255,1) 100%); + padding-top: 0.5em; + padding-bottom: 0.3em; } .sub-question-label { diff --git a/waltz-schema/src/main/resources/liquibase/db.changelog-1.59.xml b/waltz-schema/src/main/resources/liquibase/db.changelog-1.59.xml index dbeef643a1..52111e08ac 100644 --- a/waltz-schema/src/main/resources/liquibase/db.changelog-1.59.xml +++ b/waltz-schema/src/main/resources/liquibase/db.changelog-1.59.xml @@ -100,4 +100,27 @@ remarks="Severity for styling the display of the message shown against flows which have ratings determined by this rule"/> + + 7032: adding inbound_flow_classification_rule_id column to logical flow decorator + + + + + + + + + + + + + + \ No newline at end of file diff --git a/waltz-service/src/main/java/org/finos/waltz/service/entity_search/EntitySearchService.java b/waltz-service/src/main/java/org/finos/waltz/service/entity_search/EntitySearchService.java index 7fc571fd2b..974b0e5ef8 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/entity_search/EntitySearchService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/entity_search/EntitySearchService.java @@ -26,6 +26,7 @@ import org.finos.waltz.service.database_information.DatabaseInformationService; import org.finos.waltz.service.flow_diagram.FlowDiagramService; import org.finos.waltz.service.legal_entity.LegalEntityService; +import org.finos.waltz.service.licence.LicenceService; import org.finos.waltz.service.logical_data_element.LogicalDataElementService; import org.finos.waltz.service.measurable.MeasurableService; import org.finos.waltz.service.orgunit.OrganisationalUnitService; @@ -74,6 +75,7 @@ public class EntitySearchService { private final FlowDiagramService flowDiagramService; private final LegalEntityService legalEntityService; private final DatabaseInformationService databaseInformationService; + private final LicenceService licenceService; @Autowired @@ -93,7 +95,8 @@ public EntitySearchService(DBExecutorPoolInterface dbExecutorPool, SoftwareCatalogService softwareCatalogService, FlowDiagramService flowDiagramService, LegalEntityService legalEntityService, - DatabaseInformationService databaseInformationService) { + DatabaseInformationService databaseInformationService, + LicenceService licenceService) { checkNotNull(dbExecutorPool, "dbExecutorPool cannot be null"); checkNotNull(actorService, "actorService cannot be null"); @@ -112,6 +115,7 @@ public EntitySearchService(DBExecutorPoolInterface dbExecutorPool, checkNotNull(softwareCatalogService, "softwareCatalogService cannot be null"); checkNotNull(legalEntityService, "legalEntityService cannot be null"); checkNotNull(databaseInformationService, "databaseInformationService cannot be null"); + checkNotNull(licenceService, "licenceService cannot be null"); this.actorService = actorService; this.dbExecutorPool = dbExecutorPool; @@ -130,6 +134,7 @@ public EntitySearchService(DBExecutorPoolInterface dbExecutorPool, this.softwareCatalogService = softwareCatalogService; this.legalEntityService = legalEntityService; this.databaseInformationService = databaseInformationService; + this.licenceService = licenceService; } @@ -174,6 +179,8 @@ private Callable> mkCallable(EntityKind entity return () -> flowDiagramService.search(options); case LEGAL_ENTITY: return () -> legalEntityService.search(options); + case LICENCE: + return () -> licenceService.search(options); case LOGICAL_DATA_ELEMENT: return () -> logicalDataElementService.search(options); case MEASURABLE: diff --git a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationCalculator.java b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationCalculator.java index 297cadbcc7..db8e4fc2d4 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationCalculator.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationCalculator.java @@ -95,7 +95,7 @@ private int[] update(DataType dataType, EntityReference vantageRef) { vantageRef); IdSelectionOptions selectorOptions = mkOpts(vantageRef); - Select> selector = appIdSelectorFactory.apply(selectorOptions); + Select> appSelector = appIdSelectorFactory.apply(selectorOptions); Set dataTypeDescendents = entityHierarchyDao .findDesendents(dataType.entityReference()) .stream() @@ -103,7 +103,7 @@ private int[] update(DataType dataType, EntityReference vantageRef) { .collect(Collectors.toSet()); Collection impactedDecorators = logicalFlowDecoratorDao - .findByEntityIdSelector(selector, Optional.of(EntityKind.APPLICATION)) + .findByEntityIdSelector(appSelector, Optional.of(EntityKind.APPLICATION)) .stream() .filter(decorator -> dataTypeDescendents.contains(decorator.decoratorEntity().id())) .collect(toList()); diff --git a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleService.java b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleService.java index bf593cffa1..c8b7f94fca 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/flow_classification_rule/FlowClassificationRuleService.java @@ -155,7 +155,7 @@ public long insert(FlowClassificationRuleCreateCommand command, String username) } logInsert(classificationRuleId, command, username); - flowClassificationRuleDao.updatePointToPointFlowClassificationRules(); + flowClassificationRuleDao.updatePointToPointFlowClassificationRules(FlowDirection.OUTBOUND); return classificationRuleId; } @@ -208,7 +208,7 @@ public int fastRecalculateAllFlowRatings() { //finds all the vantage points to apply using parent as selector List flowClassificationRuleVantagePoints = flowClassificationRuleDao - .findFlowClassificationRuleVantagePoints(); + .findFlowClassificationRuleVantagePoints(FlowDirection.OUTBOUND); int updatedRuleDecorators = flowClassificationRuleVantagePoints .stream() @@ -216,7 +216,7 @@ public int fastRecalculateAllFlowRatings() { .sum(); //overrides rating for point to point flows (must run after the above) - int updatedPointToPointDecorators = flowClassificationRuleDao.updatePointToPointFlowClassificationRules(); + int updatedPointToPointDecorators = flowClassificationRuleDao.updatePointToPointFlowClassificationRules(FlowDirection.OUTBOUND); LOG.info( "Updated decorators for: {} for general rules and {} point-to-point flows", diff --git a/waltz-service/src/main/java/org/finos/waltz/service/licence/LicenceService.java b/waltz-service/src/main/java/org/finos/waltz/service/licence/LicenceService.java index c59e09276d..bb7d0ce2ba 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/licence/LicenceService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/licence/LicenceService.java @@ -21,7 +21,9 @@ import org.finos.waltz.data.licence.LicenceDao; import org.finos.waltz.data.licence.LicenceIdSelectorFactory; +import org.finos.waltz.data.licence.search.LicenceSearchDao; import org.finos.waltz.model.IdSelectionOptions; +import org.finos.waltz.model.entity_search.EntitySearchOptions; import org.finos.waltz.model.licence.Licence; import org.finos.waltz.model.licence.SaveLicenceCommand; import org.finos.waltz.model.tally.Tally; @@ -30,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Collection; import java.util.List; import static org.finos.waltz.common.Checks.checkNotNull; @@ -38,13 +41,17 @@ public class LicenceService { private final LicenceDao licenceDao; + private final LicenceSearchDao licenceSearchDao; private final LicenceIdSelectorFactory licenceIdSelectorFactory = new LicenceIdSelectorFactory(); @Autowired - public LicenceService(LicenceDao licenceDao) { + public LicenceService(LicenceDao licenceDao, + LicenceSearchDao licenceSearchDao) { checkNotNull(licenceDao, "licenceDao cannot be null"); + checkNotNull(licenceSearchDao, "licenceSearchDao cannot be null"); this.licenceDao = licenceDao; + this.licenceSearchDao = licenceSearchDao; } @@ -81,4 +88,8 @@ public boolean save(SaveLicenceCommand cmd, String username) { public boolean remove(long licenceId, String username) { return licenceDao.remove(licenceId); } + + public Collection search(EntitySearchOptions options) { + return licenceSearchDao.search(options); + } }