@olenbetong/appframe-core 2.16.0 → 2.16.1

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.
package/es/index.js CHANGED
@@ -19,4 +19,4 @@ export { UserSession };
19
19
  export { alert, ConfirmButtons, confirm, openAlertDialog, waitForDialog } from "./alert.js";
20
20
  export { getSettings, saveSettings } from "./settings.js";
21
21
  // oxlint-disable-next-line no-use-before-define -- this is injected build time
22
- export const version = "2.15.2";
22
+ export const version = "2.16.1";
@@ -27,4 +27,4 @@ export declare function readElements(xml: string, name: string): string[];
27
27
  * Returns `null` for anything that is not a record source, so a missing or
28
28
  * empty `RecordSource` column simply means "no lookup".
29
29
  */
30
- export declare function parseRecordSource(xml: string | null | undefined): ReportRecordSource | null;
30
+ export declare function parseRecordSource(document: string | null | undefined): ReportRecordSource | null;
@@ -80,6 +80,19 @@ function readSelectColumns(xml) {
80
80
  }
81
81
  return columns;
82
82
  }
83
+ /**
84
+ * The record source a document holds. Parameter lookups store one `<afRecordSource>`; a report
85
+ * stores its record sources as `<ArrayOfAfReportRecordSource>` with one `<afReportRecordSource>`
86
+ * per data source, of which the one with the ID `Master` feeds the report (else the first).
87
+ */
88
+ function pickRecordSource(document) {
89
+ if (!document)
90
+ return null;
91
+ let sources = [...document.matchAll(/<(afRecordSource|afReportRecordSource)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/g)].map((match) => match[2]);
92
+ if (sources.length === 0)
93
+ return null;
94
+ return sources.find((source) => readElement(source, "ID") === "Master") ?? sources[0];
95
+ }
83
96
  /**
84
97
  * Reads the parts of an `afRecordSource` a web client can act on: the view, the
85
98
  * selected columns and the design-time filter.
@@ -87,8 +100,9 @@ function readSelectColumns(xml) {
87
100
  * Returns `null` for anything that is not a record source, so a missing or
88
101
  * empty `RecordSource` column simply means "no lookup".
89
102
  */
