diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..207b887 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,8 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile + +# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster +ARG VARIANT="18-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} + +RUN apt-get update \ + && apt-get install -y doxygen diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e1f2ab7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/typescript-node +{ + "name": "Node.js & TypeScript", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 18, 16, 14. + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "args": { + "VARIANT": "16-bullseye" + } + }, + // Set *default* container specific settings.json values on container create. + "settings": {}, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "SASjs.sasjs-for-vscode" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "npm i && npm i -g @sasjs/cli", + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node" +} \ No newline at end of file diff --git a/all.sas b/all.sas index 46ad7f6..a7ebd85 100644 --- a/all.sas +++ b/all.sas @@ -5647,6 +5647,11 @@ run; %local vars; %let vars=%upcase(%mf_getvarlist(&libds)); +%if %trim(X&vars)=X %then %do; + %put &sysmacroname: Table &libds has no columns!!; + %return; +%end; + /* create the header row */ data _null_; file &outref; @@ -19485,6 +19490,155 @@ run; options &optval; %mend ms_createfile; +/** + @file + @brief Creates a group on SASjs Server + @details Creates a group on SASjs Server with the following attributes: + + @li name + @li description + @li isActive + + Examples: + + %ms_creategroup(mynewgroup) + + %ms_creategroup(mynewergroup, desc=The group description) + + @param [in] groupname The group name to create. No spaces or special chars. + @param [in] desc= (0) If no description provided, group name will be used. + @param [in] isactive= (true) Set to false to create an inactive group. + @param [in] mdebug= (0) Set to 1 to enable DEBUG messages + @param [out] outds= (work.ms_creategroup) This output dataset will contain the + values from the JSON response (such as the id of the new group) +|DESCRIPTION:$1.|GROUPID:best.|ISACTIVE:best.|NAME:$11.| +|---|---|---|---| +|`The group description`|`2 `|`1 `|`mynewergroup `| + + + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_getuniquelibref.sas + @li mp_abort.sas + +

Related Files

