diff --git a/Extensions/Signum.Chart/ChartButton.tsx b/Extensions/Signum.Chart/ChartButton.tsx index 21a79376f0..116a56fe36 100644 --- a/Extensions/Signum.Chart/ChartButton.tsx +++ b/Extensions/Signum.Chart/ChartButton.tsx @@ -4,7 +4,7 @@ import { FilterOptionParsed, FilterGroupOptionParsed, isFilterGroup } from '@fra import * as AppContext from '@framework/AppContext' import { Navigator } from '@framework/Navigator' import { default as SearchControlLoaded } from '@framework/SearchControl/SearchControlLoaded' -import { ChartMessage, ChartRequestModel } from './Signum.Chart' +import { ChartMessage, ChartRequestModel, ChartTimeSeriesEmbedded } from './Signum.Chart' import { ChartClient } from './ChartClient' import { Button } from 'react-bootstrap' import { Finder } from '@framework/Finder'; @@ -31,7 +31,8 @@ export default class ChartButton extends React.Component { const path = ChartClient.Encoder.chartPath({ queryName: fo.queryName, orderOptions: [], - filterOptions: fo.filterOptions + filterOptions: fo.filterOptions, + timeSeries: ChartClient.cloneChartTimeSeries(fo.systemTime as any), }) if (sc.props.avoidChangeUrl) diff --git a/Extensions/Signum.Chart/ChartClient.tsx b/Extensions/Signum.Chart/ChartClient.tsx index 24ea7a9af0..5e1c628b1e 100644 --- a/Extensions/Signum.Chart/ChartClient.tsx +++ b/Extensions/Signum.Chart/ChartClient.tsx @@ -9,7 +9,7 @@ import { Constructor } from '@framework/Constructor' import { Entity, getToString, is, Lite, liteKey, MList, SelectorMessage, toLite, translated } from '@framework/Signum.Entities' import { getQueryKey, getEnumInfo, QueryTokenString, tryGetTypeInfos, timeToString, toFormatWithFixes } from '@framework/Reflection' import { - FilterOption, OrderOption, QueryRequest, QueryToken, SubTokensOptions, ResultTable, OrderRequest, OrderType, FilterOptionParsed, hasAggregate, ColumnOption, withoutAggregate, FilterConditionOption, QueryDescription, FindOptions, withoutPinned + FilterOption, OrderOption, QueryRequest, QueryToken, SubTokensOptions, ResultTable, OrderRequest, OrderType, FilterOptionParsed, hasAggregate, ColumnOption, withoutAggregate, FilterConditionOption, QueryDescription, FindOptions, withoutPinned, SystemTime } from '@framework/FindOptions' import { AuthClient } from '../Signum.Authorization/AuthClient' import ChartButton from './ChartButton' @@ -27,7 +27,7 @@ import { Dic, softCast } from '@framework/Globals'; import { colorInterpolators, colorSchemes } from './ColorPalette/ColorUtils'; import { getColorInterpolation } from './D3Scripts/Components/ChartUtils'; import { UserQueryEntity } from '../Signum.UserQueries/Signum.UserQueries'; -import { ChartColumnEmbedded, ChartColumnType, ChartParameterEmbedded, ChartParameterType, ChartPermission, ChartRequestModel, ChartScriptSymbol, D3ChartScript, GoogleMapsChartScript, HtmlChartScript, SpecialParameterType, SvgMapsChartScript } from './Signum.Chart'; +import { ChartColumnEmbedded, ChartColumnType, ChartParameterEmbedded, ChartParameterType, ChartPermission, ChartRequestModel, ChartScriptSymbol, ChartTimeSeriesEmbedded, D3ChartScript, GoogleMapsChartScript, HtmlChartScript, SpecialParameterType, SvgMapsChartScript } from './Signum.Chart'; import { IChartBase, UserChartEntity } from './UserChart/Signum.Chart.UserChart'; import { UserChartPartHandler } from './Dashboard/View/UserChartPart'; import SelectorModal from '@framework/SelectorModal'; @@ -516,12 +516,23 @@ export namespace ChartClient { return clone; } - + export function cloneChartTimeSeries(ts : ChartTimeSeriesEmbedded | null): ChartTimeSeriesEmbedded | null { + if(!ts) + return null; + return ChartTimeSeriesEmbedded.New({ + timeSeriesStep: ts.timeSeriesStep, + timeSeriesUnit: ts.timeSeriesUnit, + startDate: ts.startDate, + endDate: ts.endDate, + timeSeriesMaxRowsPerStep: ts.timeSeriesMaxRowsPerStep}); + } + export interface ChartOptions { queryName: any, chartScript?: string, maxRows?: number | null, groupResults?: boolean, + timeSeries?: ChartTimeSeriesEmbedded | null | undefined; filterOptions?: (FilterOption | null | undefined)[]; orderOptions?: (OrderOption | null | undefined)[]; columnOptions?: (ChartColumnOption | null | undefined)[]; @@ -575,6 +586,7 @@ export namespace ChartClient { queryName: cr.queryKey, chartScript: cr.chartScript?.key.after(".") ?? undefined, maxRows: cr.maxRows, + timeSeries: cloneChartTimeSeries(cr.chartTimeSeries), filterOptions: Finder.toFilterOptions(cr.filterOptions), columnOptions: cr.columns.map(co => ({ token: co.element.token && co.element.token.tokenString, @@ -610,15 +622,17 @@ export namespace ChartClient { maxRows: co.maxRows === null ? "null" : co.maxRows === undefined || co.maxRows == Decoder.DefaultMaxRows ? undefined : co.maxRows, - groupResults: co.groupResults, + groupResults: co.groupResults, userChart: userChart && liteKey(userChart) }; + encodeTimeSeries(query, co.timeSeries); Finder.Encoder.encodeFilters(query, co.filterOptions?.notNull()); Finder.Encoder.encodeOrders(query, co.orderOptions?.notNull()); encodeParameters(query, co.parameters?.notNull()); encodeColumn(query, co.columnOptions?.notNull()); + return `/chart/${getQueryKey(co.queryName)}?` + QueryString.stringify(query); @@ -639,6 +653,17 @@ export namespace ChartClient { if (parameters) parameters.map((p, i) => query["param" + i] = scapeTilde(p.name!) + "~" + scapeTilde(p.value!)); } + + export function encodeTimeSeries(query: any, ts: ChartTimeSeriesEmbedded | null | undefined): void { + if (ts) + { + query['systemTimeStartDate'] = ts.startDate; + query['systemTimeEndDate'] = ts.endDate; + query['timeSeriesStep'] = ts.timeSeriesStep; + query['timeSeriesUnit'] = ts.timeSeriesUnit; + query['timeSeriesMaxRowsPerStep'] = ts.timeSeriesMaxRowsPerStep; + } + } } export module Decoder { @@ -658,8 +683,10 @@ export namespace ChartClient { const oos = Finder.Decoder.decodeOrders(query); oos.forEach(oo => completer.request(oo.token.toString(), SubTokensOptions.CanElement | SubTokensOptions.CanAggregate)); + const ts = Decoder.decodeTimeSeries(query); + const cols = Decoder.decodeColumns(query); - cols.map(a => a.element.token).filter(te => te != undefined).forEach(te => completer.request(te!.tokenString!, SubTokensOptions.CanAggregate | SubTokensOptions.CanElement)); + cols.map(a => a.element.token).filter(te => te != undefined).forEach(te => completer.request(te!.tokenString!, SubTokensOptions.CanAggregate | SubTokensOptions.CanElement | (ts ? SubTokensOptions.CanTimeSeries : 0))); return completer.finished().then(() => { @@ -677,6 +704,7 @@ export namespace ChartClient { filterOptions: fos.map(fo => completer.toFilterOptionParsed(fo)), columns: cols, parameters: Decoder.decodeParameters(query), + chartTimeSeries: ts, }); synchronizeColumns(chartRequest, cr); @@ -731,16 +759,40 @@ export namespace ChartClient { }) })); } + + export function decodeTimeSeries(query: any): ChartTimeSeriesEmbedded | null { + if(!query.timeSeriesUnit) + return null; + return ChartTimeSeriesEmbedded.New({ + startDate: query.systemTimeStartDate, + endDate: query.systemTimeEndDate, + timeSeriesUnit: query.timeSeriesUnit, + timeSeriesStep: query.timeSeriesStep && parseInt(query.timeSeriesStep), + timeSeriesMaxRowsPerStep: query.timeSeriesMaxRowsPerStep && parseInt(query.timeSeriesMaxRowsPerStep), + }); + } } export module API { export function getRequest(request: ChartRequestModel): QueryRequest { + var ts = request.chartTimeSeries; + var systemTime : SystemTime | undefined = ts == null ? undefined : + softCast({ + joinMode: 'AllCompatible', + mode : 'TimeSeries', + timeSeriesStep: ts.timeSeriesStep!, + timeSeriesUnit: ts.timeSeriesUnit!, + startDate: ts.startDate!, + endDate: ts.endDate!, + timeSeriesMaxRowsPerStep: ts.timeSeriesMaxRowsPerStep!, + }); return { queryKey: request.queryKey, groupResults: hasAggregates(request), + systemTime: systemTime, filters: Finder.toFilterRequests(request.filterOptions), columns: request.columns.map(mle => mle.element).filter(cce => cce.token != null).map(co => ({ token: co.token!.token!.fullKey }) as ColumnRequest), orders: request.columns.filter(mle => mle.element.orderByType != null && mle.element.token != null).orderBy(mle => mle.element.orderByIndex).map(mle => ({ token: mle.element.token!.token!.fullKey, orderType: mle.element.orderByType! }) as OrderRequest), diff --git a/Extensions/Signum.Chart/ChartLogic.cs b/Extensions/Signum.Chart/ChartLogic.cs index 2899f9ea9c..51479f336c 100644 --- a/Extensions/Signum.Chart/ChartLogic.cs +++ b/Extensions/Signum.Chart/ChartLogic.cs @@ -46,6 +46,7 @@ public static QueryRequest ToQueryRequest(this ChartRequestModel request) { QueryName = request.QueryName, GroupResults = request.HasAggregates(), + SystemTime = request.ChartTimeSeries?.ToSystemTimeRequest(), Columns = request.GetQueryColumns(), Filters = request.Filters, Orders = request.GetQueryOrders(), diff --git a/Extensions/Signum.Chart/ChartRequest.cs b/Extensions/Signum.Chart/ChartRequest.cs index 095d326aa5..5aeec30cc8 100644 --- a/Extensions/Signum.Chart/ChartRequest.cs +++ b/Extensions/Signum.Chart/ChartRequest.cs @@ -1,4 +1,6 @@ using Signum.DynamicQuery.Tokens; +using Signum.UserAssets; +using System.Xml.Linq; namespace Signum.Chart; @@ -11,6 +13,8 @@ public interface IChartBase MList Columns { get; } MList Parameters { get; } + ChartTimeSeriesEmbedded? ChartTimeSeries { get; } + void FixParameters(ChartColumnEmbedded chartColumnEntity); } @@ -61,6 +65,8 @@ public ChartScript GetChartScript() public int? MaxRows { get; set; } + public ChartTimeSeriesEmbedded? ChartTimeSeries { get; set; } + public List GetQueryColumns() { return Columns.Where(c => c.Token != null).Select(t => t.CreateColumn()).ToList(); @@ -94,7 +100,7 @@ public List Multiplications { get { return CollectionElementToken.GetElements(new HashSet(AllTokens())); } } - + public void FixParameters(ChartColumnEmbedded chartColumn) { ChartUtils.FixParameters(this, chartColumn); @@ -105,3 +111,61 @@ public bool HasAggregates() return Filters.Any(a=>a.IsAggregate()) || Columns.Any(a=>a.Token?.Token is AggregateToken); } } + +public class ChartTimeSeriesEmbedded : EmbeddedEntity +{ + [StringLengthValidator(Max = 100)] + public string? StartDate { get; set; } + + [StringLengthValidator(Max = 100)] + public string? EndDate { get; set; } + + public TimeSeriesUnit? TimeSeriesUnit { get; set; } + + [NumberIsValidator(ComparisonType.GreaterThan, 0)] + public int? TimeSeriesStep { get; set; } + + [NumberIsValidator(ComparisonType.GreaterThan, 0)] + public int? TimeSeriesMaxRowsPerStep { get; set; } + + internal ChartTimeSeriesEmbedded? FromXml(XElement xml) + { + StartDate = xml.Attribute("StartDate")?.Value; + EndDate = xml.Attribute("EndDate")?.Value; + TimeSeriesUnit = xml.Attribute("TimeSeriesUnit")?.Value.ToEnum(); + TimeSeriesStep = xml.Attribute("TimeSeriesStep")?.Value.ToInt(); + TimeSeriesMaxRowsPerStep = xml.Attribute("TimeSeriesMaxRowsPerStep")?.Value.ToInt(); + return this; + } + + internal XElement ToXml() + { + return new XElement("SystemTime", + StartDate == null ? null : new XAttribute("StartDate", StartDate), + EndDate == null ? null : new XAttribute("EndDate", EndDate), + TimeSeriesUnit == null ? null : new XAttribute("TimeSeriesUnit", TimeSeriesUnit.ToString()!), + TimeSeriesStep == null ? null : new XAttribute("TimeSeriesStep", TimeSeriesStep.ToString()!), + TimeSeriesMaxRowsPerStep == null ? null : new XAttribute("TimeSeriesMaxRowsPerStep", TimeSeriesMaxRowsPerStep.ToString()!) + ); + } + + internal SystemTimeRequest ToSystemTimeRequest() => new SystemTimeRequest + { + mode = SystemTimeMode.TimeSeries, + joinMode = SystemTimeJoinMode.AllCompatible, + endDate = ParseDate(this.EndDate), + startDate = ParseDate(this.StartDate), + timeSeriesStep = this.TimeSeriesStep, + timeSeriesUnit = this.TimeSeriesUnit, + timeSeriesMaxRowsPerStep = this.TimeSeriesMaxRowsPerStep, + }; + + DateTime? ParseDate(string? date) + { + if (date.IsNullOrEmpty()) + return null; + + + return (DateTime)FilterValueConverter.Parse(date, typeof(DateTime), false)!; + } +} diff --git a/Extensions/Signum.Chart/ChartServer.cs b/Extensions/Signum.Chart/ChartServer.cs index 491a670995..64fc1435d5 100644 --- a/Extensions/Signum.Chart/ChartServer.cs +++ b/Extensions/Signum.Chart/ChartServer.cs @@ -30,7 +30,9 @@ public static void Start(IApplicationBuilder app) if (cr.Columns != null) foreach (var c in cr.Columns) - c.ParseData(cr, qd, SubTokensOptions.CanElement | SubTokensOptions.CanAggregate); + { + c.ParseData(cr, qd, SubTokensOptions.CanElement | SubTokensOptions.CanAggregate | (cr.ChartTimeSeries != null ? SubTokensOptions.CanTimeSeries : 0)); + } } }); @@ -93,7 +95,7 @@ private static void CustomizeChartRequest() var qd = QueryLogic.Queries.QueryDescription(cr.QueryName); - cr.Filters = list.Select(l => l.ToFilter(qd, canAggregate: true, SignumServer.JsonSerializerOptions)).ToList(); + cr.Filters = list.Select(l => l.ToFilter(qd, canAggregate: true, SignumServer.JsonSerializerOptions, cr.ChartTimeSeries != null)).ToList(); }, CustomWriteJsonProperty = (Utf8JsonWriter writer, WriteJsonPropertyContext ctx) => { diff --git a/Extensions/Signum.Chart/Signum.Chart.ts b/Extensions/Signum.Chart/Signum.Chart.ts index f0667dcb52..ee63652d6e 100644 --- a/Extensions/Signum.Chart/Signum.Chart.ts +++ b/Extensions/Signum.Chart/Signum.Chart.ts @@ -307,6 +307,7 @@ export interface ChartRequestModel extends Entities.ModelEntity { columns: Entities.MList; parameters: Entities.MList; maxRows: number | null; + chartTimeSeries: ChartTimeSeriesEmbedded | null; } export const ChartScriptSymbol: Type = new Type("ChartScript"); @@ -314,6 +315,16 @@ export interface ChartScriptSymbol extends Basics.Symbol { Type: "ChartScript"; } +export const ChartTimeSeriesEmbedded: Type = new Type("ChartTimeSeriesEmbedded"); +export interface ChartTimeSeriesEmbedded extends Entities.EmbeddedEntity { + Type: "ChartTimeSeriesEmbedded"; + startDate: string | null; + endDate: string | null; + timeSeriesUnit: DynamicQuery.TimeSeriesUnit | null; + timeSeriesStep: number | null; + timeSeriesMaxRowsPerStep: number | null; +} + export module D3ChartScript { export const Bars : ChartScriptSymbol = registerSymbol("ChartScript", "D3ChartScript.Bars"); export const Columns : ChartScriptSymbol = registerSymbol("ChartScript", "D3ChartScript.Columns"); diff --git a/Extensions/Signum.Chart/Templates/ChartBuilder.tsx b/Extensions/Signum.Chart/Templates/ChartBuilder.tsx index e91b544711..9623a05a40 100644 --- a/Extensions/Signum.Chart/Templates/ChartBuilder.tsx +++ b/Extensions/Signum.Chart/Templates/ChartBuilder.tsx @@ -2,29 +2,32 @@ import * as React from 'react' import { TypeContext, mlistItemContext } from '@framework/TypeContext' import { is } from '@framework/Signum.Entities' -import { ChartColumnEmbedded, ChartMessage, ChartParameterEmbedded } from '../Signum.Chart' +import { ChartColumnEmbedded, ChartMessage, ChartParameterEmbedded, ChartTimeSeriesEmbedded } from '../Signum.Chart' import { ChartClient } from '../ChartClient' import { ChartColumn } from './ChartColumn' -import { ColorPaletteClient, ColorInterpolate, ColorScheme } from '../ColorPalette/ColorPaletteClient' +import { ColorInterpolate, ColorScheme } from '../ColorPalette/ColorPaletteClient' import { useForceUpdate, useAPI } from '@framework/Hooks' import { colorInterpolators, colorSchemes } from '../ColorPalette/ColorUtils' import { Dic } from '@framework/Globals' import { IChartBase } from '../UserChart/Signum.Chart.UserChart' -import { AutoLine, EnumLine, NumberLine, NumberLineProps, TextBoxLine, TextBoxLineProps } from '@framework/Lines' +import { EnumLine, NumberLine, TextBoxLine, TextBoxLineProps } from '@framework/Lines' import { EnumLineProps, OptionItem } from '@framework/Lines/EnumLine' +import { Finder } from '@framework/Finder' +import { getTypeInfos } from '@framework/Reflection' +import { QueryDescription } from '@framework/FindOptions' +import { DateTime } from 'luxon' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import SystemTimeEditor from '../../../Signum/React/SearchControl/SystemTimeEditor' +import ChartTimeSeries from './ChartTimeSeries' export interface ChartBuilderProps { ctx: TypeContext; /*IChart*/ queryKey: string; maxRowsReached?: boolean; + queryDescription: QueryDescription; onInvalidate: () => void; onTokenChange: () => void; onRedraw: () => void; onOrderChanged: () => void; - timeSeriesEnabled?: boolean; - onEnableTimeSerisChanged?: (a: boolean) => void; } export default function ChartBuilder(p: ChartBuilderProps): React.JSX.Element { @@ -83,8 +86,31 @@ export default function ChartBuilder(p: ChartBuilderProps): React.JSX.Element { const chartScript = chartScripts?.single(cs => is(cs.symbol, chart.chartScript)); var parameterDic = mlistItemContext(p.ctx.subCtx(c => c.parameters, { formSize: "xs", formGroupStyle: "Basic" })).toObject(a => a.value.name!); - - return ( + const qs = Finder.getSettings(p.queryKey); + + const tis = getTypeInfos(p.queryDescription.columns["Entity"].type); + var ts = p.ctx.value.chartTimeSeries; + + return (<> + {(qs?.allowSystemTime ?? tis.some(a => a.isSystemVersioned == true)) &&
+ + {ts && } +
}
@@ -116,12 +142,6 @@ export default function ChartBuilder(p: ChartBuilderProps): React.JSX.Element { Token - {p.onEnableTimeSerisChanged && < label > - { - p.onEnableTimeSerisChanged!(!p.timeSeriesEnabled); - }} /> - - } @@ -129,9 +149,9 @@ export default function ChartBuilder(p: ChartBuilderProps): React.JSX.Element { {chartScript && mlistItemContext(p.ctx.subCtx(c => c.columns, { formSize: "xs" })).map((ctx, i) => handleTokenChange(ctx.value)} - onRedraw={handleOnRedraw} - onOrderChanged={handleOrderChart} columnIndex={i} parameterDic={parameterDic} />) + queryKey={p.queryKey} onTokenChange={() => handleTokenChange(ctx.value)} + onRedraw={handleOnRedraw} + onOrderChanged={handleOrderChart} columnIndex={i} parameterDic={parameterDic} />) } @@ -140,6 +160,7 @@ export default function ChartBuilder(p: ChartBuilderProps): React.JSX.Element { {chartScript && }
+ ); } diff --git a/Extensions/Signum.Chart/Templates/ChartColumn.tsx b/Extensions/Signum.Chart/Templates/ChartColumn.tsx index d3beaab556..2d7f697d04 100644 --- a/Extensions/Signum.Chart/Templates/ChartColumn.tsx +++ b/Extensions/Signum.Chart/Templates/ChartColumn.tsx @@ -111,7 +111,7 @@ export function ChartColumn(p: ChartColumnProps): React.JSX.Element { const sc = p.scriptColumn; const cb = p.chartBase; - const subTokenOptions = SubTokensOptions.CanElement | SubTokensOptions.CanAggregate; + var subTokenOptions = SubTokensOptions.CanElement | SubTokensOptions.CanAggregate | (p.chartBase.chartTimeSeries ? SubTokensOptions.CanTimeSeries : 0); const ctx = p.ctx; diff --git a/Extensions/Signum.Chart/Templates/ChartRequestView.tsx b/Extensions/Signum.Chart/Templates/ChartRequestView.tsx index f5bbfe4da3..3a7c414793 100644 --- a/Extensions/Signum.Chart/Templates/ChartRequestView.tsx +++ b/Extensions/Signum.Chart/Templates/ChartRequestView.tsx @@ -8,12 +8,11 @@ import { ValidationError, AbortableRequest } from '@framework/Services' import { FrameMessage, Lite } from '@framework/Signum.Entities' import { SubTokensOptions, QueryToken, FilterOptionParsed } from '@framework/FindOptions' import { StyleContext, TypeContext } from '@framework/TypeContext' -import { SearchMessage } from '@framework/Signum.Entities' -import { PropertyRoute, getQueryNiceName, getTypeInfo, ReadonlyBinding, GraphExplorer } from '@framework/Reflection' +import { PropertyRoute, getQueryNiceName, getTypeInfo, ReadonlyBinding, GraphExplorer, getTypeInfos } from '@framework/Reflection' import { Navigator } from '@framework/Navigator' import FilterBuilder from '@framework/SearchControl/FilterBuilder' import { ValidationErrors } from '@framework/Frames/ValidationErrors' -import { ChartRequestModel, ChartMessage } from '../Signum.Chart' +import { ChartRequestModel, ChartMessage, ChartTimeSeriesEmbedded } from '../Signum.Chart' import ChartBuilder from './ChartBuilder' import ChartTableComponent from './ChartTable' import ChartRenderer from './ChartRenderer' @@ -24,8 +23,8 @@ import { useForceUpdate, useAPI } from '@framework/Hooks' import { AutoFocus } from '@framework/Components/AutoFocus'; import PinnedFilterBuilder from '@framework/SearchControl/PinnedFilterBuilder'; import { UserChartEntity } from '../UserChart/Signum.Chart.UserChart'; -import SystemTimeEditor from '../../../Signum/React/SearchControl/SystemTimeEditor'; -import ChartTimeSeriesEditor from './ChartTimeSeriesEditor'; +import ChartTimeSeries from './ChartTimeSeries'; +import { DateTime } from 'luxon'; interface ChartRequestViewProps { chartRequest: ChartRequestModel; @@ -49,7 +48,6 @@ export default function ChartRequestView(p: ChartRequestViewProps): React.JSX.El const lastToken = React.useRef(undefined); const [showChartSettings, setShowChartSettings] = React.useState(p.showChartSettings ?? false); - const [timeSeriesEnabled, setTimeSeriesEnabled] = React.useState(false); const [resultAndLoading, setResult] = React.useState<{ result: { @@ -169,6 +167,7 @@ export default function ChartRequestView(p: ChartRequestViewProps): React.JSX.El const titleLabels = StyleContext.default.titleLabels; const maxRowsReached = result && result.chartRequest.maxRows == result.chartResult.resultTable.rows.length; + const canTimeSeries = cr.chartTimeSeries != null ? SubTokensOptions.CanTimeSeries : 0; return (

@@ -181,7 +180,7 @@ export default function ChartRequestView(p: ChartRequestViewProps): React.JSX.El
{showChartSettings ? lastToken.current = t} showPinnedFiltersOptionsButton={true} /> : @@ -191,17 +190,13 @@ export default function ChartRequestView(p: ChartRequestViewProps): React.JSX.El }
- - {timeSeriesEnabled &&
Time machine editor
} -
{showChartSettings && <> setTimeSeriesEnabled(a => !a)} onTokenChange={() => { handleTokenChange(); forceUpdate(); }} onOrderChanged={() => { if (result) diff --git a/Extensions/Signum.Chart/Templates/ChartTimeSeriesEditor.tsx b/Extensions/Signum.Chart/Templates/ChartTimeSeries.tsx similarity index 68% rename from Extensions/Signum.Chart/Templates/ChartTimeSeriesEditor.tsx rename to Extensions/Signum.Chart/Templates/ChartTimeSeries.tsx index b26d0a93e4..6ea06f46ac 100644 --- a/Extensions/Signum.Chart/Templates/ChartTimeSeriesEditor.tsx +++ b/Extensions/Signum.Chart/Templates/ChartTimeSeries.tsx @@ -1,7 +1,5 @@ import { DateTime, DurationUnit } from 'luxon' import * as React from 'react' -import { FindOptionsParsed } from '../../../Signum/React/Search'; -import { QueryDescription } from '../../../Signum/React/FindOptions'; import { QueryTokenString, toLuxonFormat, toNumberFormat } from '@framework/Reflection'; import { JavascriptMessage } from '@framework/Signum.Entities'; import { TimeSeriesUnit } from '../../../Signum/React/Signum.DynamicQuery'; @@ -10,28 +8,26 @@ import { AggregateFunction, QueryTokenDateMessage } from '../../../Signum/React/ import { DateTimePicker } from 'react-widgets'; import { classes } from '../../../Signum/React/Globals'; import { useForceUpdate } from '../../../Signum/React/Hooks'; +import { ChartRequestModel, ChartTimeSeriesEmbedded } from '../Signum.Chart'; +import { ChartClient } from '../ChartClient' +import { IChartBase } from '../UserChart/Signum.Chart.UserChart'; -interface ChartTimeSeriesEditorProps { - findOptions: FindOptionsParsed; - queryDescription: QueryDescription; - onChanged: () => void; -} -export default function ChartTimeSeriesEditor(p: ChartTimeSeriesEditorProps): React.JSX.Element { +export default function ChartTimeSeries(p: { chartTimeSeries: ChartTimeSeriesEmbedded, chartBase: IChartBase, onChange: () => void }): React.JSX.Element { + + var ts = p.chartTimeSeries; function renderTimeSeriesUnit() { function handleTimeSeriesUnit(e: React.ChangeEvent) { - let st = p.findOptions.systemTime!; - st.timeSeriesUnit = e.currentTarget.value as TimeSeriesUnit; - st.timeSeriesStep = 1; - - p.onChanged(); + ts.timeSeriesUnit = e.currentTarget.value as TimeSeriesUnit; + ts.timeSeriesStep = 1; + p.onChange(); } return ( - {TimeSeriesUnit.values().map((stm, i) => )} ); @@ -39,17 +35,15 @@ export default function ChartTimeSeriesEditor(p: ChartTimeSeriesEditorProps): Re function renderTimeSerieStep() { - const st = p.findOptions.systemTime!; function handleTimeSerieStep(e: number | null | undefined) { - st.timeSeriesStep = e ?? 1; - - p.onChanged(); + ts.timeSeriesStep = e ?? 1; + p.onChange(); } var numberFormat = toNumberFormat("0"); return ( - ); @@ -57,15 +51,13 @@ export default function ChartTimeSeriesEditor(p: ChartTimeSeriesEditorProps): Re function renderDateTime(field: "startDate" | "endDate") { - var systemTime = p.findOptions.systemTime!; - const handleDatePickerOnChange = (date: Date | null | undefined, str: string) => { const m = date && DateTime.fromJSDate(date); - systemTime[field] = m ? m.toISO()! : undefined; - p.onChanged(); + ts[field] = m ? m.toISO()! : null; + p.onChange(); }; - var utcDate = systemTime[field] + var utcDate = ts[field]; var m = utcDate == null ? null : DateTime.fromISO(utcDate); var luxonFormat = toLuxonFormat("G", "DateTime"); @@ -78,38 +70,35 @@ export default function ChartTimeSeriesEditor(p: ChartTimeSeriesEditorProps): Re valueEditFormat={luxonFormat} valueDisplayFormat={luxonFormat} includeTime={true} messages={{ dateButton: JavascriptMessage.Date.niceToString() }} />

- ); } return ( -
- {JavascriptMessage.showRecords.niceToString()} +
+ Time series {QueryTokenDateMessage.Every01.niceToString().formatHtml(renderTimeSerieStep(), renderTimeSeriesUnit())} {renderDateTime("startDate")} {renderDateTime("endDate")} - +
); } -function TotalNumStepsAndRows(p: { findOptions: FindOptionsParsed }) { - - const st = p.findOptions.systemTime!; +function TotalNumStepsAndRows(p: { chartTimeSeries: ChartTimeSeriesEmbedded, chartBase: IChartBase, onChange: () => void }) { - const isOneValue = p.findOptions.groupResults && p.findOptions.columnOptions.every(a => a.token == null || a.token.fullKey == QueryTokenString.timeSeries().token || a.token.queryTokenType == "Aggregate"); + const isOneValue = ChartClient.hasAggregates(p.chartBase) && p.chartBase.columns.every(a => a.element.token == null || a.element.token.token?.fullKey == QueryTokenString.timeSeries.token || a.element.token.token?.queryTokenType == "Aggregate"); - const forceUpdate = useForceUpdate(); + var st = p.chartTimeSeries; React.useEffect(() => { if (isOneValue) { if (st.timeSeriesMaxRowsPerStep != 1) { st.timeSeriesMaxRowsPerStep = 1; - forceUpdate(); + p.onChange(); } } else { if (st.timeSeriesMaxRowsPerStep == 1) { st.timeSeriesMaxRowsPerStep = 10; - forceUpdate(); + p.onChange(); } } }, [isOneValue]) @@ -124,13 +113,11 @@ function TotalNumStepsAndRows(p: { findOptions: FindOptionsParsed }) { const formatter = toNumberFormat("C0"); - - return ( {QueryTokenDateMessage._0Steps1Rows2TotalRowsAprox.niceToString().formatHtml( 1000 ? "text-danger" : undefined}>{formatter.format(steps)}, - { st.timeSeriesMaxRowsPerStep = e ?? 10; forceUpdate(); }} + { st.timeSeriesMaxRowsPerStep = e ?? 10; p.onChange(); }} htmlAttributes={{ className: "form-control form-control-xs ms-1", style: { width: "40px", display: "inline-block" } }} />, 1000 ? "text-danger" : undefined}> diff --git a/Extensions/Signum.Chart/UserChart/Signum.Chart.UserChart.ts b/Extensions/Signum.Chart/UserChart/Signum.Chart.UserChart.ts index 49782e02c0..2f2946b4fc 100644 --- a/Extensions/Signum.Chart/UserChart/Signum.Chart.UserChart.ts +++ b/Extensions/Signum.Chart/UserChart/Signum.Chart.UserChart.ts @@ -44,6 +44,7 @@ export interface UserChartEntity extends Entities.Entity, UserAssets.IUserAssetE displayName: string; includeDefaultFilters: boolean | null; maxRows: number | null; + chartTimeSeries: Chart.ChartTimeSeriesEmbedded | null; chartScript: Chart.ChartScriptSymbol; parameters: Entities.MList; columns: Entities.MList; diff --git a/Extensions/Signum.Chart/UserChart/UserChart.cs b/Extensions/Signum.Chart/UserChart/UserChart.cs index 16574be00f..c00145c52f 100644 --- a/Extensions/Signum.Chart/UserChart/UserChart.cs +++ b/Extensions/Signum.Chart/UserChart/UserChart.cs @@ -44,6 +44,8 @@ public object QueryName public bool? IncludeDefaultFilters { get; set; } public int? MaxRows { get; set; } + + public ChartTimeSeriesEmbedded? ChartTimeSeries { get; set; } ChartScriptSymbol chartScript; public ChartScriptSymbol ChartScript @@ -85,10 +87,12 @@ public ChartScript GetChartScript() internal void ParseData(QueryDescription description) { foreach (var f in Filters) - f.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | SubTokensOptions.CanAggregate); + f.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | SubTokensOptions.CanAggregate | (ChartTimeSeries != null ? SubTokensOptions.CanTimeSeries : 0)); foreach (var c in Columns) - c.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanAggregate); + { + c.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanAggregate | (ChartTimeSeries != null ? SubTokensOptions.CanTimeSeries : 0)); + } } static Func ToQueryName; diff --git a/Extensions/Signum.Chart/UserChart/UserChart.tsx b/Extensions/Signum.Chart/UserChart/UserChart.tsx index a7c0a33d81..55962c8d11 100644 --- a/Extensions/Signum.Chart/UserChart/UserChart.tsx +++ b/Extensions/Signum.Chart/UserChart/UserChart.tsx @@ -3,7 +3,7 @@ import ChartBuilder from '../Templates/ChartBuilder' import { FormGroup, AutoLine, EntityLine, EntityStrip, CheckboxLine } from '@framework/Lines' import { Finder } from '@framework/Finder' import { SubTokensOptions } from '@framework/FindOptions' -import { getQueryNiceName } from '@framework/Reflection' +import { getQueryNiceName, getTypeInfos } from '@framework/Reflection' import { TypeContext } from '@framework/TypeContext' import "../Chart.css" import { useAPI, useForceUpdate } from '@framework/Hooks' @@ -12,6 +12,11 @@ import { getToString } from '@framework/Signum.Entities' import { UserChartEntity } from '../UserChart/Signum.Chart.UserChart' import { UserQueryMessage } from '../../Signum.UserQueries/Signum.UserQueries' import FilterBuilderEmbedded from '../../Signum.UserAssets/Templates/FilterBuilderEmbedded' +import { ChartTimeSeriesEmbedded } from '../Signum.Chart' +import { DateTime } from 'luxon' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import ChartTimeSeries from '../Templates/ChartTimeSeries' +import { UserChartClient } from './UserChartClient' const CurrentEntityKey = "[CurrentEntity]"; export default function UserChart(p : { ctx: TypeContext }): React.JSX.Element | null { @@ -60,10 +65,11 @@ export default function UserChart(p : { ctx: TypeContext }): Re }/> e.includeDefaultFilters)} /> e.filters)} queryKey={p.ctx.value.query.key} - subTokenOptions={SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | SubTokensOptions.CanAggregate} + subTokenOptions={SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | SubTokensOptions.CanAggregate | (ctx.value.chartTimeSeries != null ? SubTokensOptions.CanTimeSeries : 0)} showPinnedFilterOptions={true} /> forceUpdate()} onTokenChange={() => forceUpdate()} onRedraw={() => forceUpdate()} diff --git a/Extensions/Signum.Chart/UserChart/UserChartClient.tsx b/Extensions/Signum.Chart/UserChart/UserChartClient.tsx index 76119ecb6d..c8f66d91bc 100644 --- a/Extensions/Signum.Chart/UserChart/UserChartClient.tsx +++ b/Extensions/Signum.Chart/UserChart/UserChartClient.tsx @@ -77,7 +77,7 @@ export namespace UserChartClient { )); - Navigator.addSettings(new EntitySettings(UserChartEntity, e => import('./UserChart'), { isCreable: "Never" })); + Navigator.addSettings(new EntitySettings(UserChartEntity, e => import('./UserChart'), { isCreable: "Never", modalSize: 'xl' })); Navigator.addSettings(new EntitySettings(UserChartPartEntity, e => import('../Dashboard/Admin/UserChartPart'))); Navigator.addSettings(new EntitySettings(CombinedUserChartPartEntity, e => import('../Dashboard/Admin/CombinedUserChartPart'))); @@ -164,6 +164,7 @@ export namespace UserChartClient { const filters = await UserAssetClient.API.parseFilters({ queryKey: uc.query.key, canAggregate: true, + canTimeSeries: cr.chartTimeSeries != null, entity: entity, filters: uc.filters!.map(mle => UserAssetClient.Converter.toQueryFilterItem(mle.element)) }); @@ -211,7 +212,7 @@ export namespace UserChartClient { } export function toChartRequest(uq: UserChartEntity, entity?: Lite): Promise { - const cs = ChartRequestModel.New({ queryKey: uq.query!.key }); + const cs = ChartRequestModel.New({ queryKey: uq.query!.key, chartTimeSeries: ChartClient.cloneChartTimeSeries(uq.chartTimeSeries)}); return applyUserChart(cs, uq, entity); } } diff --git a/Extensions/Signum.Chart/UserChart/UserChartLogic.cs b/Extensions/Signum.Chart/UserChart/UserChartLogic.cs index 1e54dc434d..993e5267a1 100644 --- a/Extensions/Signum.Chart/UserChart/UserChartLogic.cs +++ b/Extensions/Signum.Chart/UserChart/UserChartLogic.cs @@ -257,6 +257,7 @@ public static ChartRequestModel ToChartRequest(this UserChartEntity userChart) Filters = userChart.Filters.ToFilterList(), Parameters = userChart.Parameters.ToMList(), MaxRows = userChart.MaxRows, + ChartTimeSeries = userChart.ChartTimeSeries, }; cr.Columns.ZipForeach(userChart.Columns, (a, b) => diff --git a/Extensions/Signum.Chart/UserChart/UserChartMenu.tsx b/Extensions/Signum.Chart/UserChart/UserChartMenu.tsx index ca28742b47..1b910f987b 100644 --- a/Extensions/Signum.Chart/UserChart/UserChartMenu.tsx +++ b/Extensions/Signum.Chart/UserChart/UserChartMenu.tsx @@ -7,7 +7,7 @@ import { is } from '@framework/Signum.Entities' import { Finder } from '@framework/Finder' import { Navigator } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' -import { ChartRequestModel, ChartMessage, ChartColumnEmbedded } from '../Signum.Chart' +import { ChartRequestModel, ChartMessage, ChartColumnEmbedded, ChartTimeSeriesEmbedded } from '../Signum.Chart' import { UserChartClient } from './UserChartClient' import { ChartRequestViewHandle } from '../Templates/ChartRequestView' import { UserAssetClient } from '../../Signum.UserAssets/UserAssetClient' @@ -16,6 +16,8 @@ import { AutoFocus } from '@framework/Components/AutoFocus' import { KeyNames } from '@framework/Components' import { UserQueryMerger } from '../../Signum.UserQueries/UserQueryMenu' import { UserChartEntity, UserChartOperation } from '../UserChart/Signum.Chart.UserChart' +import { clone } from '@framework/Reflection' +import { ChartClient } from '../ChartClient' export interface UserChartMenuProps { chartRequestView: ChartRequestViewHandle; @@ -139,15 +141,19 @@ export default function UserChartMenu(p: UserChartMenuProps): React.JSX.Element const fos = Finder.toFilterOptions(cr.filterOptions); const qfs = await UserAssetClient.API.stringifyFilters({ + canTimeSeries: cr.chartTimeSeries != null, canAggregate: true, queryKey: cr.queryKey, filters: fos.map(fo => UserAssetClient.Converter.toFilterNode(fo)) }); + var ts = cr.chartTimeSeries; + const uc = UserChartEntity.New({ owner: AppContext.currentUser && toLite(AppContext.currentUser), query: query, chartScript: cr.chartScript, + chartTimeSeries: ChartClient.cloneChartTimeSeries(cr.chartTimeSeries), maxRows: cr.maxRows, filters: qfs.map(f => newMListElement(UserAssetClient.Converter.toQueryFilterEmbedded(f))), columns: cr.columns.map(a => newMListElement(JSON.parse(JSON.stringify(a.element)))), diff --git a/Extensions/Signum.Migrations/Signum.Migrations.ts b/Extensions/Signum.Migrations/Signum.Migrations.ts index 665d0966ad..271688553c 100644 --- a/Extensions/Signum.Migrations/Signum.Migrations.ts +++ b/Extensions/Signum.Migrations/Signum.Migrations.ts @@ -6,7 +6,7 @@ import { MessageKey, QueryKey, Type, EnumType, registerSymbol } from '../../Sign import * as Entities from '../../Signum/React/Signum.Entities' import * as Basics from '../../Signum/React/Signum.Basics' - + export const CSharpMigrationEntity: Type = new Type("CSharpMigration"); export interface CSharpMigrationEntity extends Entities.Entity { diff --git a/Extensions/Signum.UserAssets/UserAssetClient.tsx b/Extensions/Signum.UserAssets/UserAssetClient.tsx index bc9e59a3d6..2ea88950b4 100644 --- a/Extensions/Signum.UserAssets/UserAssetClient.tsx +++ b/Extensions/Signum.UserAssets/UserAssetClient.tsx @@ -198,6 +198,7 @@ export namespace UserAssetClient { filters: QueryFilterItem[]; entity: Lite | undefined; canAggregate: boolean + canTimeSeries: boolean; } @@ -208,7 +209,8 @@ export namespace UserAssetClient { export interface StringifyFiltersRequest { queryKey: string; filters: FilterNode[]; - canAggregate: boolean + canAggregate: boolean; + canTimeSeries: boolean; } export interface FilterNode { diff --git a/Extensions/Signum.UserAssets/UserAssetController.cs b/Extensions/Signum.UserAssets/UserAssetController.cs index f07533091b..c536aa32a2 100644 --- a/Extensions/Signum.UserAssets/UserAssetController.cs +++ b/Extensions/Signum.UserAssets/UserAssetController.cs @@ -17,6 +17,7 @@ public class ParseFiltersRequest { public string queryKey; public bool canAggregate; + public bool canTimeSeries; public List filters; public Lite entity; } @@ -26,7 +27,7 @@ public List ParseFilters([Required, FromBody]ParseFiltersRequest req { var queryName = QueryLogic.ToQueryName(request.queryKey); var qd = QueryLogic.Queries.QueryDescription(queryName); - var options = SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | (request.canAggregate ? SubTokensOptions.CanAggregate : 0); + var options = SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | (request.canAggregate ? SubTokensOptions.CanAggregate : 0) | (request.canTimeSeries ? SubTokensOptions.CanTimeSeries : 0); using (request.entity != null ? CurrentEntityConverter.SetCurrentEntity(request.entity.RetrieveAndRemember()) : null) { @@ -97,6 +98,7 @@ public class StringifyFiltersRequest { public string queryKey; public bool canAggregate; + public bool canTimeSeries; public List filters; } @@ -105,7 +107,7 @@ public List StringifyFilters([Required, FromBody]StringifyFilte { var queryName = QueryLogic.ToQueryName(request.queryKey); var qd = QueryLogic.Queries.QueryDescription(queryName); - var options = SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | (request.canAggregate ? SubTokensOptions.CanAggregate : 0); + var options = SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | (request.canAggregate ? SubTokensOptions.CanAggregate : 0) | (request.canTimeSeries ? SubTokensOptions.CanTimeSeries : 0); List result = new List(); foreach (var f in request.filters) diff --git a/Extensions/Signum.UserQueries/Templates/UserQuery.tsx b/Extensions/Signum.UserQueries/Templates/UserQuery.tsx index 53723e3e8c..89db22bd13 100644 --- a/Extensions/Signum.UserQueries/Templates/UserQuery.tsx +++ b/Extensions/Signum.UserQueries/Templates/UserQuery.tsx @@ -81,7 +81,7 @@ export default function UserQuery(p: { ctx: TypeContext }): Rea
e.filters)} avoidFieldSet="h5" - subTokenOptions={SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | canAggregate} + subTokenOptions={SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | canAggregate | canTimeSeries} queryKey={ctxxs.value.query!.key} showPinnedFilterOptions={true} /> e.columns)} avoidFieldSet="h5" columns={[ diff --git a/Extensions/Signum.UserQueries/UserQueryClient.tsx b/Extensions/Signum.UserQueries/UserQueryClient.tsx index 3c3efd0903..775f6cd66a 100644 --- a/Extensions/Signum.UserQueries/UserQueryClient.tsx +++ b/Extensions/Signum.UserQueries/UserQueryClient.tsx @@ -297,6 +297,7 @@ export namespace UserQueryClient { const filters = await UserAssetClient.API.parseFilters({ queryKey: query.key, canAggregate: uq.groupResults || false, + canTimeSeries: fo.systemTime?.mode == 'TimeSeries', entity: entity, filters: uq.filters!.map(mle => UserAssetClient.Converter.toQueryFilterItem(mle.element)) }); diff --git a/Extensions/Signum.UserQueries/UserQueryEntity.cs b/Extensions/Signum.UserQueries/UserQueryEntity.cs index 75cbe1a407..bdac7fe486 100644 --- a/Extensions/Signum.UserQueries/UserQueryEntity.cs +++ b/Extensions/Signum.UserQueries/UserQueryEntity.cs @@ -97,12 +97,13 @@ public bool ShouldHaveElements internal void ParseData(QueryDescription description) { var canAggregate = this.GroupResults ? SubTokensOptions.CanAggregate : 0; + var canTimeSeries = this.SystemTime?.Mode == SystemTimeMode.TimeSeries ? SubTokensOptions.CanTimeSeries : 0; foreach (var f in Filters) - f.ParseData(this, description, SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | canAggregate); + f.ParseData(this, description, SubTokensOptions.CanAnyAll | SubTokensOptions.CanElement | canAggregate | canTimeSeries); foreach (var c in Columns) - c.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanSnippet | SubTokensOptions.CanToArray | (canAggregate != 0 ? canAggregate : SubTokensOptions.CanOperation | SubTokensOptions.CanManual)); + c.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanSnippet | SubTokensOptions.CanToArray | (canAggregate != 0 ? canAggregate : SubTokensOptions.CanOperation | SubTokensOptions.CanManual) | canTimeSeries); foreach (var o in Orders) o.ParseData(this, description, SubTokensOptions.CanElement | SubTokensOptions.CanSnippet | canAggregate); diff --git a/Extensions/Signum.UserQueries/UserQueryMenu.tsx b/Extensions/Signum.UserQueries/UserQueryMenu.tsx index 2f5715de5e..84fb1bae14 100644 --- a/Extensions/Signum.UserQueries/UserQueryMenu.tsx +++ b/Extensions/Signum.UserQueries/UserQueryMenu.tsx @@ -201,6 +201,7 @@ export default function UserQueryMenu(p: UserQueryMenuProps): React.JSX.Element const qfs = await UserAssetClient.API.stringifyFilters({ canAggregate: fo.groupResults || false, + canTimeSeries: fo.systemTime?.mode == 'TimeSeries', queryKey: getQueryKey(fo.queryName), filters: (fo.filterOptions ?? []).notNull().map(fo => UserAssetClient.Converter.toFilterNode(fo)) }); diff --git a/Extensions/Signum.Word/WordServer.cs b/Extensions/Signum.Word/WordServer.cs index 6880ab23ef..4d6e08e95a 100644 --- a/Extensions/Signum.Word/WordServer.cs +++ b/Extensions/Signum.Word/WordServer.cs @@ -90,7 +90,7 @@ private static void CustomizeFiltersModel() var qd = QueryLogic.Queries.QueryDescription(cr.QueryName); - cr.Filters = list.Select(l => l.ToFilter(qd, canAggregate: true, SignumServer.JsonSerializerOptions)).ToList(); + cr.Filters = list.Select(l => l.ToFilter(qd, canAggregate: true, SignumServer.JsonSerializerOptions, false)).ToList(); }, CustomWriteJsonProperty = (Utf8JsonWriter writer, WriteJsonPropertyContext ctx) => { diff --git a/Signum/API/Json/FilterJsonConverter.cs b/Signum/API/Json/FilterJsonConverter.cs index 371088a85b..4e295af538 100644 --- a/Signum/API/Json/FilterJsonConverter.cs +++ b/Signum/API/Json/FilterJsonConverter.cs @@ -52,7 +52,7 @@ public override void Write(Utf8JsonWriter writer, FilterTS value, JsonSerializer [JsonConverter(typeof(FilterJsonConverter))] public abstract class FilterTS { - public abstract Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions); + public abstract Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions, bool canTimeSeries); public static FilterTS FromFilter(Filter filter) { @@ -82,9 +82,9 @@ public class FilterConditionTS : FilterTS public FilterOperation operation; public object? value; - public override Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions) + public override Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions, bool canTimeSeries) { - var options = SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | (canAggregate ? SubTokensOptions.CanAggregate : 0); + var options = SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | (canAggregate ? SubTokensOptions.CanAggregate : 0) | (canTimeSeries ? SubTokensOptions.CanTimeSeries : 0); var parsedToken = QueryUtils.Parse(token, qd, options); var expectedValueType = operation.IsList() ? typeof(List<>).MakeGenericType(parsedToken.Type.Nullify()) : parsedToken.Type; @@ -124,14 +124,14 @@ public class FilterGroupTS : FilterTS public string? token; public required List filters; - public override Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions) + public override Filter ToFilter(QueryDescription qd, bool canAggregate, JsonSerializerOptions jsonSerializerOptions, bool canTimeSeries) { var options = SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | - (canAggregate ? SubTokensOptions.CanAggregate : 0); + (canAggregate ? SubTokensOptions.CanAggregate : 0) | (canTimeSeries ? SubTokensOptions.CanTimeSeries : 0); var parsedToken = token == null ? null : QueryUtils.Parse(token, qd, options); - var parsedFilters = filters.Select(f => f.ToFilter(qd, canAggregate, jsonSerializerOptions)).ToList(); + var parsedFilters = filters.Select(f => f.ToFilter(qd, canAggregate, jsonSerializerOptions, canTimeSeries)).ToList(); return new FilterGroup(groupOperation, parsedToken, parsedFilters); } @@ -209,7 +209,7 @@ public QueryValueRequest ToQueryValueRequest(string queryKey, JsonSerializerOpti { QueryName = qn, MultipleValues = multipleValues ?? false, - Filters = this.filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: false, jsonSerializerOptions)).ToList(), + Filters = this.filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: false, jsonSerializerOptions, this.systemTime?.mode == SystemTimeMode.TimeSeries)).ToList(), ValueToken = value, SystemTime = this.systemTime?.ToSystemTime(), }; @@ -256,7 +256,7 @@ public QueryRequest ToQueryRequest(string queryKey, JsonSerializerOptions jsonSe QueryUrl = referrerUrl, QueryName = qn, GroupResults = groupResults, - Filters = this.filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: groupResults, jsonSerializerOptions)).ToList(), + Filters = this.filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: groupResults, jsonSerializerOptions, timeSeries)).ToList(), Orders = this.orders.EmptyIfNull().Select(f => f.ToOrder(qd, canAggregate: groupResults, canTimeSeries: timeSeries)).ToList(), Columns = this.columns.EmptyIfNull().Select(f => f.ToColumn(qd, canAggregate: groupResults, canTimeSeries: timeSeries)).ToList(), Pagination = this.pagination.ToPagination(), @@ -288,7 +288,7 @@ public QueryEntitiesRequest ToQueryEntitiesRequest(string queryKey, JsonSerializ { QueryName = qn, Count = count, - Filters = filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: false, jsonSerializerOptions)).ToList(), + Filters = filters.EmptyIfNull().Select(f => f.ToFilter(qd, canAggregate: false, jsonSerializerOptions, canTimeSeries: false)).ToList(), Orders = orders.EmptyIfNull().Select(f => f.ToOrder(qd, canAggregate: false, canTimeSeries: false)).ToList(), }; } diff --git a/Signum/DynamicQuery/AutoDynamicQuery.cs b/Signum/DynamicQuery/AutoDynamicQuery.cs index edfe112544..4459731377 100644 --- a/Signum/DynamicQuery/AutoDynamicQuery.cs +++ b/Signum/DynamicQuery/AutoDynamicQuery.cs @@ -98,6 +98,8 @@ private DQueryable GetDQueryable(QueryRequest request, out List? inMem if (!request.Columns.Where(c => c is _EntityColumn).Any()) request.Columns.Insert(0, new _EntityColumn(EntityColumnFactory().BuildColumnDescription(), QueryName)); + + if (request.CanDoMultiplicationsInSubQueries()) { var columnAndOrderTokens = request.Columns.Select(a => a.Token) @@ -116,20 +118,20 @@ private DQueryable GetDQueryable(QueryRequest request, out List? inMem } else { - var simpleFilters = request.Filters.Where(f => !f.IsAggregate()).ToList(); - var aggregateFilters = request.Filters.Where(f => f.IsAggregate()).ToList(); + var filters = request.Filters.ToList(); + var timeSeriesFilters = filters.Extract(f => f.IsTimeSeries()); var query = Query .ToDQueryable(GetQueryDescription()) .SelectMany(request.Multiplications(), request.FullTextTableFilters()) - .Where(request.Filters); + .Where(filters); if(request.SystemTime != null && request.SystemTime.mode == SystemTimeMode.TimeSeries) { inMemoryOrders = null; return query - .SelectManyTimeSeries(request.SystemTime, request.Columns, request.Orders); + .SelectManyTimeSeries(request.SystemTime, request.Columns, request.Orders, timeSeriesFilters); } else if (request.Pagination is Pagination.All) @@ -156,8 +158,9 @@ private DQueryable GetDQueryable(QueryRequest request, out List? inMem private DQueryable GetDQueryableGroup(QueryRequest request, out List? inMemoryOrders) { - var simpleFilters = request.Filters.Where(f => !f.IsAggregate()).ToList(); - var aggregateFilters = request.Filters.Where(f => f.IsAggregate()).ToList(); + var simpleFilters = request.Filters.ToList(); + var timeSeriesFilter = simpleFilters.Extract(f => f.IsTimeSeries()); + var aggregateFilters = simpleFilters.Extract(f => f.IsAggregate()); var keys = request.Columns.Select(t => t.Token).Where(t => t is not AggregateToken && t is not TimeSeriesToken).ToHashSet(); @@ -175,7 +178,7 @@ private DQueryable GetDQueryableGroup(QueryRequest request, out List? inMemoryOrders = null; return query - .SelectManyTimeSeries(request.SystemTime, request.Columns, request.Orders); + .SelectManyTimeSeries(request.SystemTime, request.Columns, request.Orders, timeSeriesFilter); } else if (request.Pagination is Pagination.All) diff --git a/Signum/DynamicQuery/DQueryable.cs b/Signum/DynamicQuery/DQueryable.cs index d7bd54d530..c3c03e4d94 100644 --- a/Signum/DynamicQuery/DQueryable.cs +++ b/Signum/DynamicQuery/DQueryable.cs @@ -763,14 +763,17 @@ public static DEnumerable TryTake(this DEnumerable collection, int? num static MethodInfo miOverrideInExpression = ReflectionTools.GetMethodInfo(() => SystemTime.OverrideInExpression(null!, 0)).GetGenericMethodDefinition(); static ConstructorInfo ciAsOf= ReflectionTools.GetConstuctorInfo(() => new SystemTime.AsOf(DateTime.Now)); - public static DQueryable SelectManyTimeSeries(this DQueryable query, SystemTimeRequest systemTime, List columns, List orders) + public static DQueryable SelectManyTimeSeries(this DQueryable query, SystemTimeRequest systemTime, List columns, List orders, List timeSeriesFilter) { - var tokens = columns.Select(a => a.Token).Concat(orders.Select(a => a.Token)).ToHashSet(); + var tokens = columns.Select(a => a.Token).Concat(orders.Select(a => a.Token)) + .Concat(timeSeriesFilter.SelectMany(a => a.GetTokens())) + .ToHashSet(); return query .OrderBy(orders.Where(a => a.Token is not TimeSeriesToken).ToList()) .Select(tokens.Where(t => t is not TimeSeriesToken).ToHashSet()) .SelectManyTimeSeries(systemTime, tokens) + .Where(timeSeriesFilter) .OrderBy(orders.ToList()) .Select(columns); diff --git a/Signum/DynamicQuery/Requests/Filter.cs b/Signum/DynamicQuery/Requests/Filter.cs index 076b88d56b..0f66db35bb 100644 --- a/Signum/DynamicQuery/Requests/Filter.cs +++ b/Signum/DynamicQuery/Requests/Filter.cs @@ -27,6 +27,7 @@ public abstract class Filter public abstract Filter? ToFullText(); public abstract bool IsAggregate(); + public abstract bool IsTimeSeries(); public abstract IEnumerable GetKeywords(); @@ -178,6 +179,11 @@ public override bool IsAggregate() return this.Filters.Any(f => f.IsAggregate()); } + public override bool IsTimeSeries() + { + return this.Filters.Any(f => f.IsTimeSeries()); + } + internal void SetIsTable(IEnumerable fullTextOrders, bool isOuter) { isOuter |= this.GroupOperation == FilterGroupOperation.Or && this.Filters.Count > 1; @@ -343,6 +349,11 @@ public override bool IsAggregate() return this.Token is AggregateToken; } + public override bool IsTimeSeries() + { + return this.Token is TimeSeriesToken; + } + public override string ToString() { return "{0} {1} {2}".FormatWith(Token.FullKey(), Operation, Value); @@ -497,6 +508,11 @@ public override bool IsAggregate() return false; } + public override bool IsTimeSeries() + { + return false; + } + public override Filter ToFullText() { throw new InvalidOperationException("Already FilterFullText!"); @@ -517,6 +533,8 @@ public override IEnumerable GetKeywords() .Select(a => a.Trim(' ', '\r', '\n', '\t').Trim('"')) .Where(a => a.Length > 0); } + + } public static class FullTextFilterOperationExtensions diff --git a/Signum/DynamicQuery/Tokens/TimeSeriesToken.cs b/Signum/DynamicQuery/Tokens/TimeSeriesToken.cs index 8d440e0c96..6f7a5675c0 100644 --- a/Signum/DynamicQuery/Tokens/TimeSeriesToken.cs +++ b/Signum/DynamicQuery/Tokens/TimeSeriesToken.cs @@ -22,7 +22,9 @@ public override Type Type public override string ToString() { return "[" + SystemTimeMode.TimeSeries.NiceToString() + "]"; - } + } + + public override bool IsGroupable => true; public const string KeyText = "TimeSeries"; diff --git a/Signum/React/Finder.tsx b/Signum/React/Finder.tsx index d8237cfead..3ce6800ae2 100644 --- a/Signum/React/Finder.tsx +++ b/Signum/React/Finder.tsx @@ -809,7 +809,7 @@ export namespace Finder { if (fo.filterOptions) - fo.filterOptions.notNull().forEach(fo => completer.requestFilter(fo, SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | canAggregate)); + fo.filterOptions.notNull().forEach(fo => completer.requestFilter(fo, SubTokensOptions.CanElement | SubTokensOptions.CanAnyAll | canAggregate | canTimeSeries)); if (fo.orderOptions) fo.orderOptions.notNull().forEach(oo => completer.request(oo.token.toString(), SubTokensOptions.CanElement | SubTokensOptions.CanSnippet | canAggregate | canTimeSeries)); diff --git a/Signum/React/SearchControl/PinnedFilterBuilder.tsx b/Signum/React/SearchControl/PinnedFilterBuilder.tsx index 095a8dbc99..ec5a338391 100644 --- a/Signum/React/SearchControl/PinnedFilterBuilder.tsx +++ b/Signum/React/SearchControl/PinnedFilterBuilder.tsx @@ -27,7 +27,7 @@ export default function PinnedFilterBuilder(p: PinnedFilterBuilderProps): React. var allPinned = getAllPinned(p.filterOptions).filter(fop => p.pinnedFilterVisible == null || p.pinnedFilterVisible(fop)); - if (allPinned.length == 0) + if (allPinned.length == 0 && !p.showGrid) return null; function getColSpan(fo: FilterOptionParsed) { diff --git a/Signum/React/SearchControl/SearchControlLoaded.tsx b/Signum/React/SearchControl/SearchControlLoaded.tsx index f71e341ec5..09250c9848 100644 --- a/Signum/React/SearchControl/SearchControlLoaded.tsx +++ b/Signum/React/SearchControl/SearchControlLoaded.tsx @@ -544,7 +544,7 @@ export class SearchControlLoaded extends React.Component this.handleFiltersChanged()} onHeightChanged={this.handleHeightChanged}