90
- export function parseRecordSource(xml) {
91
- if (!xml || !xml.includes("<afRecordSource"))
103
+ export function parseRecordSource(document) {
104
+ let xml = pickRecordSource(document);
105
+ if (!xml)
92
106
  return null;
93
107
  let viewName = readElement(xml, "ViewName");
94
108
  if (!viewName)
@@ -36,6 +36,42 @@ describe("parseRecordSource", () => {
36
36
  let xml = recordSource.replace("[Level] = 1", "[Name] Like &apos;%a &amp; b%&apos;");
37
37
  expect(parseRecordSource(xml)?.filterString).toBe("[Name] Like '%a & b%'");
38
38
  });
39
+ it("reads a report's record sources, taking the master one", () => {
40
+ let report = `<?xml version="1.0" encoding="utf-16"?>
41
+ <ArrayOfAfReportRecordSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
42
+ <afReportRecordSource>
43
+ <ViewName>aviw_Production_ElementsSub</ViewName>
44
+ <FilterString />
45
+ <ID>Sub</ID>
46
+ </afReportRecordSource>
47
+ <afReportRecordSource>
48
+ <IsFilterChanging>false</IsFilterChanging>
49
+ <ViewName>aviw_Production_ElementsProductionPerCutOffWithPrevious</ViewName>
50
+ <SelectColumns>
51
+ <afSelectColumn><Name>ProjectID</Name></afSelectColumn>
52
+ <afSelectColumn><Name>Area</Name><DataType>Decimal</DataType></afSelectColumn>
53
+ </SelectColumns>
54
+ <WhereClause>[Type] = 'Element'</WhereClause>
55
+ <FilterString>[ProjectID] = 'PT19-1004' AND [Period] = '2021-49'</FilterString>
56
+ <MaxRecords>0</MaxRecords>
57
+ <ID>Master</ID>
58
+ </afReportRecordSource>
59
+ </ArrayOfAfReportRecordSource>`;
60
+ expect(parseRecordSource(report)).toEqual({
61
+ viewName: "aviw_Production_ElementsProductionPerCutOffWithPrevious",
62
+ columns: [
63
+ { name: "ProjectID", type: "string" },
64
+ { name: "Area", type: "number" },
65
+ ],
66
+ whereClause: "[Type] = 'Element'",
67
+ filterString: "[ProjectID] = 'PT19-1004' AND [Period] = '2021-49'",
68
+ maxRecords: null,
69
+ });
70
+ });
71
+ it("takes the first record source when none is the master", () => {
72
+ let report = `<ArrayOfAfReportRecordSource><afReportRecordSource><ViewName>aviw_A</ViewName></afReportRecordSource><afReportRecordSource><ViewName>aviw_B</ViewName></afReportRecordSource></ArrayOfAfReportRecordSource>`;
73
+ expect(parseRecordSource(report)?.viewName).toBe("aviw_A");
74
+ });
39
75
  it("returns nothing for anything that is not a record source", () => {
40
76
  expect(parseRecordSource(null)).toBeNull();
41
77
  expect(parseRecordSource("")).toBeNull();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-core",
3
- "version": "2.16.0",
3
+ "version": "2.16.1",
4
4
  "description": "Javascript libraries to utilize the Appframe framework",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -44,7 +44,7 @@
44
44
  "@types/bun": "^1.4.4",
45
45
  "chalk": "^6.0.1",
46
46
  "typescript": "7.0.2",
47
- "vitest": "5.0.5"
47
+ "vitest": "5.1.0"
48
48
  },
49
49
  "peerDependencies": {},
50
50
  "optionalDependencies": {},
@@ -97,6 +97,20 @@ function readSelectColumns(xml: string): ReportRecordSourceColumn[] {
97
97
  return columns;
98
98
  }
99
99
 
100
+ /**
101
+ * The record source a document holds. Parameter lookups store one `<afRecordSource>`; a report
102
+ * stores its record sources as `<ArrayOfAfReportRecordSource>` with one `<afReportRecordSource>`
103
+ * per data source, of which the one with the ID `Master` feeds the report (else the first).
104
+ */
105
+ function pickRecordSource(document: string | null | undefined): string | null {
106
+ if (!document) return null;
107
+ let sources = [...document.matchAll(/<(afRecordSource|afReportRecordSource)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/g)].map(
108
+ (match) => match[2],
109
+ );
110
+ if (sources.length === 0) return null;
111
+ return sources.find((source) => readElement(source, "ID") === "Master") ?? sources[0];
112
+ }
113
+
100
114
  /**
101
115
  * Reads the parts of an `afRecordSource` a web client can act on: the view, the
102
116
  * selected columns and the design-time filter.
@@ -104,8 +118,9 @@ function readSelectColumns(xml: string): ReportRecordSourceColumn[] {
104
118
  * Returns `null` for anything that is not a record source, so a missing or
105
119
  * empty `RecordSource` column simply means "no lookup".
106
120
  */
107
- export function parseRecordSource(xml: string | null | undefined): ReportRecordSource | null {
108
- if (!xml || !xml.includes("<afRecordSource")) return null;
121
+ export function parseRecordSource(document: string | null | undefined): ReportRecordSource | null {
122
+ let xml = pickRecordSource(document);
123
+ if (!xml) return null;
109
124
 
110
125
  let viewName = readElement(xml, "ViewName");
111
126
  if (!viewName) return null;
@@ -47,6 +47,45 @@ describe("parseRecordSource", () => {
47
47
  expect(parseRecordSource(xml)?.filterString).toBe("[Name] Like '%a & b%'");
48
48
  });
49
49
 
50
+ it("reads a report's record sources, taking the master one", () => {
51
+ let report = `<?xml version="1.0" encoding="utf-16"?>
52
+ <ArrayOfAfReportRecordSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
53
+ <afReportRecordSource>
54
+ <ViewName>aviw_Production_ElementsSub</ViewName>
55
+ <FilterString />
56
+ <ID>Sub</ID>
57
+ </afReportRecordSource>
58
+ <afReportRecordSource>
59
+ <IsFilterChanging>false</IsFilterChanging>
60
+ <ViewName>aviw_Production_ElementsProductionPerCutOffWithPrevious</ViewName>
61
+ <SelectColumns>
62
+ <afSelectColumn><Name>ProjectID</Name></afSelectColumn>
63
+ <afSelectColumn><Name>Area</Name><DataType>Decimal</DataType></afSelectColumn>
64
+ </SelectColumns>
65
+ <WhereClause>[Type] = 'Element'</WhereClause>
66
+ <FilterString>[ProjectID] = 'PT19-1004' AND [Period] = '2021-49'</FilterString>
67
+ <MaxRecords>0</MaxRecords>
68
+ <ID>Master</ID>
69
+ </afReportRecordSource>
70
+ </ArrayOfAfReportRecordSource>`;
71
+
72
+ expect(parseRecordSource(report)).toEqual({
73
+ viewName: "aviw_Production_ElementsProductionPerCutOffWithPrevious",
74
+ columns: [
75
+ { name: "ProjectID", type: "string" },
76
+ { name: "Area", type: "number" },
77
+ ],
78
+ whereClause: "[Type] = 'Element'",
79
+ filterString: "[ProjectID] = 'PT19-1004' AND [Period] = '2021-49'",
80
+ maxRecords: null,
81
+ });
82
+ });
83
+
84
+ it("takes the first record source when none is the master", () => {
85
+ let report = `<ArrayOfAfReportRecordSource><afReportRecordSource><ViewName>aviw_A</ViewName></afReportRecordSource><afReportRecordSource><ViewName>aviw_B</ViewName></afReportRecordSource></ArrayOfAfReportRecordSource>`;
86
+ expect(parseRecordSource(report)?.viewName).toBe("aviw_A");
87
+ });
88
+
50
89
  it("returns nothing for anything that is not a record source", () => {
51
90
  expect(parseRecordSource(null)).toBeNull();
52
91
  expect(parseRecordSource("")).toBeNull();