Skip to content
This repository has been archived by the owner on Jun 28, 2024. It is now read-only.

Commit

Permalink
fix: polling errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub-Vacek committed Jul 11, 2023
1 parent ae107c2 commit 0094edf
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 79 deletions.
10 changes: 4 additions & 6 deletions src/commands/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ export default class Map extends Command {
boilerplate.saved
? `Boilerplate code prepared for ${resolvedLanguage} at ${boilerplate.path}`
: `Boilerplate for ${getLanguageName(
resolvedLanguage
)} already exists at ${boilerplate.path}.`
resolvedLanguage
)} already exists at ${boilerplate.path}.`
);

if (boilerplate.envVariables !== undefined) {
Expand All @@ -154,10 +154,8 @@ export default class Map extends Command {
ux.warn(project.installationGuide);

ux.succeed(
`Local project set up. You can now install defined dependencies and run \`superface execute ${
resolvedProviderJson.providerJson.name
} ${
ProfileId.fromScopeName(profile.scope, profile.name).id
`Local project set up. You can now install defined dependencies and run \`superface execute ${resolvedProviderJson.providerJson.name
} ${ProfileId.fromScopeName(profile.scope, profile.name).id
}\` to execute your integration.`
);
}
Expand Down
16 changes: 8 additions & 8 deletions src/common/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import { UX } from './ux';
*/
export const createUserError =
(emoji: boolean) =>
(message: string, code: number): CLIError => {
// Make sure that UX is stoped before throwing an error.
UX.clear();
(message: string, code: number): CLIError => {
// Make sure that UX is stoped before throwing an error.
UX.clear();

if (code <= 0) {
throw developerError('expected positive error code', 1);
}
if (code <= 0) {
throw developerError('expected positive error code', 1);
}

return new CLIError(emoji ? '❌ ' + message : message, { exit: code });
};
return new CLIError(emoji ? '❌ ' + message : message, { exit: code });
};
export type UserError = ReturnType<typeof createUserError>;

export type DeveloperError = typeof developerError;
Expand Down
84 changes: 50 additions & 34 deletions src/common/polling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { UserError } from './error';
import type { UX } from './ux';

export const DEFAULT_POLLING_TIMEOUT_SECONDS = 300;
export const DEFAULT_POLLING_INTERVAL_SECONDS = 1;
export const DEFAULT_POLLING_INTERVAL_SECONDS = 2;

enum PollStatus {
Success = 'Success',
Expand All @@ -19,24 +19,24 @@ enum PollResultType {
}
type PollResponse =
| {
result_url: string;
status: PollStatus.Success;
result_type: PollResultType;
}
result_url: string;
status: PollStatus.Success;
result_type: PollResultType;
}
| {
status: PollStatus.Pending;
events: {
occuredAt: Date;
type: string;
description: string;
}[];
result_type: PollResultType;
}
status: PollStatus.Pending;
events: {
occuredAt: Date;
type: string;
description: string;
}[];
result_type: PollResultType;
}
| {
status: PollStatus.Failed;
failure_reason: string;
result_type: PollResultType;
}
status: PollStatus.Failed;
failure_reason: string;
result_type: PollResultType;
}
| { status: PollStatus.Cancelled; result_type: PollResultType };

