@olenbetong/appframe-core 2.11.10 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/esm/af.common.js +1988 -626
  2. package/dist/esm/af.common.legacy.js +1942 -626
  3. package/dist/esm/af.common.legacy.min.js +4 -3
  4. package/dist/esm/af.common.legacy.min.js.map +1 -7
  5. package/dist/esm/af.common.min.js +4 -3
  6. package/dist/esm/af.common.min.js.map +1 -7
  7. package/dist/iife/af.common.js +2146 -708
  8. package/dist/iife/af.common.legacy.js +2100 -708
  9. package/dist/iife/af.common.legacy.min.js +4 -3
  10. package/dist/iife/af.common.legacy.min.js.map +1 -7
  11. package/dist/iife/af.common.min.js +4 -3
  12. package/dist/iife/af.common.min.js.map +1 -7
  13. package/es/alert.js +16 -1
  14. package/es/filter/convertFilter.d.ts +23 -0
  15. package/es/filter/convertFilter.js +26 -0
  16. package/es/filter/describeFilter.d.ts +13 -0
  17. package/es/filter/describeFilter.js +60 -0
  18. package/es/filter/describeFilter.test.d.ts +1 -0
  19. package/es/filter/describeFilter.test.js +68 -0
  20. package/es/filter/fields.d.ts +28 -0
  21. package/es/filter/fields.js +37 -0
  22. package/es/filter/fields.test.d.ts +1 -0
  23. package/es/filter/fields.test.js +49 -0
  24. package/es/filter/index.d.ts +8 -0
  25. package/es/filter/index.js +7 -0
  26. package/es/filter/operators.d.ts +37 -0
  27. package/es/filter/operators.js +235 -0
  28. package/es/filter/operators.test.d.ts +1 -0
  29. package/es/filter/operators.test.js +107 -0
  30. package/es/filter/savedFilters.d.ts +111 -0
  31. package/es/filter/savedFilters.js +118 -0
  32. package/es/filter/savedFilters.test.d.ts +1 -0
  33. package/es/filter/savedFilters.test.js +108 -0
  34. package/es/filter/standardLayout.d.ts +18 -0
  35. package/es/filter/standardLayout.js +55 -0
  36. package/es/filter/standardLayout.test.d.ts +1 -0
  37. package/es/filter/standardLayout.test.js +66 -0
  38. package/es/filter/tree.d.ts +70 -0
  39. package/es/filter/tree.js +185 -0
  40. package/es/filter/tree.test.d.ts +1 -0
  41. package/es/filter/tree.test.js +184 -0
  42. package/es/filter/types.d.ts +73 -0
  43. package/es/filter/types.js +1 -0
  44. package/es/index.d.ts +2 -0
  45. package/es/index.js +4 -2
  46. package/es/parseFunc.test.js +5 -6
  47. package/es/report/index.d.ts +6 -0
  48. package/es/report/index.js +5 -0
  49. package/es/report/parameterDefaults.d.ts +26 -0
  50. package/es/report/parameterDefaults.js +77 -0
  51. package/es/report/parameters.d.ts +46 -0
  52. package/es/report/parameters.js +188 -0
  53. package/es/report/parameters.test.d.ts +1 -0
  54. package/es/report/parameters.test.js +161 -0
  55. package/es/report/recordSource.d.ts +30 -0
  56. package/es/report/recordSource.js +105 -0
  57. package/es/report/render.d.ts +81 -0
  58. package/es/report/render.js +219 -0
  59. package/es/report/render.test.d.ts +1 -0
  60. package/es/report/render.test.js +144 -0
  61. package/es/report/report.d.ts +16 -0
  62. package/es/report/report.js +50 -0
  63. package/es/report/report.test.d.ts +1 -0
  64. package/es/report/report.test.js +86 -0
  65. package/es/report/types.d.ts +124 -0
  66. package/es/report/types.js +8 -0
  67. package/package.json +7 -7
  68. package/src/alert.ts +16 -1
  69. package/src/filter/convertFilter.ts +47 -0
  70. package/src/filter/describeFilter.test.ts +103 -0
  71. package/src/filter/describeFilter.ts +79 -0
  72. package/src/filter/fields.test.ts +58 -0
  73. package/src/filter/fields.ts +56 -0
  74. package/src/filter/index.ts +62 -0
  75. package/src/filter/operators.test.ts +137 -0
  76. package/src/filter/operators.ts +258 -0
  77. package/src/filter/savedFilters.test.ts +145 -0
  78. package/src/filter/savedFilters.ts +192 -0
  79. package/src/filter/standardLayout.test.ts +88 -0
  80. package/src/filter/standardLayout.ts +65 -0
  81. package/src/filter/tree.test.ts +258 -0
  82. package/src/filter/tree.ts +238 -0
  83. package/src/filter/types.ts +146 -0
  84. package/src/index.ts +3 -1
  85. package/src/parseFunc.test.ts +5 -6
  86. package/src/report/index.ts +37 -0
  87. package/src/report/parameterDefaults.ts +101 -0
  88. package/src/report/parameters.test.ts +218 -0
  89. package/src/report/parameters.ts +230 -0
  90. package/src/report/recordSource.ts +123 -0
  91. package/src/report/render.test.ts +230 -0
  92. package/src/report/render.ts +269 -0
  93. package/src/report/report.test.ts +104 -0
  94. package/src/report/report.ts +51 -0
  95. package/src/report/types.ts +135 -0
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { parseRecordSource } from "./recordSource.js";
4
+ import { getReportFormats, toReport } from "./report.js";
5
+
6
+ const recordSource = `<?xml version="1.0" encoding="utf-16"?>
7
+ <afRecordSource>
8
+ <ViewName>aviw_Personnel_PersonsReportingToMe</ViewName>
9
+ <SelectColumns>
10
+ <afSelectColumn><Name>PersonID</Name></afSelectColumn>
11
+ <afSelectColumn><Name>Name</Name></afSelectColumn>
12
+ </SelectColumns>
13
+ <WhereClause />
14
+ <FilterString>[Level] = 1</FilterString>
15
+ <MaxRecords>0</MaxRecords>
16
+ </afRecordSource>`;
17
+
18
+ describe("parseRecordSource", () => {
19
+ it("reads the view, columns and design-time filter", () => {
20
+ expect(parseRecordSource(recordSource)).toEqual({
21
+ viewName: "aviw_Personnel_PersonsReportingToMe",
22
+ columns: [
23
+ { name: "PersonID", type: "string" },
24
+ { name: "Name", type: "string" },
25
+ ],
26
+ whereClause: null,
27
+ filterString: "[Level] = 1",
28
+ maxRecords: null,
29
+ });
30
+ });
31
+
32
+ it("maps the CLR type of a column onto a field type", () => {
33
+ let xml = recordSource.replace(
34
+ "<afSelectColumn><Name>Name</Name></afSelectColumn>",
35
+ "<afSelectColumn><Name>Level</Name><DataType>Int32</DataType></afSelectColumn>",
36
+ );
37
+
38
+ expect(parseRecordSource(xml)?.columns).toEqual([
39
+ { name: "PersonID", type: "string" },
40
+ { name: "Level", type: "number" },
41
+ ]);
42
+ });
43
+
44
+ it("decodes entities", () => {
45
+ let xml = recordSource.replace("[Level] = 1", "[Name] Like &apos;%a &amp; b%&apos;");
46
+
47
+ expect(parseRecordSource(xml)?.filterString).toBe("[Name] Like '%a & b%'");
48
+ });
49
+
50
+ it("returns nothing for anything that is not a record source", () => {
51
+ expect(parseRecordSource(null)).toBeNull();
52
+ expect(parseRecordSource("")).toBeNull();
53
+ expect(parseRecordSource("<afRecordSource></afRecordSource>")).toBeNull();
54
+ });
55
+ });
56
+
57
+ describe("getReportFormats", () => {
58
+ it("offers the spreadsheet formats from the CSV flag", () => {
59
+ expect(getReportFormats({ FormatPDF: true, FormatCSV: true })).toEqual(["PDF", "XLSX", "XLS", "CSV"]);
60
+ });
61
+
62
+ it("accepts bits as numbers", () => {
63
+ expect(getReportFormats({ FormatPDF: 1, FormatHTML: 0 })).toEqual(["PDF"]);
64
+ });
65
+
66
+ it("offers nothing for a report with no formats", () => {
67
+ expect(getReportFormats({})).toEqual([]);
68
+ });
69
+ });
70
+
71
+ describe("toReport", () => {
72
+ it("resolves the view and the initial filter from the record source", () => {
73
+ let report = toReport({
74
+ ReportID: "arpt_Personnel_Persons",
75
+ ShortID: 1234,
76
+ Title: " Persons ",
77
+ RecordSource: recordSource,
78
+ FormatPDF: true,
79
+ HasTitle: 1,
80
+ HasHeader: 0,
81
+ });
82
+
83
+ expect(report).toEqual({
84
+ reportId: "arpt_Personnel_Persons",
85
+ shortId: 1234,
86
+ title: "Persons",
87
+ description: null,
88
+ viewName: "aviw_Personnel_PersonsReportingToMe",
89
+ initialFilter: "[Level] = 1",
90
+ hasTitle: true,
91
+ hasHeader: false,
92
+ formats: ["PDF"],
93
+ timeout: null,
94
+ });
95
+ });
96
+
97
+ it("survives a report without a record source", () => {
98
+ let report = toReport({ ReportID: "arpt_Test" });
99
+
100
+ expect(report.viewName).toBeNull();
101
+ expect(report.initialFilter).toBeNull();
102
+ expect(report.shortId).toBeNull();
103
+ });
104
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Mapping a `sviw_WinClient_MyReports` record onto the report model, including
3
+ * which output formats the report offers.
4
+ */
5
+
6
+ import { parseRecordSource } from "./recordSource.js";
7
+ import type { Report, ReportFormat, ReportRecord } from "./types.js";
8
+
9
+ function isSet(value: boolean | number | null | undefined) {
10
+ return value === true || value === 1;
11
+ }
12
+
13
+ /**
14
+ * The formats a report may be rendered to, in the order the launcher offers
15
+ * them.
16
+ *
17
+ * `FormatCSV` covers the whole spreadsheet family: the Windows launcher enables
18
+ * "save as XLS" and "save as XLSX" from the same flag, because they all come out
19
+ * of the same DevExpress export.
20
+ */
21
+ export function getReportFormats(record: ReportRecord): ReportFormat[] {
22
+ let formats: ReportFormat[] = [];
23
+
24
+ if (isSet(record.FormatPDF)) formats.push("PDF");
25
+ if (isSet(record.FormatCSV)) formats.push("XLSX", "XLS", "CSV");
26
+ if (isSet(record.FormatHTML)) formats.push("HTML");
27
+ if (isSet(record.FormatRTF)) formats.push("RTF");
28
+ if (isSet(record.FormatMHT)) formats.push("MHT");
29
+ if (isSet(record.FormatTEXT)) formats.push("TEXT");
30
+ if (isSet(record.FormatIMAGE)) formats.push("IMAGE");
31
+
32
+ return formats;
33
+ }
34
+
35
+ /** Maps a report record onto the model the launcher renders. */
36
+ export function toReport(record: ReportRecord): Report {
37
+ let recordSource = parseRecordSource(record.RecordSource);
38
+
39
+ return {
40
+ reportId: record.ReportID ?? "",
41
+ shortId: record.ShortID ?? null,
42
+ title: record.Title?.trim() || null,
43
+ description: record.Description?.trim() || null,
44
+ viewName: recordSource?.viewName ?? null,
45
+ initialFilter: recordSource?.filterString ?? null,
46
+ hasTitle: isSet(record.HasTitle),
47
+ hasHeader: isSet(record.HasHeader),
48
+ formats: getReportFormats(record),
49
+ timeout: record.Timeout ?? null,
50
+ };
51
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Report metadata — the shape of a report and its parameters, as the Windows
3
+ * report launcher understands them.
4
+ *
5
+ * The loading lives in `useReport` in `@olenbetong/appframe-react`; everything
6
+ * here is pure.
7
+ */
8
+
9
+ /**
10
+ * The parameter types `sviw_WinClient_ReportsParameters.DataType` uses. The
11
+ * Windows launcher creates a text edit, a numeric edit, a date edit or a check
12
+ * edit from this, unless the parameter has a lookup or a fixed item list.
13
+ */
14
+ export type ReportParameterType = "String" | "Integer" | "Double" | "Date" | "Boolean";
15
+
16
+ /** How a parameter should be edited. Derived from the metadata, not stored. */
17
+ export type ReportParameterEditor = "text" | "number" | "date" | "boolean" | "select" | "lookup";
18
+
19
+ /** A column selected by an `afRecordSource`. */
20
+ export type ReportRecordSourceColumn = {
21
+ name: string;
22
+ /** The field type the web data API expects, mapped from the CLR type. */
23
+ type: "string" | "number" | "boolean" | "date";
24
+ };
25
+
26
+ /** A lookup source parsed out of a parameter's `afRecordSource` XML. */
27
+ export type ReportRecordSource = {
28
+ viewName: string;
29
+ /** The columns the record source selects, in order. */
30
+ columns: ReportRecordSourceColumn[];
31
+ whereClause: string | null;
32
+ filterString: string | null;
33
+ maxRecords: number | null;
34
+ };
35
+
36
+ /** A report parameter, ready to be rendered. */
37
+ export type ReportParameter = {
38
+ name: string;
39
+ /** Label shown next to the editor. Falls back to the name. */
40
+ label: string;
41
+ /** Help text for the editor. */
42
+ description: string | null;
43
+ type: ReportParameterType;
44
+ editor: ReportParameterEditor;
45
+ /**
46
+ * The raw default. A value matching `^[a-z]stp_` names a stored procedure
47
+ * that produces the default; see `isProcedureDefault`.
48
+ */
49
+ defaultValue: string | null;
50
+ /** Fixed list of choices, one per line in `Items`. */
51
+ items: string[];
52
+ /** Lookup source, when the parameter is backed by a view. */
53
+ recordSource: ReportRecordSource | null;
54
+ /** The lookup column holding the value to send to the report. */
55
+ valueMember: string | null;
56
+ /** The lookup column to show. The first column that is not the value member. */
57
+ displayMember: string | null;
58
+ sortOrder: number;
59
+ /** Preferred size of the lookup popup, in pixels. */
60
+ lookupWidth: number | null;
61
+ lookupHeight: number | null;
62
+ };
63
+
64
+ /** A raw `sviw_WinClient_ReportsParameters` record. */
65
+ export type ReportParameterRecord = {
66
+ Name?: string | null;
67
+ Label?: string | null;
68
+ Description?: string | null;
69
+ DataType?: string | null;
70
+ DefaultValue?: string | null;
71
+ Items?: string | null;
72
+ RecordSource?: string | null;
73
+ ValueMember?: string | null;
74
+ ValueListViewName?: string | null;
75
+ SortOrder?: number | null;
76
+ LookupWidth?: number | null;
77
+ LookupHeight?: number | null;
78
+ };
79
+
80
+ /** Output formats a report can be rendered to. */
81
+ export type ReportFormat = "PDF" | "XLSX" | "XLS" | "CSV" | "HTML" | "RTF" | "MHT" | "TEXT" | "IMAGE";
82
+
83
+ /** A report, as the launcher needs it. */
84
+ export type Report = {
85
+ reportId: string;
86
+ /**
87
+ * The report's number, which is what users refer to a report by — it is
88
+ * printed in the footer of every report.
89
+ */
90
+ shortId: number | null;
91
+ title: string | null;
92
+ description: string | null;
93
+ /** The view the report reads, resolved from its record source. */
94
+ viewName: string | null;
95
+ /**
96
+ * The filter the report is designed with. Used unless the launcher is opened
97
+ * with an explicit filter.
98
+ */
99
+ initialFilter: string | null;
100
+ /** True when the report has a title band the user may override. */
101
+ hasTitle: boolean;
102
+ /** True when the report has a header band the user may fill in. */
103
+ hasHeader: boolean;
104
+ /** The formats the report may be rendered to, in the order they are offered. */
105
+ formats: ReportFormat[];
106
+ timeout: number | null;
107
+ };
108
+
109
+ /**
110
+ * A raw `sviw_WinClient_MyReports` record, optionally merged with the
111
+ * `HasTitle` / `HasHeader` columns from `sviw_WinClient_ReportsWeb`.
112
+ */
113
+ export type ReportRecord = {
114
+ ReportID?: string | null;
115
+ ShortID?: number | null;
116
+ Title?: string | null;
117
+ Description?: string | null;
118
+ RecordSource?: string | null;
119
+ FormatPDF?: boolean | number | null;
120
+ FormatCSV?: boolean | number | null;
121
+ FormatHTML?: boolean | number | null;
122
+ FormatRTF?: boolean | number | null;
123
+ FormatMHT?: boolean | number | null;
124
+ FormatTEXT?: boolean | number | null;
125
+ FormatIMAGE?: boolean | number | null;
126
+ HasTitle?: boolean | number | null;
127
+ HasHeader?: boolean | number | null;
128
+ Timeout?: number | null;
129
+ };
130
+
131
+ /** A parameter value, as sent to the render endpoint. */
132
+ export type ReportParameterValue = string | number | boolean | null;
133
+
134
+ /** Parameter values by parameter name. */
135
+ export type ReportParameterValues = Record<string, ReportParameterValue>;