diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg new file mode 100755 index 0000000..e0297de --- /dev/null +++ b/.git-hooks/commit-msg @@ -0,0 +1,18 @@ +#!/bin/sh +RED="\033[1;31m" +GREEN="\033[1;32m" + +# Get the commit message (the parameter we're given is just the path to the +# temporary file which holds the message). +commit_message=$(cat "$1") + +if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z \-]+\))?!?: .+$") then + echo "${GREEN} ✔ Commit message meets Conventional Commit standards" + exit 0 +fi + +echo "${RED}❌ Commit message does not meet the Conventional Commit standard!" +echo "An example of a valid message is:" +echo " feat(login): add the 'remember me' button" +echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" +exit 1 \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..ddbab98 --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +sasjs-tests/ +docs/ +.github/ +CONTRIBUTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd6756..7680af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +Since March 2020 the changelog is managed by github releases - see [https://github.com/sasjs/adapter/releases](https://github.com/sasjs/adapter/releases). + +## Changes up to 5th March 2020 + All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c93e457..83067b1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,75 +2,127 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email + address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at support@macropeople.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +https://sasapps.io/contact-us. +All complaints will be reviewed and investigated promptly and fairly. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE index 6765f8e..0f743d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Macro People +Copyright (c) 2021 Macro People Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9833c9c..9d267fd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,23 @@ -[![](https://data.jsdelivr.com/v1/package/npm/@sasjs/adapter/badge)](https://www.jsdelivr.com/package/npm/@sasjs/adapter) - # @sasjs/adapter +[![npm package][npm-image]][npm-url] +[![Github Workflow][githubworkflow-image]][githubworkflow-url] +[![Dependency Status][dependency-image]][dependency-url] +[![npm](https://img.shields.io/npm/dt/@sasjs/adapter)]() +![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/adapter) +[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE) +![GitHub top language](https://img.shields.io/github/languages/top/sasjs/adapter) +![GitHub issues](https://img.shields.io/github/issues/sasjs/adapter) +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/sasjs/adapter) + + +[npm-image]:https://img.shields.io/npm/v/@sasjs/adapter.svg +[npm-url]:http://npmjs.org/package/@sasjs/adapter +[githubworkflow-image]:https://github.com/sasjs/adapter/actions/workflows/build.yml/badge.svg +[githubworkflow-url]:https://github.com/sasjs/adapter/blob/main/.github/workflows/build.yml +[dependency-image]:https://david-dm.org/sasjs/adapter.svg +[dependency-url]:https://github.com/sasjs/adapter/blob/main/package.json + SASjs is a open-source framework for building Web Apps on SAS® platforms. You can use as much or as little of it as you like. This repository contains the JS adapter, the part that handles the to/from SAS communication on the client side. There are 3 ways to install it: 1 - `npm install @sasjs/adapter` - for use in a node project @@ -198,8 +214,15 @@ This approach is by far the fastest, as a result of the optimisations we have bu # More resources -For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation. +For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation. For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly. If you are a SAS 9 or SAS Viya customer you can also request a copy of [Data Controller](https://datacontroller.io) - free for up to 5 users, this tool makes use of all parts of the SASjs framework. + + +## Star Gazing + +If you find this library useful, help us grow our star graph! + +![](https://starchart.cc/sasjs/adapter.svg) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 83cbe72..abd0f9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "name": "@sasjs/adapter", + "hasInstallScript": true, "license": "ISC", "dependencies": { "@sasjs/utils": "^2.10.2", diff --git a/package.json b/package.json index f144baa..6754eff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@sasjs/adapter", "description": "JavaScript adapter for SAS", + "homepage": "https://adapter.sasjs.io", "scripts": { "build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node", "package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", @@ -11,7 +12,8 @@ "prepublishOnly": "cp -r ./build/* . && rm -rf ./build", "postpublish": "git clean -fd", "semantic-release": "semantic-release", - "typedoc": "typedoc" + "typedoc": "typedoc", + "postinstall": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true" }, "publishConfig": { "access": "public" diff --git a/sasjs-tests/README.md b/sasjs-tests/README.md index b2698ba..4a7f87b 100644 --- a/sasjs-tests/README.md +++ b/sasjs-tests/README.md @@ -55,6 +55,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; %inc mc; filename ft15f001 temp; parmcards4; + %webout(FETCH) %webout(OPEN) %macro x(); %do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i) %end; @@ -63,6 +64,7 @@ parmcards4; ;;;; %mm_createwebservice(path=/Public/app/common,name=sendObj) parmcards4; + %webout(FETCH) %webout(OPEN) %macro x(); %do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i) %end; diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 8f177ad..72e9fa6 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -1078,6 +1078,7 @@ export class SASViyaApiClient { ) { let POLL_INTERVAL = 300 let MAX_POLL_COUNT = 1000 + let MAX_ERROR_COUNT = 5 if (pollOptions) { POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL @@ -1086,6 +1087,7 @@ export class SASViyaApiClient { let postedJobState = '' let pollCount = 0 + let errorCount = 0 const headers: any = { 'Content-Type': 'application/json', 'If-None-Match': etag @@ -1100,14 +1102,18 @@ export class SASViyaApiClient { const { result: state } = await this.requestClient .get( - `${this.serverUrl}${stateLink.href}?_action=wait&wait=30`, + `${this.serverUrl}${stateLink.href}?_action=wait&wait=300`, accessToken, 'text/plain', {}, this.debug ) .catch((err) => { - throw prefixMessage(err, 'Error while getting job state. ') + console.error( + `Error fetching job state from ${this.serverUrl}${stateLink.href}. Starting poll, assuming job to be running.`, + err + ) + return { result: 'unavailable' } }) const currentState = state.trim() @@ -1122,25 +1128,40 @@ export class SASViyaApiClient { if ( postedJobState === 'running' || postedJobState === '' || - postedJobState === 'pending' + postedJobState === 'pending' || + postedJobState === 'unavailable' ) { if (stateLink) { const { result: jobState } = await this.requestClient .get( - `${this.serverUrl}${stateLink.href}?_action=wait&wait=30`, + `${this.serverUrl}${stateLink.href}?_action=wait&wait=300`, accessToken, 'text/plain', {}, this.debug ) .catch((err) => { - throw prefixMessage( - err, - 'Error while getting job state after interval. ' + errorCount++ + if ( + pollCount >= MAX_POLL_COUNT || + errorCount >= MAX_ERROR_COUNT + ) { + throw prefixMessage( + err, + 'Error while getting job state after interval. ' + ) + } + console.error( + `Error fetching job state from ${this.serverUrl}${stateLink.href}. Resuming poll, assuming job to be running.`, + err ) + return { result: 'unavailable' } }) postedJobState = jobState.trim() + if (postedJobState != 'unavailable' && errorCount > 0) { + errorCount = 0 + } if (this.debug && printedState !== postedJobState) { console.log('Polling job status...') diff --git a/src/job-execution/JesJobExecutor.ts b/src/job-execution/JesJobExecutor.ts index 60cf52f..42d22a4 100644 --- a/src/job-execution/JesJobExecutor.ts +++ b/src/job-execution/JesJobExecutor.ts @@ -33,7 +33,7 @@ export class JesJobExecutor extends BaseJobExecutor { .then((response) => { this.appendRequest(response, sasJob, config.debug) - resolve(response.result) + resolve(response) }) .catch(async (e: Error) => { if (e instanceof JobExecutionError) { diff --git a/src/job-execution/Sas9JobExecutor.ts b/src/job-execution/Sas9JobExecutor.ts index f69f8aa..2c0cae3 100644 --- a/src/job-execution/Sas9JobExecutor.ts +++ b/src/job-execution/Sas9JobExecutor.ts @@ -5,6 +5,12 @@ import { convertToCSV, isRelativePath } from '../utils' import { BaseJobExecutor } from './JobExecutor' import { Sas9RequestClient } from '../request/Sas9RequestClient' +/** + * Job executor for SAS9 servers for use in Node.js environments. + * Initiates login with the provided username and password from the config + * The cookies are stored in the request client and used in subsequent + * job execution requests. + */ export class Sas9JobExecutor extends BaseJobExecutor { private requestClient: Sas9RequestClient constructor( diff --git a/src/request/Sas9RequestClient.ts b/src/request/Sas9RequestClient.ts index 7f0b9f3..4d88d57 100644 --- a/src/request/Sas9RequestClient.ts +++ b/src/request/Sas9RequestClient.ts @@ -4,6 +4,10 @@ import * as tough from 'tough-cookie' import { prefixMessage } from '@sasjs/utils/error' import { RequestClient, throwIfError } from './RequestClient' +/** + * Specific request client for SAS9 in Node.js environments. + * Handles redirects and cookie management. + */ export class Sas9RequestClient extends RequestClient { constructor(baseUrl: string, allowInsecure = false) { super(baseUrl, allowInsecure)