function isPollResponse(input: unknown): input is PollResponse {
Expand Down Expand Up @@ -94,12 +94,10 @@ export async function pollUrl(
};
},
{
// logger,
client,
userError,
ux,
}: {
// logger: ILogger;
client: ServiceClient;
userError: UserError;
ux: UX;
Expand All @@ -111,18 +109,20 @@ export async function pollUrl(
const pollingIntervalMilliseconds =
(options?.pollingIntervalSeconds ?? DEFAULT_POLLING_INTERVAL_SECONDS) *
1000;

let lastEvenetDescription = '';
while (
new Date().getTime() - startPollingTimeStamp.getTime() <
timeoutMilliseconds
) {
const result = await pollFetch(url, { client, userError });

if (result.status === PollStatus.Success) {
ux.succeed(`Successfully finished operation`);
return result.result_url;
} else if (result.status === PollStatus.Failed) {
throw userError(
`Failed to ${getJobDescription(result.result_type)}: ${
result.failure_reason
`Failed to ${getJobDescription(result.result_type)}: ${result.failure_reason
}`,
1
);
Expand All @@ -133,19 +133,23 @@ export async function pollUrl(
)}: Operation has been cancelled.`,
1
);
} else if (result.status === PollStatus.Pending) {
// get events from response and present them to user
if (result.events.length > 0 && options?.quiet !== true) {
const currentEvent = result.events[result.events.length - 1];

if (currentEvent.description !== lastEvenetDescription) {
// console.log(`${currentEvent.type} - ${currentEvent.description}`);
ux.info(`${currentEvent.type} - ${currentEvent.description}`);
}

lastEvenetDescription = currentEvent.description;

await new Promise(resolve =>
setTimeout(resolve, pollingIntervalMilliseconds)
);
}
}

// get events from response and present them to user
if (result.events.length > 0 && options?.quiet !== true) {
const lastEvent = result.events[result.events.length - 1];

ux.info(`${lastEvent.type} - ${lastEvent.description}`);
// logger.info('pollingEvent', lastEvent.type, lastEvent.description);
}

await new Promise(resolve =>
setTimeout(resolve, pollingIntervalMilliseconds)
);
}

throw userError(
Expand Down Expand Up @@ -179,7 +183,19 @@ async function pollFetch(
},
});
if (result.status === 200) {
const data = (await result.json()) as unknown;
let data: unknown;
try {
data = (await result.json()) as unknown;
} catch (error) {
throw userError(
`Unexpected response from server: ${JSON.stringify(
await result.text(),
null,
2
)}`,
1
);
}

if (isPollResponse(data)) {
return data;
Expand Down
11 changes: 9 additions & 2 deletions src/common/ux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class UX {
private lastText = '';

private constructor() {
this.spinner = createSpinner(undefined, { color: 'cyan', interval: 25 });
this.spinner = createSpinner(undefined, { color: 'cyan', interval: 50 });
UX.instance = this;
}

Expand All @@ -28,7 +28,9 @@ export class UX {
}

public info(text: string): void {
if (text !== this.lastText) {
if (text.trim() !== this.lastText.trim()) {

this.spinner.clear();
this.spinner.update({ text });
}

Expand All @@ -39,6 +41,10 @@ export class UX {
this.spinner.warn({ text: yellow(text), mark: yellow('⚠') });
}

public stop(): void {
this.spinner.stop();
}

public static create(): UX {
if (UX.instance === undefined) {
UX.instance = new UX();
Expand All @@ -48,6 +54,7 @@ export class UX {
}

public static clear(): void {
UX.instance?.spinner.clear();
UX.instance?.spinner.stop();
}
}
17 changes: 9 additions & 8 deletions src/logic/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export type MapPreparationResponse = {
};

function assertMapResponse(
input: unknown
input: unknown,
{ userError }: { userError: UserError }
): asserts input is MapPreparationResponse {
if (typeof input === 'object' && input !== null && 'source' in input) {
const tmp = input as { source: string };
Expand All @@ -21,7 +22,7 @@ function assertMapResponse(
}
}

throw Error(`Unexpected response received`);
throw userError(`Unexpected response received`, 1);
}

export async function mapProviderToProfile(
Expand Down Expand Up @@ -58,6 +59,7 @@ export async function mapProviderToProfile(
return (
await finishMapPreparation(resultUrl, {
client,
userError,
})
).source;
}
Expand All @@ -78,9 +80,8 @@ async function startMapPreparation(
},
{ client, userError }: { client: ServiceClient; userError: UserError }
): Promise<string> {
const profileId = `${profile.scope !== undefined ? profile.scope + '.' : ''}${
profile.name
}`;
const profileId = `${profile.scope !== undefined ? profile.scope + '.' : ''}${profile.name
}`;
const jobUrlResponse = await client.fetch(
`/authoring/profiles/${profileId}/maps`,
{
Expand Down Expand Up @@ -127,7 +128,7 @@ async function startMapPreparation(

async function finishMapPreparation(
resultUrl: string,
{ client }: { client: ServiceClient }
{ client, userError }: { client: ServiceClient, userError: UserError }
): Promise<MapPreparationResponse> {
const resultResponse = await client.fetch(resultUrl, {
method: 'GET',
Expand All @@ -139,12 +140,12 @@ async function finishMapPreparation(
});

if (resultResponse.status !== 200) {
throw Error(`Unexpected status code ${resultResponse.status} received`);
throw userError(`Unexpected status code ${resultResponse.status} received`, 1);
}

const body = (await resultResponse.json()) as unknown;

assertMapResponse(body);
assertMapResponse(body, { userError });

return body;
}
38 changes: 20 additions & 18 deletions src/logic/new.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ProviderJson } from '@superfaceai/ast';
import { parseDocumentId, parseProfile, Source } from '@superfaceai/parser';
import { parseDocumentId } from '@superfaceai/parser';
import type { ServiceClient } from '@superfaceai/service-client';

import type { UserError } from '../common/error';
Expand All @@ -17,7 +17,8 @@ export type ProfilePreparationResponse = {
};

function assertProfileResponse(
input: unknown
input: unknown,
{ userError }: { userError: UserError }
): asserts input is ProfilePreparationResponse {
if (
typeof input === 'object' &&
Expand All @@ -28,26 +29,26 @@ function assertProfileResponse(
const tmp = input as { id: string; profile: { source?: string } };

if (typeof tmp.profile.source !== 'string') {
throw Error(
throw userError(
`Unexpected response received - missing profile source: ${JSON.stringify(
tmp,
null,
2
)}`
)}`, 1
);
}

try {
parseProfile(new Source(tmp.profile.source));
} catch (e) {
throw Error(
`Unexpected response received - unable to parse profile source: ${JSON.stringify(
e,
null,
2
)}`
);
}
// try {
// parseProfile(new Source(tmp.profile.source));
// } catch (e) {
// throw userError(
// `Unexpected response received - unable to parse profile source: ${JSON.stringify(
// e,
// null,
// 2
// )}`, 1
// );
// }

// TODO: validate id format?
if (typeof tmp.id === 'string') {
Expand Down Expand Up @@ -84,6 +85,7 @@ export async function newProfile(

const profileResponse = await finishProfilePreparation(resultUrl, {
client,
userError,
});

// Supports both . and / in profile id
Expand Down Expand Up @@ -146,7 +148,7 @@ async function startProfilePreparation(

async function finishProfilePreparation(
resultUrl: string,
{ client }: { client: ServiceClient }
{ client, userError }: { client: ServiceClient, userError: UserError }
): Promise<ProfilePreparationResponse> {
const resultResponse = await client.fetch(resultUrl, {
method: 'GET',
Expand All @@ -158,12 +160,12 @@ async function finishProfilePreparation(
});

if (resultResponse.status !== 200) {
throw Error(`Unexpected status code ${resultResponse.status} received`);
throw userError(`Unexpected status code ${resultResponse.status} received`, 1);
}

const body = (await resultResponse.json()) as unknown;

assertProfileResponse(body);
assertProfileResponse(body, { userError });

return body;
}
Loading

0 comments on commit 0094edf

Please sign in to comment.