Skip to content

Commit ffe5886

Browse files
authored
Merge branch 'main' into locfiles/fb31dd56-22a1-42e0-b9ea-9188b31de105
2 parents a82bdf7 + bd9570b commit ffe5886

10 files changed

Lines changed: 209 additions & 57 deletions

src/web/client/WebExtensionContext.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { QuickPickProvider } from "./webViews/QuickPickProvider";
3535
import { UserCollaborationProvider } from "./webViews/userCollaborationProvider";
3636
import { GraphClientService } from "./services/graphClientService";
3737
import { ServiceEndpointCategory } from "../../common/services/Constants";
38+
import { createHttpResponseError, isHttpResponseError } from "./utilities/errorHandlerUtil";
3839

3940
export interface IWebExtensionContext {
4041
// From portalSchema properties
@@ -576,7 +577,7 @@ class WebExtensionContext implements IWebExtensionContext {
576577
headers: getCommonHeadersForDataverse(accessToken),
577578
});
578579
if (!response?.ok) {
579-
throw new Error(JSON.stringify(response));
580+
throw await createHttpResponseError(response);
580581
}
581582
this.telemetry.sendAPISuccessTelemetry(
582583
requestUrl,
@@ -592,8 +593,8 @@ class WebExtensionContext implements IWebExtensionContext {
592593
schema
593594
);
594595
} catch (error) {
595-
if ((error as Response)?.status > 0) {
596-
const errorMsg = (error as Error)?.message;
596+
const errorMsg = (error as Error)?.message;
597+
if (isHttpResponseError(error) && error.httpDetails) {
597598
this.telemetry.sendAPIFailureTelemetry(
598599
requestUrl,
599600
languageEntityName,
@@ -602,13 +603,13 @@ class WebExtensionContext implements IWebExtensionContext {
602603
this.populateLanguageIdToCode.name,
603604
errorMsg,
604605
'',
605-
(error as Response)?.status.toString(),
606+
error.httpDetails.statusCode.toString(),
606607
);
607608
} else {
608609
this.telemetry.sendErrorTelemetry(
609610
webExtensionTelemetryEventNames.WEB_EXTENSION_POPULATE_LANGUAGE_ID_TO_CODE_SYSTEM_ERROR,
610611
this.populateLanguageIdToCode.name,
611-
(error as Error)?.message,
612+
errorMsg,
612613
error as Error
613614
);
614615
}
@@ -642,7 +643,7 @@ class WebExtensionContext implements IWebExtensionContext {
642643
headers: getCommonHeadersForDataverse(accessToken),
643644
});
644645
if (!response?.ok) {
645-
throw new Error(JSON.stringify(response));
646+
throw await createHttpResponseError(response);
646647
}
647648
this.telemetry.sendAPISuccessTelemetry(
648649
requestUrl,
@@ -655,8 +656,8 @@ class WebExtensionContext implements IWebExtensionContext {
655656
this._websiteLanguageIdToPortalLanguageMap =
656657
getWebsiteLanguageIdToPortalLanguageIdMap(result, schema);
657658
} catch (error) {
658-
if ((error as Response)?.status > 0) {
659-
const errorMsg = (error as Error)?.message;
659+
const errorMsg = (error as Error)?.message;
660+
if (isHttpResponseError(error) && error.httpDetails) {
660661
this.telemetry.sendAPIFailureTelemetry(
661662
requestUrl,
662663
languageEntityName,
@@ -665,13 +666,13 @@ class WebExtensionContext implements IWebExtensionContext {
665666
this.populateWebsiteLanguageIdToPortalLanguageMap.name,
666667
errorMsg,
667668
'',
668-
(error as Response)?.status.toString()
669+
error.httpDetails.statusCode.toString()
669670
);
670671
} else {
671672
this.telemetry.sendErrorTelemetry(
672673
webExtensionTelemetryEventNames.WEB_EXTENSION_POPULATE_WEBSITE_LANGUAGE_ID_TO_PORTALLANGUAGE_SYSTEM_ERROR,
673674
this.populateWebsiteLanguageIdToPortalLanguageMap.name,
674-
(error as Error)?.message,
675+
errorMsg,
675676
error as Error
676677
);
677678
}
@@ -705,7 +706,7 @@ class WebExtensionContext implements IWebExtensionContext {
705706
});
706707

707708
if (!response?.ok) {
708-
throw new Error(JSON.stringify(response));
709+
throw await createHttpResponseError(response);
709710
}
710711
this.telemetry.sendAPISuccessTelemetry(
711712
requestUrl,
@@ -717,8 +718,8 @@ class WebExtensionContext implements IWebExtensionContext {
717718
const result = await response?.json();
718719
this._websiteIdToLanguage = getWebsiteIdToLcidMap(result, schema);
719720
} catch (error) {
720-
if ((error as Response)?.status > 0) {
721-
const errorMsg = (error as Error)?.message;
721+
const errorMsg = (error as Error)?.message;
722+
if (isHttpResponseError(error) && error.httpDetails) {
722723
this.telemetry.sendAPIFailureTelemetry(
723724
requestUrl,
724725
websiteEntityName,
@@ -727,13 +728,13 @@ class WebExtensionContext implements IWebExtensionContext {
727728
this.populateWebsiteIdToLanguageMap.name,
728729
errorMsg,
729730
'',
730-
(error as Response)?.status.toString()
731+
error.httpDetails.statusCode.toString()
731732
);
732733
} else {
733734
this.telemetry.sendErrorTelemetry(
734735
webExtensionTelemetryEventNames.WEB_EXTENSION_POPULATE_WEBSITE_ID_TO_LANGUAGE_SYSTEM_ERROR,
735736
this.populateWebsiteIdToLanguageMap.name,
736-
(error as Error)?.message,
737+
errorMsg,
737738
error as Error
738739
);
739740
}

src/web/client/dal/remoteFetchProvider.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { IAttributePath, IFileInfo } from "../common/interfaces";
3535
import { portal_schema_V2 } from "../schema/portalSchema";
3636
import { ERROR_CONSTANTS } from "../../../common/ErrorConstants";
3737
import { showErrorDialog } from "../../../common/utilities/errorHandlerUtil";
38+
import { createHttpResponseError, isHttpResponseError } from "../utilities/errorHandlerUtil";
3839
import { EnableServerLogicChanges, EnableDuplicateFileHandling, EnableBlogSupport } from "../../../common/ecs-features/ecsFeatureGates";
3940
import { ECSFeaturesClient } from "../../../common/ecs-features/ecsFeatureClient";
4041

@@ -138,7 +139,7 @@ async function fetchFromDataverseAndCreateFiles(
138139

139140
if (!response.ok) {
140141
makeRequestCall = false;
141-
throw new Error(JSON.stringify(response));
142+
throw await createHttpResponseError(response);
142143
}
143144

144145
const result = await response.json();
@@ -181,7 +182,8 @@ async function fetchFromDataverseAndCreateFiles(
181182
makeRequestCall = false;
182183
const errorMsg = (error as Error)?.message;
183184
console.error(vscode.l10n.t("Failed to fetch some files."));
184-
if ((error as Response)?.status > 0) {
185+
if (isHttpResponseError(error) && error.httpDetails) {
186+
// HTTP error - use API failure telemetry with status code
185187
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
186188
requestUrl,
187189
entityName,
@@ -190,13 +192,14 @@ async function fetchFromDataverseAndCreateFiles(
190192
fetchFromDataverseAndCreateFiles.name,
191193
errorMsg,
192194
'',
193-
(error as Response)?.status.toString()
195+
error.httpDetails.statusCode.toString()
194196
);
195197
} else {
198+
// System error (network failure, timeout, etc.)
196199
WebExtensionContext.telemetry.sendErrorTelemetry(
197200
webExtensionTelemetryEventNames.WEB_EXTENSION_FETCH_DATAVERSE_AND_CREATE_FILES_SYSTEM_ERROR,
198201
fetchFromDataverseAndCreateFiles.name,
199-
(error as Error)?.message,
202+
errorMsg,
200203
error as Error
201204
);
202205
}
@@ -658,17 +661,18 @@ async function fetchMappingEntityContent(
658661
}
659662

660663
if (!response.ok) {
664+
const httpError = await createHttpResponseError(response);
661665
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
662666
requestUrl,
663667
entity,
664668
Constants.httpMethod.GET,
665669
new Date().getTime() - requestSentAtTime,
666670
fetchMappingEntityContent.name,
667-
JSON.stringify(response),
671+
httpError.message,
668672
'',
669-
response?.status.toString()
673+
httpError.httpDetails.statusCode.toString()
670674
);
671-
throw new Error(response.statusText);
675+
throw httpError;
672676
}
673677

674678
WebExtensionContext.telemetry.sendAPISuccessTelemetry(

src/web/client/dal/remoteSaveProvider.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { IAttributePath } from "../common/interfaces";
2121
import { webExtensionTelemetryEventNames } from "../../../common/OneDSLoggerTelemetry/web/client/webExtensionTelemetryEvents";
2222
import { MultiFileSupportedEntityName, schemaEntityKey } from "../schema/constants";
2323
import { getEntityMappingEntityId } from "../utilities/fileAndEntityUtil";
24+
import { createHttpResponseError, isHttpResponseError } from "../utilities/errorHandlerUtil";
2425

2526
interface ISaveCallParameters {
2627
requestInit: RequestInit;
@@ -190,7 +191,7 @@ async function saveDataToDataverse(
190191
);
191192

192193
if (!response.ok) {
193-
throw new Error(JSON.stringify(response));
194+
throw await createHttpResponseError(response);
194195
}
195196

196197
WebExtensionContext.telemetry.sendAPISuccessTelemetry(
@@ -203,28 +204,33 @@ async function saveDataToDataverse(
203204
fileExtensionType
204205
);
205206
} catch (error) {
206-
const authError = (error as Error)?.message;
207-
if ((error as Response)?.status > 0) {
207+
const errorMessage = (error as Error)?.message;
208+
if (isHttpResponseError(error) && error.httpDetails) {
209+
// HTTP error - use API failure telemetry with status code
208210
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
209211
saveCallParameters.requestUrl,
210212
entityName,
211213
httpMethod.PATCH,
212214
new Date().getTime() - requestSentAtTime,
213215
saveDataToDataverse.name,
214-
authError,
216+
errorMessage,
215217
fileExtensionType,
216-
(error as Response)?.status as unknown as string
218+
error.httpDetails.statusCode.toString()
217219
);
218220
} else {
221+
// System error (network failure, timeout, etc.)
219222
WebExtensionContext.telemetry.sendErrorTelemetry(
220223
webExtensionTelemetryEventNames.WEB_EXTENSION_SAVE_DATA_TO_DATAVERSE_API_ERROR,
221224
saveDataToDataverse.name,
222-
(error as Error)?.message,
225+
errorMessage,
223226
error as Error
224227
);
225228
}
226229

227-
if (typeof error === "string" && error.includes("Unauthorized")) {
230+
// Check for authorization failure (401) or "Unauthorized" in error message
231+
const isUnauthorized = (isHttpResponseError(error) && error.httpDetails?.statusCode === 401) ||
232+
errorMessage.includes("Unauthorized");
233+
if (isUnauthorized) {
228234
showErrorDialog(
229235
vscode.l10n.t(
230236
"Authorization Failed. Please run again to authorize it"
@@ -235,7 +241,7 @@ async function saveDataToDataverse(
235241
);
236242
} else {
237243
showErrorDialog(
238-
vscode.l10n.t("Theres a problem on the back end"),
244+
vscode.l10n.t("There's a problem on the back end"),
239245
vscode.l10n.t("Try again")
240246
);
241247
}

src/web/client/services/etagHandlerService.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "../utilities/fileAndEntityUtil";
2121
import { getRequestURL } from "../utilities/urlBuilderUtil";
2222
import WebExtensionContext from "../WebExtensionContext";
23+
import { createHttpResponseError, isHttpResponseError } from "../utilities/errorHandlerUtil";
2324

2425
export class EtagHandlerService {
2526
public static async getLatestFileContentAndUpdateMetadata(
@@ -109,7 +110,7 @@ export class EtagHandlerService {
109110
this.getLatestFileContentAndUpdateMetadata.name,
110111
response.statusText
111112
);
112-
throw new Error(JSON.stringify(response));
113+
throw await createHttpResponseError(response);
113114
}
114115

115116
WebExtensionContext.telemetry.sendAPISuccessTelemetry(
@@ -120,23 +121,25 @@ export class EtagHandlerService {
120121
this.getLatestFileContentAndUpdateMetadata.name
121122
);
122123
} catch (error) {
123-
if ((error as Response)?.status > 0) {
124-
const authError = (error as Error)?.message;
124+
const errorMessage = (error as Error)?.message;
125+
if (isHttpResponseError(error) && error.httpDetails) {
126+
// HTTP error - use API failure telemetry with status code
125127
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
126128
requestUrl,
127129
entityName,
128130
httpMethod.GET,
129131
new Date().getTime() - requestSentAtTime,
130132
this.getLatestFileContentAndUpdateMetadata.name,
131-
authError,
133+
errorMessage,
132134
'',
133-
(error as Response)?.status.toString()
135+
error.httpDetails.statusCode.toString()
134136
);
135137
} else {
138+
// System error (network failure, timeout, etc.)
136139
WebExtensionContext.telemetry.sendErrorTelemetry(
137140
webExtensionTelemetryEventNames.WEB_EXTENSION_ETAG_HANDLER_SERVICE_ERROR,
138141
this.getLatestFileContentAndUpdateMetadata.name,
139-
(error as Error)?.message
142+
errorMessage
140143
);
141144
}
142145
}
@@ -192,7 +195,7 @@ export class EtagHandlerService {
192195
this.updateFileEtag.name,
193196
response.statusText
194197
);
195-
throw new Error(JSON.stringify(response));
198+
throw await createHttpResponseError(response);
196199
}
197200

198201
WebExtensionContext.telemetry.sendAPISuccessTelemetry(
@@ -203,23 +206,25 @@ export class EtagHandlerService {
203206
this.updateFileEtag.name
204207
);
205208
} catch (error) {
206-
if ((error as Response)?.status > 0) {
207-
const authError = (error as Error)?.message;
209+
const errorMessage = (error as Error)?.message;
210+
if (isHttpResponseError(error) && error.httpDetails) {
211+
// HTTP error - use API failure telemetry with status code
208212
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
209213
requestUrl,
210214
entityName,
211215
httpMethod.GET,
212216
new Date().getTime() - requestSentAtTime,
213217
this.updateFileEtag.name,
214-
authError,
218+
errorMessage,
215219
'',
216-
(error as Response)?.status.toString()
220+
error.httpDetails.statusCode.toString()
217221
);
218222
} else {
223+
// System error (network failure, timeout, etc.)
219224
WebExtensionContext.telemetry.sendErrorTelemetry(
220225
webExtensionTelemetryEventNames.WEB_EXTENSION_ETAG_HANDLER_SERVICE_ERROR,
221226
this.updateFileEtag.name,
222-
(error as Error)?.message,
227+
errorMessage,
223228
error as Error
224229
);
225230
}

src/web/client/services/graphClientService.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import WebExtensionContext from "../WebExtensionContext";
88
import { getCommonHeaders, graphClientAuthentication } from "../../../common/services/AuthenticationProvider";
99
import * as Constants from "../common/constants";
1010
import { webExtensionTelemetryEventNames } from "../../../common/OneDSLoggerTelemetry/web/client/webExtensionTelemetryEvents";
11+
import { createHttpResponseError, isHttpResponseError } from "../utilities/errorHandlerUtil";
1112

1213
export class GraphClientService {
1314
private _graphToken: string;
@@ -85,7 +86,7 @@ export class GraphClientService {
8586
);
8687

8788
if (!response.ok) {
88-
throw new Error(JSON.stringify(response));
89+
throw await createHttpResponseError(response);
8990
}
9091

9192
WebExtensionContext.telemetry.sendAPISuccessTelemetry(
@@ -99,7 +100,8 @@ export class GraphClientService {
99100
return await response.json();
100101
} catch (error) {
101102
const errorMsg = (error as Error)?.message;
102-
if ((error as Response)?.status > 0) {
103+
if (isHttpResponseError(error) && error.httpDetails) {
104+
// HTTP error - use API failure telemetry with status code
103105
WebExtensionContext.telemetry.sendAPIFailureTelemetry(
104106
requestUrl.href,
105107
service,
@@ -108,9 +110,10 @@ export class GraphClientService {
108110
this.requestGraphClient.name,
109111
errorMsg,
110112
"",
111-
(error as Response)?.status.toString()
113+
error.httpDetails.statusCode.toString()
112114
);
113115
} else {
116+
// System error (network failure, timeout, etc.)
114117
WebExtensionContext.telemetry.sendErrorTelemetry(
115118
webExtensionTelemetryEventNames.WEB_EXTENSION_GET_FROM_GRAPH_CLIENT_FAILED,
116119
this.requestGraphClient.name,

src/web/client/test/integration/WebExtensionContext.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,9 +615,14 @@ describe("WebExtensionContext", () => {
615615
"getPortalLanguageIdToLcidMap"
616616
).returns(portalLanguageIdCodeMap);
617617

618+
const mockResponseBody = JSON.stringify({ value: "value" });
618619
const _mockFetch = stub(fetch, "default").resolves({
619620
ok: false,
620-
statusText: "statusText",
621+
status: 500,
622+
statusText: "Internal Server Error",
623+
url: "https://test.crm.dynamics.com",
624+
clone: function() { return this; },
625+
text: () => Promise.resolve(mockResponseBody),
621626
json: () => {
622627
return new Promise((resolve) => {
623628
return resolve({ value: "value" });

0 commit comments

Comments
 (0)