Skip to content
This repository has been archived by the owner on Jun 13, 2024. It is now read-only.

Commit

Permalink
feat: added loadable example configs from query string (#273)
Browse files Browse the repository at this point in the history
* feat: added loadable config from query

* feat: load example configs
  • Loading branch information
Jaryt authored Aug 22, 2022
1 parent 9768488 commit 48118a4
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 75 deletions.
78 changes: 7 additions & 71 deletions src/components/atoms/OpenConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { parsers } from '@circleci/circleci-config-sdk';
import { OrbImportManifest } from '@circleci/circleci-config-sdk/dist/src/lib/Orb/types/Orb.types';
import { useRef } from 'react';
import { parse } from 'yaml';
import OpenIcon from '../../icons/ui/OpenIcon';
import { useStoreActions, useStoreState } from '../../state/Hooks';
import {
useConfigParser,
useStoreActions,
useStoreState,
} from '../../state/Hooks';
import { Button } from '../atoms/Button';
import { loadOrb } from '../menus/definitions/OrbDefinitionsMenu';

export const OpenConfig = () => {
const inputFile = useRef<HTMLInputElement>(null);
const config = useStoreState((state) => state.config);
const loadConfig = useStoreActions((actions) => actions.loadConfig);
const parseConfig = useConfigParser();

return (
<>
Expand All @@ -25,73 +26,8 @@ export const OpenConfig = () => {
return;
}

const setConfig = (
yml: string,
orbImports?: Record<string, OrbImportManifest>,
) => {
let parseResult;
try {
parseResult = {
config: parsers.parseConfig(yml, orbImports),
manifests: orbImports,
};
} catch (e) {
parseResult = e as Error;
}
loadConfig(parseResult);
};

e.target.files[0].text().then((yml) => {
const configBlob = parse(yml);

if ('orbs' in configBlob) {
if (!configBlob.orbs) {
setConfig(yml);
return;
}

const orbPromises = Object.entries(configBlob.orbs).map(
([alias, stanza]) => {
const parsedOrb = parsers.parseOrbImport({ [alias]: stanza });

if (!parsedOrb) {
const parseError = new Error(
`Could not parse orb ${alias}`,
);

loadConfig(parseError);
throw parseError;
}

return loadOrb(stanza as string, parsedOrb, alias);
},
);

Promise.all(orbPromises).then((loadedOrbs) => {
const orbImports: Record<string, OrbImportManifest> =
Object.assign(
{},
...loadedOrbs.map(({ orb, manifest, alias }) => {
if (!alias) {
const parseError = new Error(
`Could not parse orb ${orb}, no alias`,
);

loadConfig(parseError);
throw parseError;
}

return {
[alias]: manifest,
};
}),
);

setConfig(yml, orbImports);
});
} else {
setConfig(yml);
}
parseConfig(yml, loadConfig);
});
}}
/>
Expand Down
33 changes: 31 additions & 2 deletions src/components/panes/EditorPane.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
import Editor, { DiffEditor } from '@monaco-editor/react';
import { useEffect, useState } from 'react';
import CopyIcon from '../../icons/ui/CopyIcon';
import { useStoreState } from '../../state/Hooks';
import {
useConfigParser,
useStoreActions,
useStoreState,
} from '../../state/Hooks';
import { version } from '../../version.json';
import { Button } from '../atoms/Button';
import { OpenConfig } from '../atoms/OpenConfig';
import templates from '../../examples';

const EditorPane = (props: any) => {
const config = useStoreState((state) => state.config);
const error = useStoreState((state) => state.errorMessage);
const error = useStoreState((state) => state.configError);
const [example, setExample] = useState<string | undefined>(undefined);
const editingConfig = useStoreState((state) => state.editingConfig);
const loadConfig = useStoreActions((actions) => actions.loadConfig);
const parseConfig = useConfigParser();

useEffect(() => {
const params = new URLSearchParams(window.location.search);

if (params.has('example') && !example) {
const queryConfig = params.get('example');

if (!queryConfig) {
return;
}

setExample(queryConfig);

if (queryConfig in templates) {
const template = templates[queryConfig as keyof typeof templates];

parseConfig(JSON.stringify(template, null, 2), loadConfig);
}
}
}, [example, loadConfig, parseConfig]);

const configYAML = (yml: string) => {
const matchSDKComment = yml?.match('# SDK Version: .*\n');
Expand Down
33 changes: 33 additions & 0 deletions src/examples/blogpost.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": 2.1,
"setup": false,
"jobs": {},
"workflows": {
"build-and-deploy": {
"jobs": [
{
"node/test": {
"run-command": "test:unit",
"pkg-manager": "yarn"
}
},
{
"heroku/deploy-via-git": {
"context": ["cfd-deploy"],
"requires": ["node/test"],
"filters": {
"branches": {
"only": ["main"]
}
},
"app-name": "cfd-sample"
}
}
]
}
},
"orbs": {
"node": "circleci/[email protected]",
"heroku": "circleci/[email protected]"
}
}
9 changes: 9 additions & 0 deletions src/examples/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import blogpost from './blogpost.json';
import readme from './readme.json';

const templates = {
blogpost,
readme,
};

export default templates;
93 changes: 93 additions & 0 deletions src/examples/readme.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"version": 2.1,
"setup": false,
"jobs": {
"build": {
"steps": [
"checkout",
{
"run": {
"command": "yarn build"
}
},
{
"persist_to_workspace": {
"root": "../",
"paths": ["build"]
}
}
],
"docker": [
{
"image": "cimg/node:16.11.1"
}
],
"resource_class": "medium"
},
"test": {
"steps": [
{
"attach_workspace": {
"at": "."
}
},
{
"run": {
"command": "yarn test",
"working_directory": "~/project/build"
}
},
{
"persist_to_workspace": {
"root": ".",
"paths": ["build"]
}
}
],
"docker": [
{
"image": "cimg/node:16.11.1"
}
],
"resource_class": "medium"
},
"deploy": {
"steps": [
{
"attach_workspace": {
"at": "."
}
},
{
"run": {
"command": "yarn deploy",
"working_directory": "~/project/build"
}
}
],
"docker": [
{
"image": "cimg/node:16.11.1"
}
],
"resource_class": "medium"
}
},
"workflows": {
"build-and-test": {
"jobs": [
"build",
{
"test": {
"requires": ["build"]
}
},
{
"deploy": {
"requires": ["test"]
}
}
]
}
}
}
71 changes: 71 additions & 0 deletions src/state/Hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { createTypedHooks } from 'easy-peasy';
import { useEffect, useState } from 'react';
import { StoreActions, StoreModel } from './Store';

import { parse } from 'yaml';
import { OrbImportManifest } from '@circleci/circleci-config-sdk/dist/src/lib/Orb/types/Orb.types';
import { Config, parsers } from '@circleci/circleci-config-sdk';
import { loadOrb } from '../components/menus/definitions/OrbDefinitionsMenu';
const typedHooks = createTypedHooks<StoreModel & StoreActions>();

export const useStoreActions = typedHooks.useStoreActions;
Expand Down Expand Up @@ -32,3 +36,70 @@ export default function useWindowDimensions() {

return windowDimensions;
}
export type CallbackResponse =
| {
config: Config;
manifests: Record<string, OrbImportManifest> | undefined;
}
| Error;

export const parseConfigHook = (
yml: string,
callback: (res: CallbackResponse) => void,
) => {
const setConfig = (
yml: string,
orbImports?: Record<string, OrbImportManifest>,
) => {
let parseResult;
try {
parseResult = {
config: parsers.parseConfig(yml, orbImports),
manifests: orbImports,
};
} catch (e) {
parseResult = e as Error;
}
callback(parseResult);
};

const configBlob = parse(yml);

if ('orbs' in configBlob) {
if (!configBlob.orbs) {
setConfig(yml);
return;
}

const orbPromises = Object.entries(configBlob.orbs).map(
([alias, stanza]) => {
const parsedOrb = parsers.parseOrbImport({ [alias]: stanza });
if (!parsedOrb) {
throw new Error(`Could not parse orb ${alias}`);
}
return loadOrb(stanza as string, parsedOrb, alias);
},
);

Promise.all(orbPromises).then((loadedOrbs) => {
const orbImports: Record<string, OrbImportManifest> = Object.assign(
{},
...loadedOrbs.map(({ orb, manifest, alias }) => {
if (!alias) {
throw new Error(`Could not load orb ${orb}`);
}
return {
[alias]: manifest,
};
}),
);
setConfig(yml, orbImports);
});
} else {
setConfig(yml);
}
};

export const useConfigParser = () => {
return parseConfigHook;
};
6 changes: 4 additions & 2 deletions src/state/Store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export type StoreModel = DefinitionsStoreModel & {
};
/** Currently selected workflow pane index */
selectedWorkflowId: string;
errorMessage?: string;
configError?: string;
};

export type UpdateDiff = {
Expand Down Expand Up @@ -714,12 +714,14 @@ const Actions: StoreActions = {

loadConfig: action((state, payload) => {
if (payload instanceof Error) {
state.errorMessage = payload.message;
state.configError = payload.message;

console.error(payload);
return;
}

state.configError = '';

const config = payload.config;
const nodeWidth = 250; // Make this dynamic
const nodeHeight = 60; // Make this dynamic
Expand Down

0 comments on commit 48118a4

Please sign in to comment.