Skip to content

fix: support for prerequisites #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 28, 2022
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
131 changes: 131 additions & 0 deletions .build/src/app/patternfly-procedure-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// COPIED FROM https://github.com/patternfly/patternfly-quickstarts/blob/main/packages/dev/src/quickstarts-data/asciidoc/procedure-parser.ts until it's published to NPMJS
/* eslint-disable */

import { QuickStart, QuickStartTask } from '@patternfly/quickstarts';

export const ProcQuickStartParser = (
quickStart: QuickStart & {
spec: {
tasks: undefined | QuickStartTask[] | string[];
};
},
environmentVariables?: { [name: string]: string },
) => {
const replaceEnvironmentVariables = (s: string | undefined) =>
s?.replace(/\${(\w+)}/, (substring, name) => {
return environmentVariables ? ([name] ? environmentVariables[name] : substring) : substring;
});

quickStart.spec.tasks = quickStart.spec.tasks?.map((task: QuickStartTask | string, index) => {
let proc: string;
let answer: QuickStartTask;
if (typeof task === 'string') {
proc = task;
answer = {};
} else {
// @ts-ignore
proc = task.proc;
answer = task;
// @ts-ignore
delete task.proc;
}

let description = '',
procedure,
verification,
title,
summaryFailed,
success,
reviewFailed: string | undefined,
prerequisites;
if (proc) {
const taskDOM = document.createElement('div');
taskDOM.innerHTML = proc;

// remove the screencapture images
taskDOM.querySelectorAll('.imageblock.screencapture').forEach((node) => {
node.parentElement?.removeChild(node);
});

title = taskDOM
.querySelector('h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child')
?.innerHTML.trim();
let sectionBody = taskDOM.querySelector('.sectionbody');
if (!sectionBody?.hasChildNodes()) {
// possibly in other templates, where we want to look for article
sectionBody = taskDOM.querySelector('article');
}
if (sectionBody) {
for (let i = 0; i < sectionBody.children.length || 0; i++) {
/**
child typically looks like:

<div class="paragraph|olist|ulist|admonitionblock">
<div class="title">Procedure|Prerequisites|Verification|Note|Warning</div>
<ol|ul class="arabic">
<li>
<li>...
</ol|ul>
</div>

And the below code extracts the <ol> or <ul>
Except for when there is no <div class="title|heading"/>, then the description is extracted
in the else if below
*/
const child = sectionBody.children.item(i);
// find the title
const sectionTitle = child?.querySelector('.heading,.title');
// should this section be assigned to a specific section
const sectionTitleText = sectionTitle?.textContent?.trim();
const isKnownSection = ['Procedure', 'Verification', 'Prerequisites'].includes(
sectionTitle?.textContent?.trim(),
);
if (isKnownSection) {
switch (sectionTitleText) {
case 'Procedure':
procedure = child?.querySelector(':not(.heading):not(.title)')?.outerHTML.trim();
break;
case 'Verification':
verification = child?.querySelector(':not(.heading):not(.title)')?.outerHTML.trim();
break;
case 'Prerequisites':
prerequisites = child
?.querySelector(':not(.heading):not(.title)')
?.outerHTML.trim();
break;
}
} else if (!procedure) {
// Otherwise if it comes before a procedure it's part of the description
description = description + child?.outerHTML.trim();
}
}
}
success = taskDOM.querySelector('.qs-summary.success')?.innerHTML.trim();
reviewFailed = taskDOM.querySelector('.qs-review.failed')?.innerHTML.trim();
summaryFailed = taskDOM.querySelector('.qs-summary.failed')?.innerHTML.trim();
}

answer.title = replaceEnvironmentVariables(answer.title || title);
answer.description = replaceEnvironmentVariables(
answer.description || `${description} ${prerequisites || ''} ${procedure}`,
);
answer.review = answer.review || {};
answer.review.instructions = replaceEnvironmentVariables(
answer.review?.instructions || verification || 'Have you completed these steps?',
);
answer.review.failedTaskHelp = replaceEnvironmentVariables(
answer.review.failedTaskHelp ||
reviewFailed ||
'This task isn’t verified yet. Try the task again.',
);
answer.summary = answer.summary || {};
answer.summary.success = replaceEnvironmentVariables(
answer.summary.success || success || 'You have completed this task!',
);
answer.summary.failed = replaceEnvironmentVariables(
answer.summary.failed || summaryFailed || 'Try the steps again.',
);
return answer;
});
return quickStart;
};
122 changes: 34 additions & 88 deletions .build/src/app/procedure-parser.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,44 @@
import {QuickStart, QuickStartTask} from "@patternfly/quickstarts";
/* eslint-disable */

export type GuidesQuickStart = QuickStart & {
metadata?: {
annotations?: {
draft?: boolean,
order?: number
}
}
spec: {
tasks: undefined | QuickStartTask[] | string[]
}
}
import { QuickStart, QuickStartTask } from '@patternfly/quickstarts';
import {ProcQuickStartParser} from "@app/patternfly-procedure-parser";


