From 552079543443bff685e0a5b2fbe48dbefaaf149b Mon Sep 17 00:00:00 2001 From: Olmo del Corral Cano Date: Wed, 5 Jan 2022 23:39:40 +0100 Subject: [PATCH] more on Dashboard Pinned Filters --- .../Dashboard/DashboardLogic.cs | 110 +++++++++++++----- .../UserQueries/UserQueryEntity.cs | 11 +- Signum.Entities/DynamicQuery/Filter.cs | 2 +- .../Dashboard/View/CombinedUserChartPart.tsx | 2 +- .../View/DashboardFilterController.tsx | 88 +++++++++++--- .../Dashboard/View/DashboardView.tsx | 19 ++- .../Dashboard/View/UserChartPart.tsx | 21 +++- .../Dashboard/View/UserQueryPart.tsx | 18 ++- .../UserAssets/UserAssetClient.tsx | 9 +- Signum.React/Scripts/FindOptions.ts | 2 + Signum.React/Scripts/Finder.tsx | 7 +- .../Scripts/SearchControl/FilterBuilder.tsx | 4 +- .../Scripts/Signum.Entities.DynamicQuery.ts | 2 +- 13 files changed, 225 insertions(+), 70 deletions(-) diff --git a/Signum.Engine.Extensions/Dashboard/DashboardLogic.cs b/Signum.Engine.Extensions/Dashboard/DashboardLogic.cs index 34a2b95587..979aa4e54a 100644 --- a/Signum.Engine.Extensions/Dashboard/DashboardLogic.cs +++ b/Signum.Engine.Extensions/Dashboard/DashboardLogic.cs @@ -64,10 +64,10 @@ public static void Start(SchemaBuilder sb, IFileTypeAlgorithm cachedQueryAlgorit SchedulerLogic.ExecuteTask.Register((DashboardEntity db, ScheduledTaskContext ctx) => { db.Execute(DashboardOperation.RegenerateCachedQueries); return null; }); - OnGetCachedQueryDefinition.Register((UserChartPartEntity ucp, PanelPartEmbedded pp) => new[] { new CachedQueryDefinition(ucp.UserChart.ToChartRequest().ToQueryRequest(), ucp.UserChart.Filters.GetPinnedFilterTokens(), pp, ucp.UserChart, ucp.IsQueryCached, canWriteFilters: true) }); - OnGetCachedQueryDefinition.Register((CombinedUserChartPartEntity cucp, PanelPartEmbedded pp) => cucp.UserCharts.Select(uc => new CachedQueryDefinition(uc.UserChart.ToChartRequest().ToQueryRequest(), uc.UserChart.Filters.GetPinnedFilterTokens(), pp, uc.UserChart, uc.IsQueryCached, canWriteFilters: false))); - OnGetCachedQueryDefinition.Register((UserQueryPartEntity uqp, PanelPartEmbedded pp) => new[] { new CachedQueryDefinition(uqp.RenderMode == UserQueryPartRenderMode.BigValue ? uqp.UserQuery.ToQueryRequestValue() : uqp.UserQuery.ToQueryRequest(), uqp.UserQuery.Filters.GetPinnedFilterTokens(), pp, uqp.UserQuery, uqp.IsQueryCached, canWriteFilters: false) }); - OnGetCachedQueryDefinition.Register((ValueUserQueryListPartEntity vuql, PanelPartEmbedded pp) => vuql.UserQueries.Select(uqe => new CachedQueryDefinition(uqe.UserQuery.ToQueryRequestValue(), uqe.UserQuery.Filters.GetPinnedFilterTokens(), pp, uqe.UserQuery, uqe.IsQueryCached, canWriteFilters: false))); + OnGetCachedQueryDefinition.Register((UserChartPartEntity ucp, PanelPartEmbedded pp) => new[] { new CachedQueryDefinition(ucp.UserChart.ToChartRequest().ToQueryRequest(), ucp.UserChart.Filters.GetDashboardPinnedFilterTokens(), pp, ucp.UserChart, ucp.IsQueryCached, canWriteFilters: true) }); + OnGetCachedQueryDefinition.Register((CombinedUserChartPartEntity cucp, PanelPartEmbedded pp) => cucp.UserCharts.Select(uc => new CachedQueryDefinition(uc.UserChart.ToChartRequest().ToQueryRequest(), uc.UserChart.Filters.GetDashboardPinnedFilterTokens(), pp, uc.UserChart, uc.IsQueryCached, canWriteFilters: false))); + OnGetCachedQueryDefinition.Register((UserQueryPartEntity uqp, PanelPartEmbedded pp) => new[] { new CachedQueryDefinition(uqp.RenderMode == UserQueryPartRenderMode.BigValue ? uqp.UserQuery.ToQueryRequestValue() : uqp.UserQuery.ToQueryRequest(), uqp.UserQuery.Filters.GetDashboardPinnedFilterTokens(), pp, uqp.UserQuery, uqp.IsQueryCached, canWriteFilters: false) }); + OnGetCachedQueryDefinition.Register((ValueUserQueryListPartEntity vuql, PanelPartEmbedded pp) => vuql.UserQueries.Select(uqe => new CachedQueryDefinition(uqe.UserQuery.ToQueryRequestValue(), uqe.UserQuery.Filters.GetDashboardPinnedFilterTokens(), pp, uqe.UserQuery, uqe.IsQueryCached, canWriteFilters: false))); OnGetCachedQueryDefinition.Register((UserTreePartEntity ute, PanelPartEmbedded pp) => Array.Empty()); OnGetCachedQueryDefinition.Register((LinkListPartEntity uqp, PanelPartEmbedded pp) => Array.Empty()); @@ -447,37 +447,23 @@ public static List GetCachedQueryDefinitions(DashboardEnt if (!writers.Any()) continue; - var equivalences = db.TokenEquivalencesGroups.Where(a => a.InteractionGroup == key || a.InteractionGroup == null); + var equivalenceGroups = db.TokenEquivalencesGroups.Where(a => a.InteractionGroup == key || a.InteractionGroup == null); foreach (var wr in writers) { - var keyColumns = wr.QueryRequest.GroupResults ? - wr.QueryRequest.Columns.Where(c => c.Token is not AggregateToken) : - wr.QueryRequest.Columns; - - var equivalencesDictionary = (from gr in equivalences - from t in gr.TokenEquivalences.Where(a => a.Query.ToQueryName() == wr.QueryRequest.QueryName) - select KeyValuePair.Create(t.Token.Token, gr.TokenEquivalences.GroupToDictionary(a => a.Query.ToQueryName(), a => a.Token.Token))) - .ToDictionaryEx(); + var keyColumns = wr.QueryRequest.GroupResults ? + wr.QueryRequest.Columns.Where(c => c.Token is not AggregateToken).Select(c => c.Token).Distinct().ToList() : + wr.QueryRequest.Columns.Select(c => c.Token).Distinct().ToList(); + + Dictionary>> equivalencesDictionary = GetEquivalenceDictionary(equivalenceGroups, fromQuery: wr.QueryRequest.QueryName); foreach (var cqd in cqdefs.Where(e => e != wr)) { - var extraColumns = keyColumns.Select(k => - { - var translatedToken = TranslatedToken(k.Token, cqd.QueryRequest.QueryName, equivalencesDictionary); - - if (translatedToken == null) - return null; - - if (!cqd.QueryRequest.Columns.Any(c => translatedToken.Contains(c.Token))) - return translatedToken.FirstEx(); //Doesn't really matter if we add "Product" or "Entity.Product"; - - return null; - }).NotNull().ToList(); + List extraColumns = ExtraColumns(keyColumns, cqd, equivalencesDictionary); if (extraColumns.Any()) { - ExpandColumns(cqd, extraColumns); + ExpandColumns(cqd, extraColumns, "Dashboard Filters from " + key); } cqd.QueryRequest.Pagination = new Pagination.All(); @@ -485,10 +471,32 @@ select KeyValuePair.Create(t.Token.Token, gr.TokenEquivalences.GroupToDictionary } } - foreach (var cqd in definitions) + foreach (var writer in definitions) { - if (cqd.PinnedFiltersTokens.Any()) - ExpandColumns(cqd, cqd.PinnedFiltersTokens); + if (writer.PinnedFiltersTokens.Any()) + { + var pft = writer.PinnedFiltersTokens.Where(a => a.prototedToDashboard == false).Select(a=>a.token).ToList(); + if (pft.Any()) + ExpandColumns(writer, pft, "Pinned Filters"); + + var dpft = writer.PinnedFiltersTokens.Where(a => a.prototedToDashboard == true).Select(a => a.token).ToList(); + if (dpft.Any()) + { + var equivalenceGroups = db.TokenEquivalencesGroups.Where(a => /*a.InteractionGroup == writer.PanelPart.InteractionGroup Needed? ||*/ a.InteractionGroup == null); + + Dictionary>> equivalencesDictionary = GetEquivalenceDictionary(equivalenceGroups, fromQuery: writer.QueryRequest.QueryName); + + foreach (var cqd in definitions.Where(e => e != writer)) + { + List extraColumns = ExtraColumns(dpft, cqd, equivalencesDictionary); + if (extraColumns.Any()) + { + ExpandColumns(cqd, extraColumns, "Dashboard Pinned Filters"); + } + } + } + + } } var cached = definitions.Where(a => a.IsQueryCached).ToList(); @@ -496,8 +504,46 @@ select KeyValuePair.Create(t.Token.Token, gr.TokenEquivalences.GroupToDictionary return cached; } - private static void ExpandColumns(CachedQueryDefinition cqd, List extraColumns) + private static List ExtraColumns(List requiredTokens, CachedQueryDefinition cqd, Dictionary>> equivalencesDictionary) + { + var extraColumns = requiredTokens.Select(t => + { + var translatedToken = TranslatedToken(t, cqd.QueryRequest.QueryName, equivalencesDictionary); + + if (translatedToken == null) + return null; + + if (!cqd.QueryRequest.Columns.Any(c => translatedToken.Contains(c.Token))) + return translatedToken.FirstEx(); //Doesn't really matter if we add "Product" or "Entity.Product"; + + return null; + }).NotNull().ToList(); + return extraColumns; + } + + private static Dictionary>> GetEquivalenceDictionary(IEnumerable equivalences, object fromQuery) + { + return (from gr in equivalences + from t in gr.TokenEquivalences.Where(a => a.Query.ToQueryName() == fromQuery) + select KeyValuePair.Create(t.Token.Token, gr.TokenEquivalences.GroupToDictionary(a => a.Query.ToQueryName(), a => a.Token.Token))) + .ToDictionaryEx(); + } + + private static void ExpandColumns(CachedQueryDefinition cqd, List extraColumns, string errorContext) { + if (cqd.QueryRequest.GroupResults) + { + var errors = extraColumns + .Select(a => new { + token = a, + error = QueryUtils.CanColumn(a) ?? (cqd.QueryRequest.GroupResults && !a.IsGroupable ? "Is not groupable" : null) + }) + .Where(a => a.error != null); + + if (errors.Any()) + throw new InvalidOperationException($"Unable to expand columns in '{cqd.UserAsset.KeyLong()}' (query {QueryUtils.GetKey(cqd.QueryRequest.QueryName)}) requested by {errorContext} because: \r\n{errors.ToString(a => a.token.FullKey() + ": " + a.error, "\r\n")}"); + } + cqd.QueryRequest.Columns.AddRange(extraColumns.Select(c => new Column(c, null))); var avgs = cqd.QueryRequest.Columns.Extract(a => a.Token is AggregateToken at && at.AggregateFunction == AggregateFunction.Average); foreach (var av in avgs) @@ -578,7 +624,7 @@ public static List CombineCachedQueryDefinitions( public class CachedQueryDefinition { - public CachedQueryDefinition(QueryRequest queryRequest, List pinnedFiltersTokens, PanelPartEmbedded panelPart, IUserAssetEntity userAsset, bool isQueryCached, bool canWriteFilters) + public CachedQueryDefinition(QueryRequest queryRequest, List<(QueryToken token, bool prototedToDashboard)> pinnedFiltersTokens, PanelPartEmbedded panelPart, IUserAssetEntity userAsset, bool isQueryCached, bool canWriteFilters) { QueryRequest = queryRequest; PinnedFiltersTokens = pinnedFiltersTokens; @@ -590,7 +636,7 @@ public CachedQueryDefinition(QueryRequest queryRequest, List pinnedF } public QueryRequest QueryRequest { get; set; } - public List PinnedFiltersTokens { get; set; } + public List<(QueryToken token, bool prototedToDashboard)> PinnedFiltersTokens { get; set; } public PanelPartEmbedded PanelPart { get; set; } public Guid Guid { get; set; } public Lite UserAsset { get; set; } diff --git a/Signum.Entities.Extensions/UserQueries/UserQueryEntity.cs b/Signum.Entities.Extensions/UserQueries/UserQueryEntity.cs index e781e02ecd..bbc4dd23b3 100644 --- a/Signum.Entities.Extensions/UserQueries/UserQueryEntity.cs +++ b/Signum.Entities.Extensions/UserQueries/UserQueryEntity.cs @@ -499,19 +499,22 @@ public static List ToFilterList(this IEnumerable fi }).NotNull().ToList(); } - public static List GetPinnedFilterTokens(this IEnumerable filters, int indent = 0) + public static List<(QueryToken, bool prototedToDashboard)> GetDashboardPinnedFilterTokens(this IEnumerable filters, int indent = 0) { return filters.GroupWhen(filter => filter.Indentation == indent).SelectMany(gr => { var filter = gr.Key; if (filter.Pinned != null) - return gr.Select(a => a.Token?.Token).NotNull().Distinct(); + { + var promotedToDashboard = filter.DashboardBehaviour == DashboardBehaviour.PromoteToDasboardPinnedFilter; + return gr.PreAnd(filter).Select(a => a.Token?.Token).NotNull().Distinct().Select(t => (t, promotedToDashboard)); + } if (filter.IsGroup) - return gr.GetPinnedFilterTokens(indent + 1); + return gr.GetDashboardPinnedFilterTokens(indent + 1); else - return Enumerable.Empty(); + return Enumerable.Empty<(QueryToken, bool prototedToDashboard)>(); }).ToList(); } } diff --git a/Signum.Entities/DynamicQuery/Filter.cs b/Signum.Entities/DynamicQuery/Filter.cs index b9874086ad..05eb00bc33 100644 --- a/Signum.Entities/DynamicQuery/Filter.cs +++ b/Signum.Entities/DynamicQuery/Filter.cs @@ -274,7 +274,7 @@ public enum PinnedFilterActive public enum DashboardBehaviour { //ShowAsPartFilter = 0, //Pinned Filter shown in the Part Widget - PromoteToDasboardFilter = 1, //Pinned Filter promoted to dashboard + PromoteToDasboardPinnedFilter = 1, //Pinned Filter promoted to dashboard UseAsInitialSelection, //Filters other parts in the same interaction group as if the user initially selected UseWhenNoFilters } diff --git a/Signum.React.Extensions/Dashboard/View/CombinedUserChartPart.tsx b/Signum.React.Extensions/Dashboard/View/CombinedUserChartPart.tsx index bbf870e92e..382838a60f 100644 --- a/Signum.React.Extensions/Dashboard/View/CombinedUserChartPart.tsx +++ b/Signum.React.Extensions/Dashboard/View/CombinedUserChartPart.tsx @@ -121,7 +121,7 @@ export default function CombinedUserChartPart(p: PanelPartContentProps { inf.makeQuery?.().done(); }); - }, [p.part, ...p.deps ?? [], infos.max(e => p.filterController.lastChange.get(e.userChart.query.key))]); + }, [p.part, ...p.deps ?? [], infos.max(e => p.filterController.getLastChange(e.userChart.query.key))]); function renderError(e: any, key: number) { const se = e instanceof ServiceError ? (e as ServiceError) : undefined; diff --git a/Signum.React.Extensions/Dashboard/View/DashboardFilterController.tsx b/Signum.React.Extensions/Dashboard/View/DashboardFilterController.tsx index 4548f9c73c..2f68d91af2 100644 --- a/Signum.React.Extensions/Dashboard/View/DashboardFilterController.tsx +++ b/Signum.React.Extensions/Dashboard/View/DashboardFilterController.tsx @@ -1,5 +1,5 @@ import { DashboardEntity, PanelPartEmbedded } from '../Signum.Entities.Dashboard'; -import { FilterConditionOptionParsed, FilterGroupOptionParsed, FilterOptionParsed, FindOptions, QueryToken } from '@framework/FindOptions'; +import { FilterConditionOptionParsed, FilterGroupOptionParsed, FilterOptionParsed, FindOptions, isFilterGroupOptionParsed, QueryToken } from '@framework/FindOptions'; import { FilterGroupOperation } from '@framework/Signum.Entities.DynamicQuery'; import { ChartRequestModel } from '../../Chart/Signum.Entities.Chart'; import { ChartRow } from '../../Chart/ChartClient'; @@ -14,12 +14,17 @@ export class DashboardFilterController { forceUpdate: () => void; filters: Map = new Map(); + pinnedFilters: Map = new Map(); lastChange: Map = new Map(); dashboard: DashboardEntity; + queriesWithEquivalences: string/*queryKey*/[]; constructor(forceUpdate: () => void, dashboard: DashboardEntity) { this.forceUpdate = forceUpdate; this.dashboard = dashboard; + + this.queriesWithEquivalences = dashboard.tokenEquivalencesGroups.flatMap(a => a.element.tokenEquivalences.map(a => a.element.query.key)).distinctBy(a => a); + } setFilter(filter: DashboardFilter) { @@ -28,23 +33,42 @@ export class DashboardFilterController { this.forceUpdate(); } - clear(partEmbedded: PanelPartEmbedded) { + setPinnedFilter(filter: DashboardPinnedFilters) { + this.lastChange.set(filter.queryKey, new Date().getTime()); + this.pinnedFilters.set(filter.partEmbedded, filter); + this.forceUpdate(); + } + + clearPinnesFilter(partEmbedded: PanelPartEmbedded) { + var current = this.pinnedFilters.get(partEmbedded); + if (current) + this.lastChange.set(current.queryKey, new Date().getTime()); + + this.pinnedFilters.delete(partEmbedded); + this.forceUpdate(); + } + + clearFilters(partEmbedded: PanelPartEmbedded) { var current = this.filters.get(partEmbedded); - if (current) { + if (current) this.lastChange.set(current.queryKey, new Date().getTime()); - } this.filters.delete(partEmbedded); this.forceUpdate(); } - getFilterOptions(partEmbedded: PanelPartEmbedded, queryKey: string): FilterOptionParsed[] { + getLastChange(queryKey: string) { + if (this.queriesWithEquivalences.contains(queryKey)) + return this.queriesWithEquivalences.max(qk => this.lastChange.get(qk)); - if (partEmbedded.interactionGroup == null) - return []; + return this.lastChange.get(queryKey); + } - var otherFilters = Array.from(this.filters.values()).filter(f => f.partEmbedded != partEmbedded && f.partEmbedded.interactionGroup == partEmbedded.interactionGroup && f.rows?.length); + getFilterOptions(partEmbedded: PanelPartEmbedded, queryKey: string): FilterOptionParsed[] { - debugger; + var otherFilters = partEmbedded.interactionGroup == null ? [] : Array.from(this.filters.values()).filter(f => f.partEmbedded != partEmbedded && f.partEmbedded.interactionGroup == partEmbedded.interactionGroup && f.rows?.length); + var pinnedFilters = Array.from(this.pinnedFilters.values()).filter(a => a.pinnedFilters.length > 0); + if (otherFilters.length == 0 && pinnedFilters.length == 0) + return []; var equivalences = this.dashboard.tokenEquivalencesGroups .filter(a => a.element.interactionGroup == partEmbedded.interactionGroup || a.element.interactionGroup == null) @@ -59,9 +83,8 @@ export class DashboardFilterController { }))); }).groupToObject(a => a.fromQueryKey) - var result = otherFilters.map( + var resultFilters = otherFilters.map( df => { - var tokenEquivalences = equivalences[df.queryKey]?.groupToObject(a => a.fromToken!.fullKey); @@ -81,13 +104,21 @@ export class DashboardFilterController { ).notNull()) }).notNull(); - return result; + var resultPinnedFilters = pinnedFilters.flatMap(a => { + if (a.queryKey == queryKey) + return a.pinnedFilters; + + var tokenEquivalences = equivalences[a.queryKey]?.groupToObject(a => a.fromToken!.fullKey); + + return a.pinnedFilters.map(fop => tokenEquivalences && translateFilterToken(fop, tokenEquivalences)).notNull(); + }) + + return [...resultPinnedFilters, ...resultFilters]; } applyToFindOptions(partEmbedded: PanelPartEmbedded, fo: FindOptions): FindOptions { - var fops = this.getFilterOptions(partEmbedded, getQueryKey(fo.queryName)); if (fops.length == 0) return fo; @@ -103,10 +134,28 @@ export class DashboardFilterController { } } +function translateFilterToken(fop: FilterOptionParsed, tokenEquivalences: { [token: string]: TokenEquivalenceTuple[] }): FilterOptionParsed | null { + var newToken: QueryToken | null | undefined = fop.token; + if (newToken != null) { + newToken = translateToken(newToken, tokenEquivalences); + if (newToken == null) + return null; + } + + if (isFilterGroupOptionParsed(fop)) { + return ({ ...fop, token: newToken, filters: fop.filters.map(f => translateFilterToken(f, tokenEquivalences)).notNull() }); + } + else + return ({ ...fop, token: newToken }); +} + function translateToken(token: QueryToken, tokenEquivalences: { [token: string]: TokenEquivalenceTuple[] }) { var toAdd: QueryToken[] = []; + if (tokenEquivalences == null) + return null; + for (var t = token; t != null; t = t.parent!) { var equivalence = tokenEquivalences[t.fullKey]; @@ -118,7 +167,6 @@ function translateToken(token: QueryToken, tokenEquivalences: { [token: string]: toAdd.insertAt(0, t); if (t.parent == null) {//Is a Column, like 'Supplier', but maybe 'Entity' is mapped to we can interpret it as 'Entity.Supplier' - debugger; equivalence = tokenEquivalences["Entity"]; if (equivalence != null) { @@ -152,6 +200,18 @@ interface TokenEquivalenceTuple { toToken: QueryToken; } +export class DashboardPinnedFilters { + partEmbedded: PanelPartEmbedded; + queryKey: string; + pinnedFilters: FilterOptionParsed[]; + + constructor(partEmbedded: PanelPartEmbedded, queryKey: string, pinnedFilters: FilterOptionParsed[]) { + this.partEmbedded = partEmbedded; + this.queryKey = queryKey; + this.pinnedFilters = pinnedFilters; + } +} + export class DashboardFilter { partEmbedded: PanelPartEmbedded; queryKey: string; diff --git a/Signum.React.Extensions/Dashboard/View/DashboardView.tsx b/Signum.React.Extensions/Dashboard/View/DashboardView.tsx index 28f25751fc..17e9775eea 100644 --- a/Signum.React.Extensions/Dashboard/View/DashboardView.tsx +++ b/Signum.React.Extensions/Dashboard/View/DashboardView.tsx @@ -15,6 +15,7 @@ import { translated } from '../../Translation/TranslatedInstanceTools' import { DashboardFilterController } from './DashboardFilterController' import { FilePathEmbedded } from '../../Files/Signum.Entities.Files' import { CachedQueryJS } from '../CachedQueryExecutor' +import PinnedFilterBuilder from '../../../Signum.React/Scripts/SearchControl/PinnedFilterBuilder' export default function DashboardView(p: { dashboard: DashboardEntity, cachedQueries: { [userAssetKey: string]: Promise }, entity?: Entity, deps?: React.DependencyList; reload: () => void; }) { @@ -91,10 +92,18 @@ export default function DashboardView(p: { dashboard: DashboardEntity, cachedQue } - if (p.dashboard.combineSimilarRows) - return renderCombinedRows(); - else - return renderBasic(); + return ( +
+ {filterController.pinnedFilters.size > 0 && a.pinnedFilters)} + onFiltersChanged={forceUpdate} />} + { + p.dashboard.combineSimilarRows ? + renderCombinedRows() : + renderBasic() + } +
+ ); } function combineRows(rows: CombinedRow[]): CombinedRow[] { @@ -220,7 +229,7 @@ export function PanelPart(p: PanelPartProps) { var dashboardFilter = p.filterController?.filters.get(p.ctx.value); function handleClearFilter(e: React.MouseEvent) { - p.filterController.clear(p.ctx.value); + p.filterController.clearFilters(p.ctx.value); } return ( diff --git a/Signum.React.Extensions/Dashboard/View/UserChartPart.tsx b/Signum.React.Extensions/Dashboard/View/UserChartPart.tsx index 668f1f518e..21f2d5aa6f 100644 --- a/Signum.React.Extensions/Dashboard/View/UserChartPart.tsx +++ b/Signum.React.Extensions/Dashboard/View/UserChartPart.tsx @@ -15,7 +15,7 @@ import { useAPI, useAPIWithReload } from '@framework/Hooks' import { PanelPartContentProps } from '../DashboardClient' import { getTypeInfos } from '@framework/Reflection' import SelectorModal from '@framework/SelectorModal' -import { DashboardFilter, DashboardFilterController, DashboardFilterRow, equalsDFR } from "./DashboardFilterController" +import { DashboardFilter, DashboardFilterController, DashboardFilterRow, DashboardPinnedFilters, equalsDFR } from "./DashboardFilterController" import { filterOperations, FilterOptionParsed, isFilterGroupOption, isFilterGroupOptionParsed, QueryToken } from '@framework/FindOptions' import { CachedQueryJS, executeChartCached } from '../CachedQueryExecutor' import { DashboardBehaviour } from '../../../Signum.React/Scripts/Signum.Entities.DynamicQuery' @@ -25,7 +25,9 @@ export default function UserChartPart(p: PanelPartContentProps Finder.getQueryDescription(p.part.userChart.query.key), [p.part.userChart.query.key]); const chartRequest = useAPI(() => UserChartClient.Converter.toChartRequest(p.part.userChart, p.entity), [p.part.userChart, p.entity, ...p.deps ?? []]); const initialSelection = React.useMemo(() => chartRequest?.filterOptions.singleOrNull(a => a.dashboardBehaviour == "UseAsInitialSelection"), [chartRequest]); - const originalFilters = React.useMemo(() => chartRequest?.filterOptions.filter(a => a.dashboardBehaviour != "UseAsInitialSelection"), [chartRequest]); + const dashboardPinnedFilters = React.useMemo(() => chartRequest?.filterOptions.filter(a => a.dashboardBehaviour == "PromoteToDasboardPinnedFilter"), [chartRequest]); + const useWhenNoFilters = React.useMemo(() => chartRequest?.filterOptions.filter(a => a.dashboardBehaviour == "UseWhenNoFilters"), [chartRequest]); + const simpleFilters = React.useMemo(() => chartRequest?.filterOptions.filter(a => a.dashboardBehaviour == null), [chartRequest]); if (chartRequest != null) { chartRequest.filterOptions.clear(); @@ -39,7 +41,8 @@ export default function UserChartPart(p: PanelPartContentProps a.dashboardBehaviour !== "UseWhenNoFilters" || !tokens.some(t => t.fullKey == a.token?.fullKey)), + ...simpleFilters!, + ...useWhenNoFilters!.filter(a => !tokens.some(t => t.fullKey == a.token?.fullKey)), ...dashboardFilters, ]; } @@ -58,8 +61,16 @@ export default function UserChartPart(p: PanelPartContentProps { if (!e.ctrlKey) { - p.filterController.clear(p.partEmbedded); + p.filterController.clearFilters(p.partEmbedded); } }} dashboardFilter={p.filterController.filters.get(p.partEmbedded)} diff --git a/Signum.React.Extensions/Dashboard/View/UserQueryPart.tsx b/Signum.React.Extensions/Dashboard/View/UserQueryPart.tsx index 5a12093f9c..e362bbd5f6 100644 --- a/Signum.React.Extensions/Dashboard/View/UserQueryPart.tsx +++ b/Signum.React.Extensions/Dashboard/View/UserQueryPart.tsx @@ -19,11 +19,26 @@ import { BootstrapStyle } from '../../Basics/Signum.Entities.Basics' import { parseIcon } from '../../Basics/Templates/IconTypeahead' import { translated } from '../../Translation/TranslatedInstanceTools' import { CachedQueryJS, executeQueryCached, executeQueryValueCached } from '../CachedQueryExecutor' +import { DashboardPinnedFilters } from './DashboardFilterController' export default function UserQueryPart(p: PanelPartContentProps) { let fo = useAPI(signal => UserQueryClient.Converter.toFindOptions(p.part.userQuery, p.entity), [p.part.userQuery, p.entity]); + React.useEffect(() => { + + if (fo) { + var dashboardPinnedFilters = fo.filterOptions?.filter(a => a?.pinned == "PromoteToDasboardFilter") ?? []; + + if (dashboardPinnedFilters.length) { + Finder.getQueryDescription(fo.queryName) + .then(qd => Finder.parseFilterOptions(dashboardPinnedFilters, fo!.groupResults ?? false, qd)) + .then(fops => p.filterController.setPinnedFilter(new DashboardPinnedFilters(p.partEmbedded, getQueryKey(fo!.queryName), fops))) + .done(); + } + } + }, [fo]); + const cachedQuery = p.cachedQueries[liteKey(toLite(p.part.userQuery))]; if (!fo) @@ -45,7 +60,8 @@ export default function UserQueryPart(p: PanelPartContentProps; } diff --git a/Signum.React.Extensions/UserAssets/UserAssetClient.tsx b/Signum.React.Extensions/UserAssets/UserAssetClient.tsx index a6fe854f14..3ea890a6ea 100644 --- a/Signum.React.Extensions/UserAssets/UserAssetClient.tsx +++ b/Signum.React.Extensions/UserAssets/UserAssetClient.tsx @@ -89,14 +89,16 @@ export module Converter { token: fr.token && fr.token.fullKey, groupOperation: fr.groupOperation, filters: fr.filters!.map(f => toFilterOption(f)), - pinned: fr.pinned + pinned: fr.pinned, + dashboardBehaviour: fr.dashboardBehaviour, } as FilterGroupOption); else return ({ token: fr.token!.fullKey, operation: fr.operation ?? "EqualTo", value: fr.value, - pinned: fr.pinned + pinned: fr.pinned, + dashboardBehaviour: fr.dashboardBehaviour, } as FilterConditionOption); } @@ -109,6 +111,7 @@ export module Converter { value: fr.value, pinned: fr.pinned, filters: fr.filters.map(f => toFilterNode(f)), + dashboardBehaviour: fr.dashboardBehaviour, }); else @@ -117,6 +120,7 @@ export module Converter { operation: fr.operation ?? "EqualTo", value: fr.value, pinned: fr.pinned, + dashboardBehaviour: fr.dashboardBehaviour, }); } @@ -142,6 +146,7 @@ export module Converter { operation: e.operation, valueString: e.valueString, pinned: e.pinned && toPinnedFilterEmbedded(e.pinned), + dashboardBehaviour: e.dashboardBehaviour, indentation: e.indentation, }); } diff --git a/Signum.React/Scripts/FindOptions.ts b/Signum.React/Scripts/FindOptions.ts index 0e2766505a..2c353a6e4a 100644 --- a/Signum.React/Scripts/FindOptions.ts +++ b/Signum.React/Scripts/FindOptions.ts @@ -63,6 +63,7 @@ export interface FilterConditionOption { operation?: FilterOperation; value?: any; pinned?: PinnedFilter; + dashboardBehaviour?: DashboardBehaviour; } export interface FilterGroupOption { @@ -70,6 +71,7 @@ export interface FilterGroupOption { groupOperation: FilterGroupOperation; filters: FilterOption[]; pinned?: PinnedFilter; + dashboardBehaviour?: DashboardBehaviour; value?: string; /*For search in multiple columns*/ } diff --git a/Signum.React/Scripts/Finder.tsx b/Signum.React/Scripts/Finder.tsx index a207d4274b..569365f42c 100644 --- a/Signum.React/Scripts/Finder.tsx +++ b/Signum.React/Scripts/Finder.tsx @@ -680,7 +680,6 @@ export function toFilterOptions(filterOptionsParsed: FilterOptionParsed[]): Filt function toFilterOption(fop: FilterOptionParsed): FilterOption | null { - var pinned = fop.pinned && Dic.simplify({ ...fop.pinned }) as PinnedFilter; if (isFilterGroupOptionParsed(fop)) return ({ @@ -688,6 +687,7 @@ export function toFilterOptions(filterOptionsParsed: FilterOptionParsed[]): Filt groupOperation: fop.groupOperation, value: fop.value === "" ? undefined : fop.value, pinned: pinned, + dashboardBehaviour: fop.dashboardBehaviour, filters: fop.filters.map(fp => toFilterOption(fp)).filter(fo => !!fo), }) as FilterGroupOption; else { @@ -699,7 +699,8 @@ export function toFilterOptions(filterOptionsParsed: FilterOptionParsed[]): Filt operation: fop.operation, value: fop.value === "" ? undefined : fop.value, frozen: fop.frozen ? true : undefined, - pinned: pinned + pinned: pinned, + dashboardBehaviour: fop.dashboardBehaviour, }) as FilterConditionOption; } } @@ -1190,6 +1191,7 @@ export class TokenCompleter { groupOperation: fo.groupOperation, value: fo.value, pinned: fo.pinned && toPinnedFilterParsed(fo.pinned), + dashboardBehaviour: fo.dashboardBehaviour, filters: fo.filters.map(f => this.toFilterOptionParsed(f)), frozen: false, expanded: false, @@ -1206,6 +1208,7 @@ export class TokenCompleter { value: fo.value, frozen: fo.frozen || false, pinned: fo.pinned && toPinnedFilterParsed(fo.pinned), + dashboardBehaviour: fo.dashboardBehaviour, } as FilterConditionOptionParsed); } } diff --git a/Signum.React/Scripts/SearchControl/FilterBuilder.tsx b/Signum.React/Scripts/SearchControl/FilterBuilder.tsx index 1322fe413b..6861fb9a0c 100644 --- a/Signum.React/Scripts/SearchControl/FilterBuilder.tsx +++ b/Signum.React/Scripts/SearchControl/FilterBuilder.tsx @@ -686,7 +686,7 @@ function DashboardBehaviourComponent(p: { filter: FilterOptionParsed, readonly: { p.filter.dashboardBehaviour = v; - if (v == "PromoteToDasboardFilter" && p.filter.pinned == null) + if (v == "PromoteToDasboardPinnedFilter" && p.filter.pinned == null) p.filter.pinned = {}; else if ((v == "UseAsInitialSelection" || v == "UseWhenNoFilters") && p.filter.pinned != null) p.filter.pinned = undefined; @@ -794,7 +794,7 @@ export function MultiValue(p: MultiValueProps) { function fixDashboardBehaviour(fop: FilterOptionParsed) { - if (fop.dashboardBehaviour == "PromoteToDasboardFilter" && fop.pinned == null) + if (fop.dashboardBehaviour == "PromoteToDasboardPinnedFilter" && fop.pinned == null) fop.dashboardBehaviour = undefined; if ((fop.dashboardBehaviour == "UseWhenNoFilters" || fop.dashboardBehaviour == "UseAsInitialSelection") && fop.pinned != null) diff --git a/Signum.React/Scripts/Signum.Entities.DynamicQuery.ts b/Signum.React/Scripts/Signum.Entities.DynamicQuery.ts index 4baaaf86c3..a76199ca5d 100644 --- a/Signum.React/Scripts/Signum.Entities.DynamicQuery.ts +++ b/Signum.React/Scripts/Signum.Entities.DynamicQuery.ts @@ -15,7 +15,7 @@ export type ColumnOptionsMode = export const DashboardBehaviour = new EnumType("DashboardBehaviour"); export type DashboardBehaviour = - "PromoteToDasboardFilter" | + "PromoteToDasboardPinnedFilter" | "UseAsInitialSelection" | "UseWhenNoFilters";