Skip to content

Commit 72aae58

Browse files
authored
Merge pull request #131 from BroadcomMFD/code4z-example
Code4z example
2 parents ecd7a3b + 0d812ac commit 72aae58

18 files changed

+2050
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ Sample scripts for each product are located in the directory that shares its nam
88

99
Alternatively, you can select from the use cases below:
1010

11+
## Code4z - VS Code extension for the example.com company
12+
This is an [example](code4z/example-com-extension) of an extension that a fictitious company `example.com` might want to create to supplement Code4z with custom, company-specific functionality. It shows how to make the functionality of custom in-house ISPF applications available in VS Code.
13+
1114
## Endevor - Automated Test Facility for Batch Applications
1215
This [sample repository](endevor/Automated-Test-Facility-for-Batch-Applications) contains artifacts described in the [How to Leverage Endevor Processors to Test Batch Applications](https://medium.com/modern-mainframe/how-to-leverage-endevor-processors-to-test-batch-applications-6247a9dfdafa) blog on Medium. The objects are for using Endevor processors in Building an Automated Test Facility for Batch Applications in Endevor.
1316

1417
## Endevor - Self-servicing Project Workareas in Endevor with Dynamic Environments
18+
1519
This [sample repository](endevor/Self-servicing-Project-Workareas-in-Endevor-with-Dynamic-Environments) contains artifacts described in the [Self-servicing Project Workareas in Endevor with Dynamic Environments](https://medium.com/modern-mainframe/self-service-developer-workspaces-in-endevor-3b83c72bdc14) blog on Medium. The objects are sample processors for enabling self service with Dynamic Environments backed by Deferred File Creation.
1620

1721
## Endevor - Shipments for a Single-Destination
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
node_modules
3+
*.vsix
4+
*.zip
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Run Extension",
9+
"type": "extensionHost",
10+
"request": "launch",
11+
"runtimeExecutable": "${execPath}",
12+
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
13+
}
14+
]
15+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"editor.formatOnSave": true
3+
}