export const ProcQuickStartParser = (
quickStart: GuidesQuickStart,
export const ProcQuickStartParserWithImageSupport = (
quickStart: QuickStart & {
spec: {
tasks: undefined | QuickStartTask[] | string[];
};
},
basePath: string,
environmentVariables?: { [name: string]: string }
environmentVariables?: { [name: string]: string },
) => {
const replaceEnvironmentVariables = (s: string | undefined) =>
s?.replace(/\${(\w+)}/, (substring, name) => {
return environmentVariables ? [name]
? environmentVariables[name]
: substring : substring;
});

quickStart.spec.tasks = quickStart.spec.tasks?.map((task: QuickStartTask | string, index) => {
let proc: string;
let answer: QuickStartTask;
if (typeof task === "string") {
proc = task;
answer = {};
} else {
proc = task["proc"]
answer = task;
delete task["proc"];
}
// Use the upstream parser
const resource = ProcQuickStartParser(quickStart, environmentVariables);

let procedure, verification, title, summaryFailed, success, reviewFailed: string | undefined;
let description = "";
if (proc) {
const parser = new DOMParser();
proc = proc.replace("<img src=\"\./images", "<img src=\"" + basePath + "/images");
const taskDOM = parser.parseFromString(proc, 'text/html');
// add image path fixing
function fixImagePath(str: string): string;
function fixImagePath(str: undefined | string): undefined | string;
function fixImagePath (str: string | undefined): string | undefined {
return str === undefined ? undefined :str.replace("<img src=\"\./images", "<img src=\"" + basePath + "/images");
}

// remove the screencapture images
taskDOM.querySelectorAll(".imageblock.screencapture").forEach(node => {
node.parentElement?.removeChild(node);
});
resource.spec.tasks = resource.spec.tasks?.map(task => {

title = taskDOM.querySelector("h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child")?.innerHTML.trim();
let sectionBody = taskDOM.querySelector(".sectionbody");
if (!sectionBody?.hasChildNodes()) {
// possibly in other templates, where we want to look for article
sectionBody = taskDOM.querySelector("article");
}
if (sectionBody) {
for (let i = 0; i < sectionBody.children.length || 0; i++) {
const child = sectionBody.children.item(i);
// find the title
const title = child?.querySelector(".heading,.title");
if (title) {
switch (title?.textContent?.trim()) {
case "Procedure":
procedure = child?.querySelector(":not(.heading):not(.title)")?.outerHTML.trim();
break;
case "Verification":
verification = child?.querySelector(":not(.heading):not(.title)")?.outerHTML.trim();
break;
}
} else if (!procedure) {
// Otherwise if it comes before a procedure it's part of the description
description += child?.innerHTML.trim();
}
}
}
success = taskDOM.querySelector(".qs-summary.success")?.innerHTML.trim();
reviewFailed = taskDOM.querySelector(".qs-review.failed")?.innerHTML.trim();
summaryFailed = taskDOM.querySelector(".qs-summary.failed")?.innerHTML.trim();
task.description = fixImagePath(task.description);
if (task.summary !== undefined) {
task.summary.success = fixImagePath(task.summary.success);
task.summary.failed = fixImagePath(task.summary.failed);
}


answer.title = replaceEnvironmentVariables(answer.title || title)
answer.description = replaceEnvironmentVariables(answer.description || `${description} ${procedure}`);
answer.review = answer.review || {};
answer.review.instructions = replaceEnvironmentVariables(answer.review?.instructions || verification || "Have you completed these steps?")
answer.review.failedTaskHelp = replaceEnvironmentVariables(answer.review.failedTaskHelp || reviewFailed || "This task isn’t verified yet. Try the task again.");
answer.summary = answer.summary || {};
answer.summary.success = replaceEnvironmentVariables(answer.summary.success ||
success
|| "You have completed this task!");
answer.summary.failed = replaceEnvironmentVariables(answer.summary.failed || summaryFailed
|| "Try the steps again.");
return answer;
if (task.review !== undefined) {
task.review.failedTaskHelp = fixImagePath(task.review.failedTaskHelp);
task.review.instructions = fixImagePath(task.review.instructions);
}
task.title = fixImagePath(task.title);
return task;
});
return quickStart;
};
resource.spec.description = fixImagePath(resource.spec.description)
resource.spec.introduction = fixImagePath(resource.spec.introduction)
resource.spec.conclusion = fixImagePath(resource.spec.conclusion);
return resource;
};
4 changes: 2 additions & 2 deletions .build/src/app/quickstartLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GuidesQuickStart, ProcQuickStartParser } from "@app/procedure-parser";
import { GuidesQuickStart, ProcQuickStartParserWithImageSupport } from "@app/procedure-parser";
import React, { useState, useEffect, FunctionComponent } from "react";
import { QuickStart } from "@patternfly/quickstarts";
import {useAssets} from "@rhoas/app-services-ui-shared";
Expand Down Expand Up @@ -35,7 +35,7 @@ export const loadJSONQuickStarts = async (
}
return true;
})
.map((content) => ProcQuickStartParser(content, basePath));
.map((content) => ProcQuickStartParserWithImageSupport(content, basePath));
};

export interface QuickStartLoaderProps {
Expand Down