Skip to content

Commit

Permalink
Merge pull request galaxyproject#17863 from jmchilton/update-label-ma…
Browse files Browse the repository at this point in the history
…rkdown-editor

Update labels in Markdown editor when workflow labels change.
  • Loading branch information
davelopez authored Apr 2, 2024
2 parents 1de33e4 + 1b2f8b8 commit c253a99
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 70 deletions.
74 changes: 8 additions & 66 deletions client/src/components/Markdown/Markdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,12 @@ import Vue from "vue";
import { useWorkflowStore } from "@/stores/workflowStore";
import { splitMarkdown as splitMarkdownUnrendered } from "./parse";
import MarkdownContainer from "./MarkdownContainer.vue";
import LoadingSpan from "components/LoadingSpan.vue";
import StsDownloadButton from "components/StsDownloadButton.vue";
const FUNCTION_VALUE_REGEX = `\\s*(?:[\\w_\\-]+|\\"[^\\"]+\\"|\\'[^\\']+\\')\\s*`;
const FUNCTION_CALL = `\\s*[\\w\\|]+\\s*=` + FUNCTION_VALUE_REGEX;
const FUNCTION_CALL_LINE = `\\s*(\\w+)\\s*\\(\\s*(?:(${FUNCTION_CALL})(,${FUNCTION_CALL})*)?\\s*\\)\\s*`;
const FUNCTION_CALL_LINE_TEMPLATE = new RegExp(FUNCTION_CALL_LINE, "m");
const mdNewline = markdownItRegexp(/<br>/, () => {
return "<div style='clear:both;'/><br>";
});
Expand Down Expand Up @@ -195,70 +192,15 @@ export default {
}
},
splitMarkdown(markdown) {
const sections = [];
let digest = markdown;
while (digest.length > 0) {
const galaxyStart = digest.indexOf("```galaxy");
if (galaxyStart != -1) {
const galaxyEnd = digest.substr(galaxyStart + 1).indexOf("```");
if (galaxyEnd != -1) {
if (galaxyStart > 0) {
const defaultContent = digest.substr(0, galaxyStart).trim();
if (defaultContent) {
sections.push({
name: "default",
content: md.render(defaultContent),
});
}
}
const galaxyEndIndex = galaxyEnd + 4;
const galaxySection = digest.substr(galaxyStart, galaxyEndIndex);
let args = null;
try {
args = this.getArgs(galaxySection);
sections.push(args);
} catch (e) {
this.markdownErrors.push({
error: "Found an unresolved tag.",
line: galaxySection,
});
}
digest = digest.substr(galaxyStart + galaxyEndIndex);
} else {
digest = digest.substr(galaxyStart + 1);
}
} else {
sections.push({
name: "default",
content: md.render(digest),
});
break;
const { sections, markdownErrors } = splitMarkdownUnrendered(markdown);
markdownErrors.forEach((error) => markdownErrors.push(error));
sections.forEach((section) => {
if (section.name == "default") {
section.content = md.render(section.content);
}
}
});
return sections;
},
getArgs(content) {
const galaxy_function = FUNCTION_CALL_LINE_TEMPLATE.exec(content);
const args = {};
const function_name = galaxy_function[1];
// we need [... ] to return empty string, if regex doesn't match
const function_arguments = [...content.matchAll(new RegExp(FUNCTION_CALL, "g"))];
for (let i = 0; i < function_arguments.length; i++) {
if (function_arguments[i] === undefined) {
continue;
}
const arguments_str = function_arguments[i].toString().replace(/,/g, "").trim();
if (arguments_str) {
const [key, val] = arguments_str.split("=");
args[key.trim()] = val.replace(/['"]+/g, "").trim();
}
}
return {
name: function_name,
args: args,
content: content,
};
},
},
};
</script>
175 changes: 175 additions & 0 deletions client/src/components/Markdown/parse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { getArgs, replaceLabel, splitMarkdown } from "./parse";

describe("parse.ts", () => {
describe("getArgs", () => {
it("parses simple directive expression", () => {
const args = getArgs("job_metrics(job_id=THISFAKEID)");
expect(args.name).toBe("job_metrics");
});

it("parses labels spaces at the end with single quotes", () => {
const args = getArgs("job_metrics(step=' fakestepname ')");
expect(args.name).toBe("job_metrics");
expect(args.args.step).toBe(" fakestepname ");
});

it("parses labels spaces at the end with double quotes", () => {
const args = getArgs('job_metrics(step=" fakestepname ")');
expect(args.name).toBe("job_metrics");
expect(args.args.step).toBe(" fakestepname ");
});
});

describe("splitMarkdown", () => {
it("strip leading whitespace by default", () => {
const { sections } = splitMarkdown("\n```galaxy\njob_metrics(job_id=THISFAKEID)\n```");
expect(sections.length).toBe(1);
});

it("should not strip leading whitespace if disabled", () => {
const { sections } = splitMarkdown("\n```galaxy\njob_metrics(job_id=THISFAKEID)\n```", true);
expect(sections.length).toBe(2);
expect(sections[0].content).toBe("\n");
});

it("should parse labels with leading spaces", () => {
const { sections } = splitMarkdown("\n```galaxy\njob_metrics(step='THISFAKEID ')\n```", true);
expect(sections.length).toBe(2);
expect(sections[0].content).toBe("\n");
});
});

describe("replaceLabel", () => {
it("should leave unaffected markdown alone", () => {
const input = "some random\n`markdown content`\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(result);
});

it("should leave unaffected galaxy directives alone", () => {
const input = "some random\n`markdown content`\n```galaxy\ncurrent_time()\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(result);
});

it("should leave galaxy directives of same type with other labels alone", () => {
const input = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output=moo)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(result);
});

it("should leave galaxy directives of other types with same labels alone", () => {
const input = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(input=from)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(result);
});

it("should swap simple directives of specified type", () => {
const input = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output=from)\n```\n";
const output = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output=to)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

it("should swap single quoted directives of specified type", () => {
const input = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output='from')\n```\n";
const output = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output=to)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

it("should swap single quoted directives of specified type with extra args", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='from', title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=to, title=dog)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

it("should swap double quoted directives of specified type", () => {
const input = 'some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output="from")\n```\n';
const output = "some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(output=to)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

it("should swap double quoted directives of specified type with extra args", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=\"from\", title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=to, title=dog)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

it("should work with double quotes for labels and spaces in the quotes", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=\"from this\", title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=\"to that\", title=dog)\n```\n";
const result = replaceLabel(input, "output", "from this", "to that");
expect(result).toBe(output);
});

it("should work with single quotes for labels and spaces in the quotes", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='from this', title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='to that', title=dog)\n```\n";
const result = replaceLabel(input, "output", "from this", "to that");
expect(result).toBe(output);
});

it("should work with single quotes for labels and spaces in the quotes including end", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='from this ', title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='to thatx ', title=dog)\n```\n";
const result = replaceLabel(input, "output", "from this ", "to thatx ");
expect(result).toBe(output);
});

it("should add quotes when refactoring to labels with spaces", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=fromthis, title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='to that', title=dog)\n```\n";
const result = replaceLabel(input, "output", "fromthis", "to that");
expect(result).toBe(output);
});

it("should add quotes when refactoring to labels with spaces including end space", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output=fromthis, title=dog)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(footer='cow', output='to that ', title=dog)\n```\n";
const result = replaceLabel(input, "output", "fromthis", "to that ");
expect(result).toBe(output);
});

it("should leave non-arguments alone", () => {
const input =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(title='cow from farm', output=from)\n```\n";
const output =
"some random\n`markdown content`\n```galaxy\nhistory_dataset_embedded(title='cow from farm', output=to)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});

// not a valid workflow label per se but make sure we're escaping the regex to be safe
it("should not be messed up by labels containing regexp content", () => {
const input = "```galaxy\nhistory_dataset_embedded(output='from(')\n```\n";
const output = "```galaxy\nhistory_dataset_embedded(output=to$1)\n```\n";
const result = replaceLabel(input, "output", "from(", "to$1");
expect(result).toBe(output);
});

it("should not swallow leading newlines", () => {
const input = "\n```galaxy\nhistory_dataset_embedded(output='from')\n```\n";
const output = "\n```galaxy\nhistory_dataset_embedded(output=to)\n```\n";
const result = replaceLabel(input, "output", "from", "to");
expect(result).toBe(output);
});
});
});
Loading

0 comments on commit c253a99

Please sign in to comment.