Skip to content

Commit f3a2cd7

Browse files
authored
Merge pull request #16 from Acumatica/logging-and-bugfixes
Logging and bugfixes
2 parents d51e675 + eb65e9d commit f3a2cd7

25 files changed

+429
-85
lines changed

acumate-plugin/readme.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ The **AcuMate** extension for Visual Studio Code offers a range of powerful feat
6262
- Verifies `<field name="...">` entries against the PXView resolved from the surrounding markup and ignores deliberate `unbound replace-content` placeholders.
6363
- Validates `state.bind` attributes point to PXAction members, and `qp-field control-state.bind` values follow the `<view>.<field>` format with existing fields.
6464
- Enforces `<qp-panel id="...">` bindings by making sure the id maps to an existing PXView, and reuses that view context when checking footer `<qp-button state.bind="...">` actions so dialogs only reference actions exposed by their owning view.
65+
- Requires `qp-panel` nodes and all `qp-*` controls to define `id` attributes (except for `qp-field`, `qp-label`, and `qp-include`) so missing identifiers and misbound panels are caught before packaging.
6566
- Enforces Acumatica-specific constructs: required qp-include parameters, rejection of undeclared include attributes, and qp-template name checks sourced from ScreenTemplates metadata.
67+
- Guards `<qp-template name="record-*">` usages so record templates only validate when the markup sits inside a `<qp-data-feed>` container, matching runtime restrictions.
6668
- Leverages client-controls config schemas to inspect `config.bind` JSON on qp-* controls, reporting malformed JSON, missing required properties, and unknown keys before runtime.
6769
- Parses customization attributes such as `before`, `after`, `append`, `prepend`, `move`, and ensures their CSS selectors resolve against the base screen HTML so misplaced selectors surface immediately instead of at publish time.
6870
- Integrates these diagnostics with ESLint + VS Code so warnings surface consistently in editors and CI.
@@ -76,6 +78,13 @@ The **AcuMate** extension for Visual Studio Code offers a range of powerful feat
7678
- Offers IntelliSense suggestions for available `view.bind` values sourced from the PXScreen metadata.
7779
- Provides field name suggestions that automatically scope to the closest parent view binding, so only valid fields appear.
7880
- Attribute parsing tolerates empty values (`view.bind=""`) to keep suggestions responsive while editing.
81+
- Template name completions automatically filter out `record-*` entries unless the caret is inside a `<qp-data-feed>`, keeping suggestions aligned with validation rules.
82+
83+
### Logging & Observability
84+
85+
- All extension subsystems log to a single **AcuMate** output channel, making it easy to trace backend requests, caching behavior, and command execution without hunting through multiple panes.
86+
- Every AcuMate command writes a structured log entry (arguments + timing) so build/validation flows can be audited when integrating with CI or troubleshooting user reports.
87+
- Backend API calls, cache hits/misses, and configuration reloads emit detailed log lines, giving immediate visibility into why a control lookup or metadata request might have failed.
7988

8089
4. **Backend Field Hovers**
8190
- Hovering over `<field name="...">` or `<qp-field name="...">` immediately shows backend metadata (display name, type, default control type, originating view) sourced from the same data that powers TypeScript hovers.

