Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ Sample scripts for each product are located in the directory that shares its nam

Alternatively, you can select from the use cases below:

## Code4z - VS Code extension for the example.com company
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.

## Endevor - Automated Test Facility for Batch Applications
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.

## Endevor - Self-servicing Project Workareas in Endevor with Dynamic Environments

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.

## Endevor - Shipments for a Single-Destination
Expand Down
4 changes: 4 additions & 0 deletions code4z/example-com-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
node_modules
*.vsix
*.zip
15 changes: 15 additions & 0 deletions code4z/example-com-extension/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
}
]
}
3 changes: 3 additions & 0 deletions code4z/example-com-extension/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
Empty file.
66 changes: 66 additions & 0 deletions code4z/example-com-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# VS Code Extension for the Example.com Company

This is an example of an extension the company `example.com` might want to create to supplement Code4z with custom, company-specific functionality.

## Make Your Custom ISPF Apps Available in VS Code

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.

The following sections outline the steps to take.

### Update Your ISPF Application to Run in Plain TSO

1. Update your ISPF application to accept arguments instead of relying on screen inputs.
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.
1. Test your updated application by running it in TSO without ISPF and check its output.
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.

### Use the Basic-Report Command in This Extension

1. Update the `REXX_EXEC` constant in [basic-report.js](commands/basic-report.js#L6) to point to your application.
1. Start the extension by pressing <kbd>F5</kbd>. This opens a new VS Code window with this extension.
1. In this new VS Code window open the Command Palette by pressing <kbd>F1</kbd>
1. Type `example.com` in the command palette input box.
A list of commands displays.
![Command Palette](command-palette.png)
1. Select the `Basic Report on a Dataset` command.
1. After a short moment an editor with the output of your applications opens.

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:

![Report](report.png)

### Explore the Enhanced-Report

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:

- Input validation
- Error checking
- Storing previous activity in memory
- Adding a progress bar
- Storing the report to a data set and retrieving it from there
- Adding a VS Code Output channel to diagnose issues
- Adding a setting for the location of the REXX exec instead of hard coding it in the extension code

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.

### Build the Extension

To build the extension, run these two commands:

```
# Install development dependencies - typescript, types, and vsce
npm ci
# Package the extension
npm run package
```

### Next Steps

A few ideas about what you might want to try next:

- Store the last 10 user inputs in memory and let the user choose one (in addition to typing a new one).
- Submit a job and retrieve its output instead of running a REXX exec.
- Use the Zowe SDK instead of Zowe CLI (remove run-time dependency).
- Execute the REXX exec over SSH, or submit a job over FTP if you do not have Zowe CLI available.
- Copy the REXX exec to the mainframe before a command runs (in case the REXX does not exist) - to self-deploy the extension.
Binary file added code4z/example-com-extension/command-palette.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions code4z/example-com-extension/commands/basic-report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-nocheck
const vscode = require('vscode');
const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);

const REXX_EXEC = "PUBLIC.REXX(BASIC)";

const simpleReport = context => async () => {
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
const reportUri = vscode.Uri.joinPath(context.globalStorageUri, "report.txt");

const dsn = await vscode.window.showInputBox({
placeHolder: 'Please enter a name of a PDS to inquire'
});

const output = await execRexx(dsn);
await vscode.workspace.fs.writeFile(reportUri, Buffer.from(output, 'utf-8'));

const document = await vscode.workspace.openTextDocument(reportUri);
await vscode.window.showTextDocument(document);
}

async function execRexx(dsn) {
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${REXX_EXEC}' '${dsn}'`]);
return stdout;
}

module.exports = {
simpleReport
}
15 changes: 15 additions & 0 deletions code4z/example-com-extension/commands/basic-report.rexx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* REXX */
parse arg dsn

say "==========================================================="
say "|"
say "| REPORT FROM RUNNING 'LISTDS' MEMBER ON:"
say "|"
say "| " dsn
say "|"
say "==========================================================="
say " "

"LISTDS '"dsn"' MEMBERS"

EXIT
137 changes: 137 additions & 0 deletions code4z/example-com-extension/commands/enhanced-report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const vscode = require('vscode');
const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);

const CONFIG_PREFIX = 'example-com';
const REPORT_EXEC = 'reportExec';

/**
* @type {vscode.ExtensionContext}
*/
let context;

/**
* @type {vscode.LogOutputChannel}
*/
let log;

/**
* @param {vscode.ExtensionContext} ctx
* @param {vscode.LogOutputChannel} lg
*/

function registerReportCommand(ctx, lg) {
context = ctx;
log = lg;
return reportCommand;
}

async function reportCommand() {
await vscode.workspace.fs.createDirectory(context.globalStorageUri);
const exec = vscode.workspace.getConfiguration(`${CONFIG_PREFIX}`).get(REPORT_EXEC);
if (!exec) {
const response = await vscode.window.showInformationMessage("Report exec setting missing, canceling action", 'Open Settings');
if (response == 'Open Settings') {
vscode.commands.executeCommand('workbench.action.openSettings', `${CONFIG_PREFIX}.${REPORT_EXEC}`);
}
return;
}
let arg = await vscode.window.showInputBox({
placeHolder: 'Please enter a name of a PDS to inquire',
value: context.globalState.get('arg'),
validateInput: isValidDsn
});
if (!arg) {
vscode.window.showInformationMessage("No dataset name provided, canceling action");
return;
}
arg = arg.toUpperCase();
context.globalState.update('arg', arg);
const reportFileName = `report_on_${arg.toUpperCase()}.txt`;
const reportFileUri = vscode.Uri.joinPath(context.globalStorageUri, reportFileName);
// log.show();
await vscode.window.withProgress({
title: `Reporting on ${arg}`,
location: vscode.ProgressLocation.Notification,
cancellable: false
}, async (progress, _token) => {
progress.report({ increment: 20, message: `Execuring Report` });
const reportOutputDsn = await executeReport(exec, arg);
if (!reportOutputDsn) {
vscode.window.showInformationMessage("No report output provided");
return;
}
progress.report({ increment: 30, message: `Downloading Report` });
await downloadReport(reportOutputDsn, reportFileUri);

});

const reportUri = vscode.Uri.from({ scheme: ReportProvider.scheme, path: reportFileName });
await openReport(reportUri);
}

