1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 09:24:35 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
Allan Bowe
a006ead205 Merge pull request #46 from sasjs/issue45
fix: jobViaWeb config override
2020-08-23 17:07:47 +02:00
Mihajlo Medjedovic
422c2a1fd5 fix: jobViaWeb config override 2020-08-23 17:04:39 +02:00
Krishna Acondy
0c6409e402 Merge pull request #44 from sasjs/session-expiry-retry
fix(session-expiry-retry): retry job with new session on expiry
2020-08-18 21:45:47 +01:00
Krishna Acondy
68b864cf75 fix(session-expiry): discard and create new session if expired 2020-08-18 21:35:02 +01:00
Krishna Acondy
75a11cdff4 fix(session-expiry-retry): retry job with new session when current session has expired 2020-08-18 20:23:59 +01:00
Allan Bowe
4e2b6d32cc Merge pull request #43 from sasjs/parse-compute-log
fix(log): use compute log directly when available
2020-08-18 18:21:56 +02:00
Krishna Acondy
cd9757b383 Merge branch 'master' into parse-compute-log 2020-08-18 17:01:33 +01:00
Mihajlo Medjedovic
fb727788d0 fix: log capture if job fails, test framework update, added test for log capture 2020-08-18 17:36:25 +02:00
Allan Bowe
35eb6c4935 Merge pull request #40 from sasjs/dependabot/npm_and_yarn/types/jest-26.0.10
chore(deps-dev): bump @types/jest from 26.0.9 to 26.0.10
2020-08-18 13:26:57 +02:00
Mihajlo Medjedovic
ea0f338b90 Merge branch 'master' into parse-compute-log 2020-08-18 13:26:16 +02:00
Allan Bowe
b6a17b39b9 Merge branch 'master' into dependabot/npm_and_yarn/types/jest-26.0.10 2020-08-18 13:25:28 +02:00
Krishna Acondy
98c9cb78ff fix(log): use compute log as-is when available 2020-08-18 10:05:34 +01:00
dependabot-preview[bot]
c28a8ebf15 chore(deps-dev): bump @types/jest from 26.0.9 to 26.0.10
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.9 to 26.0.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-17 13:19:21 +00:00
9 changed files with 202 additions and 147 deletions

6
package-lock.json generated
View File

@@ -1648,9 +1648,9 @@
}
},
"@types/jest": {
"version": "26.0.9",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.9.tgz",
"integrity": "sha512-k4qFfJ5AUKrWok5KYXp2EPm89b0P/KZpl7Vg4XuOTVVQEhLDBDBU3iBFrjjdgd8fLw96aAtmnwhXHl63bWeBQQ==",
"version": "26.0.10",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.10.tgz",
"integrity": "sha512-i2m0oyh8w/Lum7wWK/YOZJakYF8Mx08UaKA1CtbmFeDquVhAEdA7znacsVSf2hJ1OQ/OfVMGN90pw/AtzF8s/Q==",
"dev": true,
"requires": {
"jest-diff": "^25.2.1",

View File

@@ -37,7 +37,7 @@
"license": "ISC",
"devDependencies": {
"@types/isomorphic-fetch": "0.0.35",
"@types/jest": "^26.0.9",
"@types/jest": "^26.0.10",
"cp": "^0.2.0",
"jest": "^25.5.4",
"path": "^0.12.7",

View File

@@ -1357,9 +1357,9 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
"@sasjs/adapter": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.2.0.tgz",
"integrity": "sha512-PcQcmb7TsfPJ94tzFnvycm+tMYD3wKx2a6niwHfsV9+g6XHtmwReVV3EPZZ5XB4s565vU6Qc+ZnFbMIAeik8QA==",
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.3.6.tgz",
"integrity": "sha512-d2B+cTII+vabKCU8mJy90mEz3tCWw2pEp4qIBGsDamJiTS0Rx69dgXGHuRUm8KtjLDHHrzwXATsqviU3dnU0QQ==",
"requires": {
"es6-promise": "^4.2.8",
"form-data": "^3.0.0",
@@ -1379,9 +1379,9 @@
}
},
"@sasjs/test-framework": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.3.3.tgz",
"integrity": "sha512-Ou4UXlxBAVR8jv7boVvJ/eKLHRTQvDi9LouPAasLCO2EC4AD0wX1hLMwVhmydCvsdgVEeXs6InvX3ROHiKSADg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.4.0.tgz",
"integrity": "sha512-Pd8PUH5B5RO6q4w3OQXX7aWicvA/CJMXA/FCf2xp332ZTKBb/5uV+HphAOFKpCh58y+ykYYVSV0ZaDO/4t1h3A==",
"requires": {
"@types/react-highlight.js": "^1.0.0",
"immer": "^7.0.7",
@@ -12234,9 +12234,9 @@
}
},
"semantic-ui-react": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.1.1.tgz",
"integrity": "sha512-QtzLNkK4MUe1HQo4S7/tIkSp4NFtxSGDzTMKxmvztMJ6jt+nKGmMyjpyxJsrm3ohU8Z3sTyBUyiBsDYW4jNtjw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.2.0.tgz",
"integrity": "sha512-9tNL94nEy16RdupTQNiURyemWUIxtTpQgFimCbOOHRBOe1ApsFz3FWFsrGjv9zFtE7dQMslLYov9BQOelTCVwA==",
"requires": {
"@babel/runtime": "^7.10.5",
"@semantic-ui-react/event-stack": "^3.1.0",
@@ -12260,9 +12260,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}
}
},