acumate-plugin/snippets.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"prefix": ["hook", "handleEvent", "currentRowChanged"],
44
"body": [
55
"@handleEvent(CustomEventType.CurrentRowChanged, { view: \"${1:ViewName}\" })",
6-
"on${2:Row}Changed(args: CurrentRowChangedHandlerArgs<${3:ViewType}>) {",
7-
" $4",
6+
"on${1:ViewName}Changed(args: CurrentRowChangedHandlerArgs<${2:ViewType}>) {",
7+
" $3",
88
"}"
99
],
1010
"description": "CurrentRowChanged event hook"
@@ -13,18 +13,18 @@
1313
"prefix": ["hook", "handleEvent", "rowSelected"],
1414
"body": [
1515
"@handleEvent(CustomEventType.RowSelected, { view: \"${1:ViewName}\" })",
16-
"on${2:Row}Changed(args: RowSelectedHandlerArgs<${3:ViewType}>) {",
17-
" $4",
16+
"on${1:ViewName}Changed(args: RowSelectedHandlerArgs<${2:ViewType}>) {",
17+
" $3",
1818
"}"
1919
],
2020
"description": "RowSelected event hook"
2121
},
2222
"ValueChanged": {
2323
"prefix": ["hook", "handleEvent", "valueChanged"],
2424
"body": [
25-
"@handleEvent(CustomEventType.ValueChanged, { view: \"${1:ViewName}\" })",
26-
"on${1:ViewName}Changed(args: ValueChangedHandlerArgs<${2:ViewType}>) {",
27-
" $3",
25+
"@handleEvent(CustomEventType.ValueChanged, { view: \"${1:ViewName}\", field: \"${2:FieldName}\" })",
26+
"on${2:FieldName}Changed(args: ValueChangedHandlerArgs<${3:ViewType}>) {",
27+
" $4",
2828
"}"
2929
],
3030
"description": "ValueChanged event hook"
@@ -43,8 +43,8 @@
4343
"prefix": ["hook", "handleEvent", "getRowCss"],
4444
"body": [
4545
"@handleEvent(CustomEventType.GetRowCss, { view: \"${1:ViewName}\" })",
46-
"get${2:Row}RowCss(args: RowCssHandlerArgs): string | undefined {",
47-
" $3",
46+
"get${1:ViewName}RowCss(args: RowCssHandlerArgs): string | undefined {",
47+
" $2",
4848
"}"
4949
],
5050
"description": "GetRowCss event hook"
@@ -53,8 +53,8 @@
5353
"prefix": ["hook", "handleEvent", "getCellCss"],
5454
"body": [
5555
"@handleEvent(CustomEventType.GetCellCss, { view: \"${1:ViewName}\", allColumns: true })",
56-
"get${2:Cell}CellCss(args: CellCssHandlerArgs): string | undefined {",
57-
" $3",
56+
"get${1:ViewName}CellCss(args: CellCssHandlerArgs): string | undefined {",
57+
" $2",
5858
"}"
5959
],
6060
"description": "GetCellCss event hook"

acumate-plugin/src/api/api-service.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GraphAPIRoute, GraphAPIStructureRoute, AuthEndpoint, LogoutEndpoint, Fe
44
import { IAcuMateApiClient } from "./acu-mate-api-client";
55
import { AcuMateContext } from "../plugin-context";
66
import { FeatureModel } from "../model/FeatureModel";
7+
import { logError, logInfo } from "../logging/logger";
78

89
interface FeatureSetsResponse {
910
sets?: FeatureSetEntry[];
@@ -63,6 +64,7 @@ export class AcuMateApiClient implements IAcuMateApiClient {
6364
return;
6465
}
6566

67+
logInfo('Logging out from AcuMate backend.');
6668
const response = await fetch(AcuMateContext.ConfigurationService.backedUrl!+LogoutEndpoint, {
6769
method:'POST',
6870
headers: {
@@ -74,24 +76,26 @@ export class AcuMateApiClient implements IAcuMateApiClient {
7476
this.sessionCookieHeader = undefined;
7577

7678
if (response.ok) {
77-
console.log('Logged out successfully.');
79+
logInfo('Backend session closed successfully.');
7880
} else {
7981
const errorBody = await response.text().catch(() => "");
80-
console.error(`Authentication failed with status ${response.status}: ${errorBody}`);
82+
logError('Backend logout failed.', { status: response.status, errorBody });
8183
}
8284
}
8385

8486
private async makeGetRequest<T>(route: string): Promise<T | undefined> {
8587
if (!AcuMateContext.ConfigurationService.useBackend) {
88+
logInfo('Skipped backend request because acuMate.useBackend is disabled.', { route });
8689
return undefined;
8790
}
8891

8992
try {
93+
logInfo('Authenticating before backend request.', { route });
9094
const authResponse = await this.auth();
9195

9296
if (authResponse.status !== 200 && authResponse.status !== 204) {
9397
const errorBody = await authResponse.text().catch(() => "");
94-
console.error(`Authentication failed with status ${authResponse.status}: ${errorBody}`);
98+
logError('AcuMate backend authentication failed.', { status: authResponse.status, errorBody });
9599
return undefined;
96100
}
97101

@@ -106,29 +110,33 @@ export class AcuMateApiClient implements IAcuMateApiClient {
106110
headers
107111
};
108112
settings.credentials = `include`;
113+
logInfo('Issuing backend GET request.', { url });
109114
const response = await fetch(url, settings);
110115

111116
if (!response.ok) {
112117
const errorBody = await response.text().catch(() => "");
113-
console.error(`GET ${url} failed with status ${response.status}: ${errorBody}`);
118+
logError('Backend GET request failed.', { url, status: response.status, errorBody });
114119
return undefined;
115120
}
116121

117122
const contentType = response.headers.get("content-type") ?? "";
118123
if (!contentType.includes("application/json")) {
119124
const errorBody = await response.text().catch(() => "");
120-
console.error(`GET ${url} returned non-JSON content (${contentType}): ${errorBody}`);
125+
logError('Backend GET returned unexpected content type.', { url, contentType, errorBody });
121126
return undefined;
122127
}
123128

124129
const data = await response.json();
125-
126-
console.log(data);
130+
const summary: Record<string, unknown> = { url };
131+
if (Array.isArray(data)) {
132+
summary.items = data.length;
133+
}
134+
logInfo('Backend GET succeeded.', summary);
127135

128136
return data as T;
129137
}
130138
catch (error) {
131-
console.error('Error making GET request:', error);
139+
logError('Unexpected error during backend GET request.', { route, error });
132140
}
133141
finally {
134142
await this.logout();

acumate-plugin/src/api/layered-data-service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IAcuMateApiClient } from "./acu-mate-api-client";
55
import { CachedDataService } from "./cached-data-service";
66
import { FeaturesCache, GraphAPICache, GraphAPIStructureCachePrefix } from "./constants";
77
import { FeatureModel } from "../model/FeatureModel";
8+
import { logInfo } from "../logging/logger";
89

910
export class LayeredDataService implements IAcuMateApiClient {
1011

@@ -19,16 +20,19 @@ export class LayeredDataService implements IAcuMateApiClient {
1920
async getGraphs(): Promise<GraphModel[] | undefined> {
2021
const cachedResult = await this.cacheService.getGraphs();
2122
if (cachedResult) {
23+
logInfo('Serving graphs from cache.', { count: cachedResult.length });
2224
return cachedResult;
2325
}
2426

27+
logInfo('Graph cache miss. Fetching from backend...');
2528
if (this.inflightGraphs) {
2629
return this.inflightGraphs;
2730
}
2831

2932
this.inflightGraphs = this.apiService
3033
.getGraphs()
3134
.then(result => {
35+
logInfo('Graphs fetched from backend.', { count: result?.length ?? 0 });
3236
this.cacheService.store(GraphAPICache, result);
3337
return result;
3438
})
@@ -43,6 +47,7 @@ export class LayeredDataService implements IAcuMateApiClient {
4347
async getGraphStructure(graphName: string): Promise<GraphStructure | undefined> {
4448
const cachedResult = await this.cacheService.getGraphStructure(graphName);
4549
if (cachedResult) {
50+
logInfo('Serving cached graph structure.', { graphName });
4651
return cachedResult;
4752
}
4853

@@ -51,9 +56,11 @@ export class LayeredDataService implements IAcuMateApiClient {
5156
return existing;
5257
}
5358

59+
logInfo('Graph structure cache miss. Fetching from backend...', { graphName });
5460
const pending = this.apiService
5561
.getGraphStructure(graphName)
5662
.then(result => {
63+
logInfo('Graph structure fetched from backend.', { graphName, hasResult: Boolean(result) });
5764
this.cacheService.store(GraphAPIStructureCachePrefix + graphName, result);
5865
return result;
5966
})
@@ -68,16 +75,19 @@ export class LayeredDataService implements IAcuMateApiClient {
6875
async getFeatures(): Promise<FeatureModel[] | undefined> {
6976
const cachedResult = await this.cacheService.getFeatures();
7077
if (cachedResult) {
78+
logInfo('Serving feature metadata from cache.', { count: cachedResult.length });
7179
return cachedResult;
7280
}
7381

82+
logInfo('Feature cache miss. Fetching from backend...');
7483
if (this.inflightFeatures) {
7584
return this.inflightFeatures;
7685
}
7786

7887
this.inflightFeatures = this.apiService
7988
.getFeatures()
8089
.then(result => {
90+
logInfo('Features fetched from backend.', { count: result?.length ?? 0 });
8191
this.cacheService.store(FeaturesCache, result);
8292
return result;
8393
})

0 commit comments

Comments
 (0)