+ @li ms_creategroup.test.sas + @li ms_getgroups.sas + +**/ + +%macro ms_creategroup(groupname + ,desc=0 + ,isactive=true + ,outds=work.ms_creategroup + ,mdebug=0 + ); + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(syscc=&syscc on macro entry) +) + +%local fref0 fref1 fref2 libref optval rc msg; +%let fref0=%mf_getuniquefileref(); +%let fref1=%mf_getuniquefileref(); +%let fref2=%mf_getuniquefileref(); +%let libref=%mf_getuniquelibref(); + +/* avoid sending bom marker to API */ +%let optval=%sysfunc(getoption(bomfile)); +options nobomfile; + +data _null_; + file &fref0 termstr=crlf; + name=quote(cats(symget('groupname'))); + description=quote(cats(symget('desc'))); + if cats(description)='"0"' then description=name; + isactive=symget('isactive'); +%if &mdebug=1 %then %do; + putlog _all_; +%end; + + put '{'@; + put '"name":' name @; + put ',"description":' description @; + put ',"isActive":' isactive @; + put '}'; +run; + +data _null_; + file &fref1 lrecl=1000; + infile "&_sasjs_tokenfile" lrecl=1000; + input; + if _n_=1 then do; + put "Content-Type: application/json"; + put "accept: application/json"; + end; + put _infile_; +run; + +%if &mdebug=1 %then %do; + data _null_; + infile &fref0; + input; + put _infile_; + data _null_; + infile &fref1; + input; + put _infile_; + run; +%end; + +proc http method='POST' in=&fref0 headerin=&fref1 out=&fref2 + url="&_sasjs_apiserverurl/SASjsApi/group"; +%if &mdebug=1 %then %do; + debug level=1; +%end; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(Issue submitting query to SASjsApi/group) +) + +libname &libref JSON fileref=&fref2; + +data &outds; + set &libref..root; + drop ordinal_root; +%if &mdebug=1 %then %do; + putlog _all_; +%end; +run; + + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(Issue reading response JSON) +) + +/* reset options */ +options &optval; + +%if &mdebug=0 %then %do; + filename &fref0 clear; + filename &fref1 clear; + filename &fref2 clear; + libname &libref clear; +%end; +%else %do; + data _null_; + infile &fref2; + input; + putlog _infile_; + run; +%end; + +%mend ms_creategroup; /** @file @brief Creates a user on SASjs Server @@ -20266,6 +20420,111 @@ filename &binaryfref clear; filename &headref clear; %mend ms_getfile;/** + @file + @brief Fetches the list of groups from SASjs Server + @details Fetches the list of groups from SASjs Server and writes them to an + output dataset. + + Example: + + %ms_getgroups(outds=userlist) + + @param [in] mdebug= (0) Set to 1 to enable DEBUG messages + @param [out] outds= (work.ms_getgroups) This output dataset will contain the + list of groups. Format: +|NAME:$32.|DESCRIPTION:$64.|GROUPID:best.| +|---|---|---| +|`SomeGroup `|`A group `|`1`| +|`Another Group`|`this is a different group`|`2`| +|`admin`|`Administrators `|`3`| + + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_getuniquelibref.sas + @li mp_abort.sas + +

Related Files

+ @li ms_creategroup.sas + @li ms_getusers.test.sas + +**/ + +%macro ms_getgroups( + outds=work.ms_getgroups + ,mdebug=0 + ); + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getusers.sas + ,msg=%str(syscc=&syscc on macro entry) +) + +%local fref0 fref1 libref optval rc msg; +%let fref0=%mf_getuniquefileref(); +%let fref1=%mf_getuniquefileref(); +%let libref=%mf_getuniquelibref(); + +/* avoid sending bom marker to API */ +%let optval=%sysfunc(getoption(bomfile)); +options nobomfile; + +data _null_; + file &fref0 lrecl=1000; + infile "&_sasjs_tokenfile" lrecl=1000; + input; + if _n_=1 then put "accept: application/json"; + put _infile_; +run; + +%if &mdebug=1 %then %do; + data _null_; + infile &fref0; + input; + put _infile_; + run; +%end; + +proc http method='GET' headerin=&fref0 out=&fref1 + url="&_sasjs_apiserverurl/SASjsApi/group"; +%if &mdebug=1 %then %do; + debug level=1; +%end; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getgroups.sas + ,msg=%str(Issue submitting GET query to SASjsApi/group) +) + +libname &libref JSON fileref=&fref1; + +data &outds; + length NAME $32 DESCRIPTION $64. GROUPID 8; + if _n_=1 then call missing(of _all_); + set &libref..root; + drop ordinal_root; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getusers.sas + ,msg=%str(Issue reading response JSON) +) + +/* reset options */ +options &optval; + +%if &mdebug=1 %then %do; + filename &fref0 clear; + filename &fref1 clear; + libname &libref clear; +%end; + +%mend ms_getgroups; +/** @file @brief Fetches the list of users from SASjs Server @details Fetches the list of users from SASjs Server and writes them to an diff --git a/base/mp_ds2md.sas b/base/mp_ds2md.sas index a7d5a7d..543c5cc 100644 --- a/base/mp_ds2md.sas +++ b/base/mp_ds2md.sas @@ -57,6 +57,11 @@ %local vars; %let vars=%upcase(%mf_getvarlist(&libds)); +%if %trim(X&vars)=X %then %do; + %put &sysmacroname: Table &libds has no columns!!; + %return; +%end; + /* create the header row */ data _null_; file &outref; diff --git a/server/ms_creategroup.sas b/server/ms_creategroup.sas new file mode 100644 index 0000000..a733ebc --- /dev/null +++ b/server/ms_creategroup.sas @@ -0,0 +1,149 @@ +/** + @file + @brief Creates a group on SASjs Server + @details Creates a group on SASjs Server with the following attributes: + + @li name + @li description + @li isActive + + Examples: + + %ms_creategroup(mynewgroup) + + %ms_creategroup(mynewergroup, desc=The group description) + + @param [in] groupname The group name to create. No spaces or special chars. + @param [in] desc= (0) If no description provided, group name will be used. + @param [in] isactive= (true) Set to false to create an inactive group. + @param [in] mdebug= (0) Set to 1 to enable DEBUG messages + @param [out] outds= (work.ms_creategroup) This output dataset will contain the + values from the JSON response (such as the id of the new group) +|DESCRIPTION:$1.|GROUPID:best.|ISACTIVE:best.|NAME:$11.| +|---|---|---|---| +|`The group description`|`2 `|`1 `|`mynewergroup `| + + + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_getuniquelibref.sas + @li mp_abort.sas + +

Related Files

+ @li ms_creategroup.test.sas + @li ms_getgroups.sas + +**/ + +%macro ms_creategroup(groupname + ,desc=0 + ,isactive=true + ,outds=work.ms_creategroup + ,mdebug=0 + ); + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(syscc=&syscc on macro entry) +) + +%local fref0 fref1 fref2 libref optval rc msg; +%let fref0=%mf_getuniquefileref(); +%let fref1=%mf_getuniquefileref(); +%let fref2=%mf_getuniquefileref(); +%let libref=%mf_getuniquelibref(); + +/* avoid sending bom marker to API */ +%let optval=%sysfunc(getoption(bomfile)); +options nobomfile; + +data _null_; + file &fref0 termstr=crlf; + name=quote(cats(symget('groupname'))); + description=quote(cats(symget('desc'))); + if cats(description)='"0"' then description=name; + isactive=symget('isactive'); +%if &mdebug=1 %then %do; + putlog _all_; +%end; + + put '{'@; + put '"name":' name @; + put ',"description":' description @; + put ',"isActive":' isactive @; + put '}'; +run; + +data _null_; + file &fref1 lrecl=1000; + infile "&_sasjs_tokenfile" lrecl=1000; + input; + if _n_=1 then do; + put "Content-Type: application/json"; + put "accept: application/json"; + end; + put _infile_; +run; + +%if &mdebug=1 %then %do; + data _null_; + infile &fref0; + input; + put _infile_; + data _null_; + infile &fref1; + input; + put _infile_; + run; +%end; + +proc http method='POST' in=&fref0 headerin=&fref1 out=&fref2 + url="&_sasjs_apiserverurl/SASjsApi/group"; +%if &mdebug=1 %then %do; + debug level=1; +%end; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(Issue submitting query to SASjsApi/group) +) + +libname &libref JSON fileref=&fref2; + +data &outds; + set &libref..root; + drop ordinal_root; +%if &mdebug=1 %then %do; + putlog _all_; +%end; +run; + + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_creategroup.sas + ,msg=%str(Issue reading response JSON) +) + +/* reset options */ +options &optval; + +%if &mdebug=0 %then %do; + filename &fref0 clear; + filename &fref1 clear; + filename &fref2 clear; + libname &libref clear; +%end; +%else %do; + data _null_; + infile &fref2; + input; + putlog _infile_; + run; +%end; + +%mend ms_creategroup; diff --git a/server/ms_getgroups.sas b/server/ms_getgroups.sas new file mode 100644 index 0000000..beba852 --- /dev/null +++ b/server/ms_getgroups.sas @@ -0,0 +1,105 @@ +/** + @file + @brief Fetches the list of groups from SASjs Server + @details Fetches the list of groups from SASjs Server and writes them to an + output dataset. + + Example: + + %ms_getgroups(outds=userlist) + + @param [in] mdebug= (0) Set to 1 to enable DEBUG messages + @param [out] outds= (work.ms_getgroups) This output dataset will contain the + list of groups. Format: +|NAME:$32.|DESCRIPTION:$64.|GROUPID:best.| +|---|---|---| +|`SomeGroup `|`A group `|`1`| +|`Another Group`|`this is a different group`|`2`| +|`admin`|`Administrators `|`3`| + + +

SAS Macros

+ @li mf_getuniquefileref.sas + @li mf_getuniquelibref.sas + @li mp_abort.sas + +

Related Files

+ @li ms_creategroup.sas + @li ms_getusers.test.sas + +**/ + +%macro ms_getgroups( + outds=work.ms_getgroups + ,mdebug=0 + ); + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getusers.sas + ,msg=%str(syscc=&syscc on macro entry) +) + +%local fref0 fref1 libref optval rc msg; +%let fref0=%mf_getuniquefileref(); +%let fref1=%mf_getuniquefileref(); +%let libref=%mf_getuniquelibref(); + +/* avoid sending bom marker to API */ +%let optval=%sysfunc(getoption(bomfile)); +options nobomfile; + +data _null_; + file &fref0 lrecl=1000; + infile "&_sasjs_tokenfile" lrecl=1000; + input; + if _n_=1 then put "accept: application/json"; + put _infile_; +run; + +%if &mdebug=1 %then %do; + data _null_; + infile &fref0; + input; + put _infile_; + run; +%end; + +proc http method='GET' headerin=&fref0 out=&fref1 + url="&_sasjs_apiserverurl/SASjsApi/group"; +%if &mdebug=1 %then %do; + debug level=1; +%end; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getgroups.sas + ,msg=%str(Issue submitting GET query to SASjsApi/group) +) + +libname &libref JSON fileref=&fref1; + +data &outds; + length NAME $32 DESCRIPTION $64. GROUPID 8; + if _n_=1 then call missing(of _all_); + set &libref..root; + drop ordinal_root; +run; + +%mp_abort( + iftrue=(&syscc ne 0) + ,mac=ms_getusers.sas + ,msg=%str(Issue reading response JSON) +) + +/* reset options */ +options &optval; + +%if &mdebug=1 %then %do; + filename &fref0 clear; + filename &fref1 clear; + libname &libref clear; +%end; + +%mend ms_getgroups; diff --git a/tests/crossplatform/mp_dirlist.test.sas b/tests/crossplatform/mp_dirlist.test.sas index c6cf883..407c0c2 100644 --- a/tests/crossplatform/mp_dirlist.test.sas +++ b/tests/crossplatform/mp_dirlist.test.sas @@ -21,7 +21,8 @@ %mf_mkdir(&root/a/d) %mf_mkdir(&root/a/e) %mf_mkdir(&root/a/e/f) -data "&root/a/e/f/ds1.sas7bdat"; +libname test "&root/a/e/f"; +data test.ds1; x=1; run; diff --git a/tests/serveronly/ms_creategroup.test.sas b/tests/serveronly/ms_creategroup.test.sas new file mode 100644 index 0000000..cda4f7f --- /dev/null +++ b/tests/serveronly/ms_creategroup.test.sas @@ -0,0 +1,50 @@ +/** + @file + @brief Testing ms_creategroup.sas macro + +

SAS Macros

+ @li mf_getuniquename.sas + @li mp_assert.sas + @li mp_assertscope.sas + @li ms_creategroup.sas + @li ms_getgroups.sas + +**/ + +%let group=%substr(%mf_getuniquename(),1,8); + +%mp_assertscope(SNAPSHOT) +%ms_creategroup(&group, desc=The description,mdebug=&sasjs_mdebug,outds=test1) +%mp_assertscope(COMPARE + ,ignorelist=MCLIB0_JADP1LEN MCLIB0_JADPNUM MCLIB0_JADVLEN +) + +%let id=0; +data _null_; + set work.test1; + call symputx('id',groupid); +run; +%mp_assert( + iftrue=(&id>0), + desc=Checking that group was created with an ID, + outds=work.test_results +) + +/* double check by querying the list of users */ +%ms_getgroups(outds=work.test2) +%let checkid=0; +data _null_; + set work.test2; + where name="&group"; + call symputx('checkid',groupid); +run; +%mp_assert( + iftrue=(&checkid=&id), + desc=Checking that fetched group exists and has the same ID, + outds=work.test_results +) + + + + + diff --git a/tests/serveronly/ms_getgroups.test.sas b/tests/serveronly/ms_getgroups.test.sas new file mode 100644 index 0000000..b0eebcf --- /dev/null +++ b/tests/serveronly/ms_getgroups.test.sas @@ -0,0 +1,23 @@ +/** + @file + @brief Testing ms_getgroups.sas macro + +

SAS Macros

+ @li ms_getgroups.sas + @li mp_assertdsobs.sas + @li mp_assertscope.sas + +**/ + + +%mp_assertscope(SNAPSHOT) +%ms_getgroups(outds=work.test1,mdebug=&sasjs_mdebug) +%mp_assertscope(COMPARE + ,ignorelist=MCLIB0_JADP1LEN MCLIB0_JADPNUM MCLIB0_JADVLEN +) + +%mp_assertdsobs(work.test1,test=ATLEAST 1) + + + +