/**
* @param {string} exec rexx or clist to execute
* @param {string} arg argument to the report exec
*/

async function executeReport(exec, arg) {
const { stdout, stderr } = await execFile('zowe', ["tso", "issue", "command", `exec '${exec}' '${arg}'`]);
log.info(`Executing REXX exec: ${exec} ${arg}`);
stdout.split("\n").forEach(line => line && log.info(line));
stderr.split("\n").forEach(line => line && log.error(line));
const dsnLine = stdout.split("\n").find(line => line.match(/^DSN=/));
const dsn = dsnLine?.replace('DSN=', '');
return dsn;
}
/**
* @param {string} reportDsn dataset from which to download report
* @param {vscode.Uri} reportUri file uri where to store the downloaded report
*/

async function downloadReport(reportDsn, reportUri) {
const { stdout, stderr } = await execFile('zowe', ["files", "download", "data-set", reportDsn, "-f", reportUri.fsPath]);
log.info(`Downloading report from ${reportDsn} to ${reportUri}`);
stdout.split("\n").forEach(line => line && log.info(line));
stderr.split("\n").forEach(line => line && log.error(line));
}
/**
* @implements {vscode.TextDocumentContentProvider}
*/
class ReportProvider {
static scheme = "com-example+report";
/**
* @param {vscode.Uri} uri
* @param {vscode.CancellationToken} token
*/
async provideTextDocumentContent(uri, token) {
const fileUri = vscode.Uri.joinPath(context.globalStorageUri, uri.path);
const content = await vscode.workspace.fs.readFile(fileUri);
return content.toString();
}
}

/**
* @param {vscode.Uri} reportUri
*/

async function openReport(reportUri) {
const document = await vscode.workspace.openTextDocument(reportUri);
await vscode.window.showTextDocument(document);
}
/**
*
* @param {string} input
*/
function isValidDsn(input) {
const datasetPattern = /^([A-Za-z@#$][0-9A-za-z@#$]{0,7}\.)+([A-Za-z@#$][0-9A-Za-z@#$]{0,7})$/;
if (input.length <= 44 && input.match(datasetPattern)) {
return;
}
return 'Please, enter a valid dataset name';
}

module.exports = {
registerReportCommand,
ReportProvider
}
35 changes: 35 additions & 0 deletions code4z/example-com-extension/commands/enhanced-report.rexx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* REXX */
PARSE ARG dsname
OUTPUT = userid()||'.PUBLIC.JCLOUT(REPORT)'

flower.0 = 0
call prnt "==========================================================="
call prnt "|"
call prnt "| REPORT FROM RUNNING 'LISTDS' MEMBER ON:"
call prnt "|"
call prnt "| " dsname
call prnt "|"
call prnt "==========================================================="
call prnt " "

X = OUTTRAP("memlst.")
"LISTDS '"dsname"' MEMBERS"
X = OUTTRAP("OFF")

"ALLOC FI(SYSUT2) DA('"||OUTPUT||"') SHR REUSE"
"EXECIO 0 DISKW SYSUT2 (OPEN"
"EXECIO * DISKW SYSUT2 (STEM flower."
"EXECIO * DISKW SYSUT2 (STEM memlst."
"EXECIO 0 DISKW SYSUT2 (FINIS"
"FREE FI(SYSUT2)"

SAY "DSN='"||OUTPUT||"'"
EXIT

prnt:
arg text
i = flower.0
i = i+1
flower.i = text
flower.0 = i
return
11 changes: 11 additions & 0 deletions code4z/example-com-extension/commands/open-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const vscode = require('vscode');
const url = 'https://techdocs.broadcom.com/us/en/ca-mainframe-software/devops/code4z/2-0.html';

async function openDocsCommand() {
const docsUrl = vscode.Uri.parse(url);
vscode.env.openExternal(docsUrl);
}

module.exports = {
openDocsCommand
}
34 changes: 34 additions & 0 deletions code4z/example-com-extension/extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const vscode = require('vscode');
const { registerReportCommand, ReportProvider } = require("./commands/enhanced-report");
const { openDocsCommand } = require('./commands/open-docs');
const { simpleReport } = require('./commands/basic-report');

/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
const log = vscode.window.createOutputChannel('Example.com', { log: true });

// Report on Dataset
const reportDisp = vscode.commands.registerCommand('com.example.report.on.dataset', registerReportCommand(context, log));
const provider = new ReportProvider;
const providerRegistration = vscode.workspace.registerTextDocumentContentProvider(ReportProvider.scheme, provider);
context.subscriptions.push(reportDisp, providerRegistration);

// Simple Report
const simpleReportDisp = vscode.commands.registerCommand('com.example.simple.report', simpleReport(context));
context.subscriptions.push(simpleReportDisp);

// Open Docs
const openDocsDisp = vscode.commands.registerCommand('com.example.open.docs', openDocsCommand);
context.subscriptions.push(openDocsDisp);

console.log('Congrats, your com.example extension is now active!');
}

function deactivate() { }

module.exports = {
activate,
deactivate
}
Loading