View File

@@ -4,8 +4,8 @@
"homepage": ".",
"private": true,
"dependencies": {
"@sasjs/adapter": "^1.2.0",
"@sasjs/test-framework": "^1.3.3",
"@sasjs/adapter": "^1.3.6",
"@sasjs/test-framework": "^1.4.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",

View File

@@ -21,5 +21,26 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
}
},
},
{
title: "Make error and capture log",
description: "Should make an error and capture log",
test: async () => {
return new Promise( async (resolve, reject) => {
adapter.request("common/makeErr", data)
.then((res) => {
//no action here, this request must throw error
})
.catch((err) => {
let sasRequests = adapter.getSasRequests();
let makeErrRequest = sasRequests.find(req => req.serviceLink.includes('makeErr')) || null;
resolve(!!makeErrRequest);
})
})
},
assertion: (response) => {
return response;
},
},
],
});

View File

@@ -206,137 +206,149 @@ export class SASViyaApiClient {
silent = false,
data = null,
debug = false
) {
): Promise<any> {
silent = !debug;
const headers: any = {
"Content-Type": "application/json",
};
try {
const headers: any = {
"Content-Type": "application/json",
};
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
}
let executionSessionId: string;
const session = await this.sessionManager.getSession(accessToken);
executionSessionId = session!.id;
let executionSessionId: string;
const session = await this.sessionManager.getSession(accessToken);
executionSessionId = session!.id;
const jobArguments: { [key: string]: any } = {
_contextName: contextName,
_OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true,
};
const jobArguments: { [key: string]: any } = {
_contextName: contextName,
_OMITJSONLISTING: true,
_OMITJSONLOG: true,
_OMITSESSIONRESULTS: true,
_OMITTEXTLISTING: true,
_OMITTEXTLOG: true,
};
if (debug) {
jobArguments["_OMITTEXTLOG"] = false;
jobArguments["_OMITSESSIONRESULTS"] = false;
jobArguments["_DEBUG"] = 131;
}
if (debug) {
jobArguments["_OMITTEXTLOG"] = false;
jobArguments["_OMITSESSIONRESULTS"] = false;
jobArguments["_DEBUG"] = 131;
}
const fileName = `exec-${
jobName.includes("/") ? jobName.split("/")[1] : jobName
}`;
const fileName = `exec-${
jobName.includes("/") ? jobName.split("/")[1] : jobName
}`;
let jobVariables: any = {
SYS_JES_JOB_URI: "",
_program: this.rootFolderName + "/" + jobName,
};
let jobVariables: any = {
SYS_JES_JOB_URI: "",
_program: this.rootFolderName + "/" + jobName,
};
let files: any[] = [];
let files: any[] = [];
if (data) {
if (JSON.stringify(data).includes(";")) {
files = await this.uploadTables(data, accessToken);
jobVariables["_webin_file_count"] = files.length;
files.forEach((fileInfo, index) => {
jobVariables[
`_webin_fileuri${index + 1}`
] = `/files/files/${fileInfo.file.id}`;
jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName;
});
if (data) {
if (JSON.stringify(data).includes(";")) {
files = await this.uploadTables(data, accessToken);
jobVariables["_webin_file_count"] = files.length;
files.forEach((fileInfo, index) => {
jobVariables[
`_webin_fileuri${index + 1}`
] = `/files/files/${fileInfo.file.id}`;
jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName;
});
} else {
jobVariables = { ...jobVariables, ...formatDataForRequest(data) };
}
}
// Execute job in session
const postJobRequest = {
method: "POST",
headers,
body: JSON.stringify({
name: fileName,
description: "Powered by SASjs",
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments,
}),
};
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`,
postJobRequest
);
if (!silent) {
console.log(`Job has been submitted for ${fileName}`);
console.log(
`You can monitor the job progress at ${this.serverUrl}${
postedJob.links.find((l: any) => l.rel === "state")!.href
}`
);
}
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
silent
);
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
{ headers }
);
let jobResult, log;
const logLink = currentJob.links.find((l) => l.rel === "log");
if (true && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`,
{
headers,
}
).then((res: any) =>
res.result.items.map((i: any) => i.line).join("\n")
);
}
if (jobStatus === "failed" || jobStatus === "error") {
return Promise.reject({ error: currentJob.error, log: log });
}
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`;
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`,
{ headers },
"text"
).catch((e) => ({
result: JSON.stringify(e),
}));
}
await this.sessionManager.clearSession(executionSessionId, accessToken);
return { result: jobResult?.result, log };
} catch (e) {
if (e && e.status === 404) {
return this.executeScript(
jobName,
linesOfCode,
contextName,
accessToken,
silent,
data,
debug
);
} else {
jobVariables = { ...jobVariables, ...formatDataForRequest(data) };
throw e;
}
}
// Execute job in session
const postJobRequest = {
method: "POST",
headers,
body: JSON.stringify({
name: fileName,
description: "Powered by SASjs",
code: linesOfCode,
variables: jobVariables,
arguments: jobArguments,
}),
};
const { result: postedJob, etag } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`,
postJobRequest
);
if (!silent) {
console.log(`Job has been submitted for ${fileName}`);
console.log(
`You can monitor the job progress at ${this.serverUrl}${
postedJob.links.find((l: any) => l.rel === "state")!.href
}`
);
}
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
silent
);
const { result: currentJob } = await this.request<Job>(
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
{ headers }
);
let jobResult, log;
if (jobStatus === "failed" || jobStatus === "error") {
return Promise.reject(currentJob.error);
}
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`;
const logLink = currentJob.links.find((l) => l.rel === "log");
if (resultLink) {
jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`,
{ headers },
"text"
).catch((e) => ({
result: JSON.stringify(e),
}));
}
if (true && logLink) {
log = await this.request<any>(
`${this.serverUrl}${logLink.href}/content?limit=10000`,
{
headers,
}
).then((res: any) => res.result.items.map((i: any) => i.line).join("\n"));
}
await this.sessionManager.clearSession(executionSessionId, accessToken);
return { result: jobResult?.result, log };
// } else {
// console.error(
// `Unable to find execution context ${contextName}.\nPlease check the contextName in the tgtDeployVars and try again.`
// );
// console.error("Response from server: ", JSON.stringify(this.contexts));
// }
}
/**
@@ -718,12 +730,12 @@ export class SASViyaApiClient {
`The job ${sasJob} was not found in ${this.rootFolderName}`
);
}
let files: any[] = [];
if (data && Object.keys(data).length) {
files = await this.uploadTables(data, accessToken);
}
const jobName = path.basename(sasJob);
const jobFolder = sasJob.replace(`/${jobName}`, "");
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", ""));

View File

@@ -579,8 +579,10 @@ export default class SASjs {
resolve(responseJson);
})
.catch(async (e) => {
if (needsRetry(JSON.stringify(e))) {
.catch(async (response) => {
let error = response.error || response;
if (needsRetry(JSON.stringify(error))) {
if (this.retryCountComputeApi < requestRetryLimit) {
let retryResponse = await this.executeJobViaComputeApi(
sasJob,
@@ -599,15 +601,17 @@ export default class SASjs {
}
}
if (e && e.status === 401) {
if (error && error.status === 401) {
if (loginRequiredCallback) loginRequiredCallback(true);
sasjsWaitingRequest.requestPromise.resolve = resolve;
sasjsWaitingRequest.requestPromise.reject = reject;
sasjsWaitingRequest.config = config;
this.sasjsWaitingRequests.push(sasjsWaitingRequest);
} else {
reject({ MESSAGE: e || "Job execution failed" });
reject({ MESSAGE: error || "Job execution failed" });
}
this.appendSasjsRequest(response.log, sasJob, null);
});
}
);
@@ -726,7 +730,7 @@ export default class SASjs {
}`;
const requestParams = {
...this.getRequestParamsWeb(),
...this.getRequestParamsWeb(config),
};
const formData = new FormData();
@@ -937,14 +941,14 @@ export default class SASjs {
this.sasjsWaitingRequests = [];
}
private getRequestParamsWeb(): any {
private getRequestParamsWeb(config: any): any {
const requestParams: any = {};
if (this.csrfTokenWeb) {
requestParams["_csrf"] = this.csrfTokenWeb.value;
}
if (this.sasjsConfig.debug) {
if (config.debug) {
requestParams["_omittextlog"] = "false";
requestParams["_omitsessionresults"] = "false";
@@ -1067,7 +1071,11 @@ export default class SASjs {
generatedCode = parseGeneratedCode(response.log);
if (this.sasjsConfig.debug) {
sasWork = JSON.parse(parseWeboutResponse(response.result)).WORK;
if (response.log) {
sasWork = response.log;
} else {
sasWork = JSON.parse(parseWeboutResponse(response.result)).WORK;
}
} else {
sasWork = JSON.parse(response.result).WORK;
}

View File

@@ -17,6 +17,16 @@ export class SessionManager {
await this.createSessions(accessToken);
this.createAndWaitForSession(accessToken);
const session = this.sessions.pop();
const secondsSinceSessionCreation =
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
1000;
if (
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
) {
await this.createSessions(accessToken);
const freshSession = this.sessions.pop();
return freshSession;
}
return session;
}

View File

@@ -4,4 +4,8 @@ export interface Session {
id: string;
state: string;
links: Link[];
attributes: {
sessionInactiveTimeout: number;
};
creationTimeStamp: string;
}