code4z/example-com-extension/LICENSE

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# VS Code Extension for the Example.com Company
2+
3+
This is an example of an extension the company `example.com` might want to create to supplement Code4z with custom, company-specific functionality.
4+
5+
## Make Your Custom ISPF Apps Available in VS Code
6+
7+
If you have custom ISPF panels for your internal processes, you can make their functionality available in VS Code as part of your DevOps modernization.
8+
9+
The following sections outline the steps to take.
10+
11+
### Update Your ISPF Application to Run in Plain TSO
12+
13+
1. Update your ISPF application to accept arguments instead of relying on screen inputs.
14+
1. Update the application to store its output to a dataset, or print it to the terminal instead of showing the result on an ISPF screen.
15+
1. Test your updated application by running it in TSO without ISPF and check its output.
16+
1. Run your updated application through Zowe CLI. Issue the following command: `zowe tso issue command "exec 'PUBLIC.REXX(REPOUT)' 'ARG1 ARG2'"`, where `PUBLIC.REXX(REPOUT)` is your REXX application and `ARG1` `ARG2` are the arguments that are passed to it. The syntax above works both in Windows CMD and PowerShell as well as in Bourne compatible UNIX shells.
17+
18+
### Use the Basic-Report Command in This Extension
19+
20+
1. Update the `REXX_EXEC` constant in [basic-report.js](commands/basic-report.js#L6) to point to your application.
21+
1. Start the extension by pressing <kbd>F5</kbd>. This opens a new VS Code window with this extension.
22+
1. In this new VS Code window open the Command Palette by pressing <kbd>F1</kbd>
23+
1. Type `example.com` in the command palette input box.
24+
A list of commands displays.
25+
![Command Palette](command-palette.png)
26+
1. Select the `Basic Report on a Dataset` command.
27+
1. After a short moment an editor with the output of your applications opens.
28+
29+
To try this out with a basic REXX program, you can use the included [basic-report.rexx](commands/basic-report.rexx) sample. A successful output report looks like this:
30+
31+
![Report](report.png)
32+
33+
### Explore the Enhanced-Report
34+
35+
The [basic report](commands/basic-report.js) is only 30 lines long. It is as simple as possible to get started quickly. To complete the extension there is a lot more to do. For example:
36+
37+
- Input validation
38+
- Error checking
39+
- Storing previous activity in memory
40+
- Adding a progress bar
41+
- Storing the report to a data set and retrieving it from there
42+
- Adding a VS Code Output channel to diagnose issues
43+
- Adding a setting for the location of the REXX exec instead of hard coding it in the extension code
44+
45+
All of these enhancements have been added to the [enhanced report](commands/enhanced-report.js) with its corresponding [enhanced-report.rexx](commands/enhanced-report.rexx) REXX exec. This adds a little over 100 lines of code and illustrates many other useful VS Code APIs. It also adds typescript checking via [JS Doc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) annotations to help you catch errors while authoring the code rather than at runtime.
46+
47+
### Build the Extension
48+
49+
To build the extension, run these two commands:
50+
51+
```
52+
# Install development dependencies - typescript, types, and vsce
53+
npm ci
54+
# Package the extension
55+
npm run package
56+
```
57+
58+
### Next Steps
59+
60+
A few ideas about what you might want to try next:
61+
62+
- Store the last 10 user inputs in memory and let the user choose one (in addition to typing a new one).
63+
- Submit a job and retrieve its output instead of running a REXX exec.
64+
- Use the Zowe SDK instead of Zowe CLI (remove run-time dependency).
65+
- Execute the REXX exec over SSH, or submit a job over FTP if you do not have Zowe CLI available.
66+
- Copy the REXX exec to the mainframe before a command runs (in case the REXX does not exist) - to self-deploy the extension.
62.4 KB
Loading
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// @ts-nocheck
2+
const vscode = require('vscode');
3+
const util = require('node:util');
4+
const execFile = util.promisify(require('node:child_process').execFile);
5+
6+
const REXX_EXEC = "PUBLIC.REXX(BASIC)";
7+
8+
const simpleReport = context => async () => {
9+
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
10+
const reportUri = vscode.Uri.joinPath(context.globalStorageUri, "report.txt");
11+
12+
const dsn = await vscode.window.showInputBox({
13+
placeHolder: 'Please enter a name of a PDS to inquire'
14+
});
15+
16+
const output = await execRexx(dsn);
17+
await vscode.workspace.fs.writeFile(reportUri, Buffer.from(output, 'utf-8'));
18+
19+
const document = await vscode.workspace.openTextDocument(reportUri);
20+
await vscode.window.showTextDocument(document);
21+
}
22+
23+
async function execRexx(dsn) {
24+
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${REXX_EXEC}' '${dsn}'`]);
25+
return stdout;
26+
}
27+
28+
module.exports = {
29+
simpleReport
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* REXX */
2+
parse arg dsn
3+
4+
say "==========================================================="
5+
say "|"
6+
say "| REPORT FROM RUNNING 'LISTDS' MEMBER ON:"
7+
say "|"
8+
say "| " dsn
9+
say "|"
10+
say "==========================================================="
11+
say " "
12+
13+
"LISTDS '"dsn"' MEMBERS"
14+
15+
EXIT
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const vscode = require('vscode');
2+
const util = require('node:util');
3+
const execFile = util.promisify(require('node:child_process').execFile);
4+
5+
const CONFIG_PREFIX = 'example-com';
6+
const REPORT_EXEC = 'reportExec';
7+
8+
/**
9+
* @type {vscode.ExtensionContext}
10+
*/
11+
let context;
12+
13+
/**
14+
* @type {vscode.LogOutputChannel}
15+
*/
16+
let log;
17+
18+
/**
19+
* @param {vscode.ExtensionContext} ctx
20+
* @param {vscode.LogOutputChannel} lg
21+
*/
22+
23+
function registerReportCommand(ctx, lg) {
24+
context = ctx;
25+
log = lg;
26+
return reportCommand;
27+
}
28+
29+
async function reportCommand() {
30+
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
31+
const exec = vscode.workspace.getConfiguration(`${CONFIG_PREFIX}`).get(REPORT_EXEC);
32+
if (!exec) {
33+
const response = await vscode.window.showInformationMessage("Report exec setting missing, canceling action", 'Open Settings');
34+
if (response == 'Open Settings') {
35+
vscode.commands.executeCommand('workbench.action.openSettings', `${CONFIG_PREFIX}.${REPORT_EXEC}`);
36+
}
37+
return;
38+
}
39+
let arg = await vscode.window.showInputBox({
40+
placeHolder: 'Please enter a name of a PDS to inquire',
41+
value: context.globalState.get('arg'),
42+
validateInput: isValidDsn
43+
});
44+
if (!arg) {
45+
vscode.window.showInformationMessage("No dataset name provided, canceling action");
46+
return;
47+
}
48+
arg = arg.toUpperCase();
49+
context.globalState.update('arg', arg);
50+
const reportFileName = `report_on_${arg.toUpperCase()}.txt`;
51+
const reportFileUri = vscode.Uri.joinPath(context.globalStorageUri, reportFileName);
52+
// log.show();
53+
await vscode.window.withProgress({
54+
title: `Reporting on ${arg}`,
55+
location: vscode.ProgressLocation.Notification,
56+
cancellable: false
57+
}, async (progress, _token) => {
58+
progress.report({ increment: 20, message: `Execuring Report` });
59+
const reportOutputDsn = await executeReport(exec, arg);
60+
if (!reportOutputDsn) {
61+
vscode.window.showInformationMessage("No report output provided");
62+
return;
63+
}
64+
progress.report({ increment: 30, message: `Downloading Report` });
65+
await downloadReport(reportOutputDsn, reportFileUri);
66+
67+
});
68+
69+
const reportUri = vscode.Uri.from({ scheme: ReportProvider.scheme, path: reportFileName });
70+
await openReport(reportUri);
71+
}
72+
73+
/**
74+
* @param {string} exec rexx or clist to execute
75+
* @param {string} arg argument to the report exec
76+
*/
77+
78+
async function executeReport(exec, arg) {
79+
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${exec}' '${arg}'`]);
80+
log.info(`Executing REXX exec: ${exec} ${arg}`);
81+
stdout.split("\n").forEach(line => line && log.info(line));
82+
stderr.split("\n").forEach(line => line && log.error(line));
83+
const dsnLine = stdout.split("\n").find(line => line.match(/^DSN=/));
84+
const dsn = dsnLine?.replace('DSN=', '');
85+
return dsn;
86+
}
87+
/**
88+
* @param {string} reportDsn dataset from which to download report
89+
* @param {vscode.Uri} reportUri file uri where to store the downloaded report
90+
*/
91+
92+
async function downloadReport(reportDsn, reportUri) {
93+
const { stdout, stderr } = await execFile('zowe', ["files", "download", "data-set", reportDsn, "-f", reportUri.fsPath]);
94+
log.info(`Downloading report from ${reportDsn} to ${reportUri}`);
95+
stdout.split("\n").forEach(line => line && log.info(line));
96+
stderr.split("\n").forEach(line => line && log.error(line));
97+
}
98+
/**
99+
* @implements {vscode.TextDocumentContentProvider}
100+
*/
101+
class ReportProvider {
102+
static scheme = "com-example+report";
103+
/**
104+
* @param {vscode.Uri} uri
105+
* @param {vscode.CancellationToken} token
106+
*/
107+
async provideTextDocumentContent(uri, token) {
108+
const fileUri = vscode.Uri.joinPath(context.globalStorageUri, uri.path);
109+
const content = await vscode.workspace.fs.readFile(fileUri);
110+
return content.toString();
111+
}
112+
}
113+
114+
/**
115+
* @param {vscode.Uri} reportUri
116+
*/
117+
118+
async function openReport(reportUri) {
119+
const document = await vscode.workspace.openTextDocument(reportUri);
120+
await vscode.window.showTextDocument(document);
121+
}
122+
/**
123+
*
124+
* @param {string} input
125+
*/
126+
function isValidDsn(input) {
127+
const datasetPattern = /^([A-Za-z@#$][0-9A-za-z@#$]{0,7}\.)+([A-Za-z@#$][0-9A-Za-z@#$]{0,7})$/;
128+
if (input.length <= 44 && input.match(datasetPattern)) {
129+
return;
130+
}
131+
return 'Please, enter a valid dataset name';
132+
}
133+
134+
module.exports = {
135+
registerReportCommand,
136+
ReportProvider
137+
}

0 commit comments

Comments
 (0)