Skip to content
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

feat: Quickstart apps using only a URL QueryString parameter #30

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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
18 changes: 17 additions & 1 deletion src/common/services/userapps.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@
*/


import { V1 } from ".";
import {handleError, V1} from ".";
import ReactGA from "react-ga";

export const deepCopy = (obj: any) => {
return JSON.parse(JSON.stringify(obj));
}

export const installUserapp = (appSpec: V1.Service, allSpecs: Array<V1.Service>): Promise<void | V1.Stack> => {
const userApp: V1.Stack = newStack(appSpec, allSpecs);

// POST /stacks
return V1.UserAppService.createUserapp(userApp).then(stk => {
ReactGA.event({
category: 'application',
action: 'add',
label: stk.key
});

return stk;
}).catch(reason => handleError(`Failed to add ${userApp.key} user app`, reason))
}

export const newSpec = (): V1.Service => ({
// Basic Info
key: '',
Expand Down
22 changes: 9 additions & 13 deletions src/views/all-apps/SpecCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import {faPlus} from "@fortawesome/free-solid-svg-icons/faPlus";

// Custom helpers
import Taglist from "./Taglist";
import {newStack, copySpec, CONFLICT_409, handleError, V1} from "../../common/services";
import {copySpec, CONFLICT_409, handleError, V1} from "../../common/services";

import '../../index.css';
import './SpecCard.css';
import {installUserapp} from "../../common/services/userapps.service";


// TODO: Abstract this?
Expand All @@ -47,18 +48,13 @@ function SpecCard(props: CardProps) {

const installApplication = (): void => {
const appSpec = props.spec;
const userApp: V1.Stack = newStack(appSpec, props.specs);

// POST /stacks
V1.UserAppService.createUserapp(userApp).then(stk => {
ReactGA.event({
category: 'application',
action: 'add',
label: stk.key
});
props.stacks.push(stk);
setRedirect(`/my-apps`);
}).catch(reason => handleError(`Failed to add ${userApp.key} user app`, reason));
const allSpecs = props.specs;
installUserapp(appSpec, allSpecs).then(stk => {
if (stk) {
props.stacks.push(stk);
setRedirect(`/my-apps`);
}
});
}

const cloneSpec = (): void => {
Expand Down
18 changes: 6 additions & 12 deletions src/views/all-apps/SpecView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {useSelector} from "react-redux";
import ReactGA from "react-ga";

import './SpecView.css';
import {newStack} from "../../common/services/userapps.service";
import {installUserapp} from "../../common/services/userapps.service";
import {faPlus} from "@fortawesome/free-solid-svg-icons/faPlus";

type SpecViewParams = {
Expand All @@ -36,17 +36,11 @@ function SpecView() {
if (!appSpec) {
return;
}
const userApp: V1.Stack = newStack(appSpec, specs);

// POST /stacks
V1.UserAppService.createUserapp(userApp).then(stk => {
ReactGA.event({
category: 'application',
action: 'add',
label: stk.key
});
setRedirect(`/my-apps`);
}).catch(reason => handleError(`Failed to add ${userApp.key} user app`, reason));
installUserapp(appSpec, specs).then(stk => {
if (stk) {
setRedirect(`/my-apps`);
}
});
}

useEffect(() => {
Expand Down
89 changes: 67 additions & 22 deletions src/views/my-apps/MyApps.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect, useState} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import '../../index.css';
import Accordion from "react-bootstrap/Accordion";
import Button from "react-bootstrap/Button";
Expand Down Expand Up @@ -32,9 +32,13 @@ import {colors} from "../../App";
import ReactGA from "react-ga";

import './MyApps.css';
import {StringParam, useQueryParam} from "use-query-params";
import {installUserapp} from "../../common/services/userapps.service";

const navigate = (stk: V1.Stack, ep: any) => {
window.open(`${ep.url}`, '_blank');
if (ep?.url) {
window.open(ep.url, '_blank');
}
}

const sortBy = (s1: V1.Stack, s2: V1.Stack) => {
Expand All @@ -59,6 +63,9 @@ function MyAppsPage(props: any) {
const env = useSelector((state: any) => state.env);
const user = useSelector((state: any) => state.auth.user);

const [quickstart, setQuickstart] = useQueryParam('quickstart', StringParam);
const [quickStartThread, setQuickStartThread] = useState<any>(undefined);

// Server data
const [stacks, setStacks] = useState<Array<V1.Stack>>([]);
const [specs, setSpecs] = useState<Array<V1.Service>>([]);
Expand Down Expand Up @@ -91,6 +98,25 @@ function MyAppsPage(props: any) {
}
}, [env?.analytics_tracking_id]);

const startStack = useCallback((stack: V1.Stack) => {
const stackId = stack.id + "";
return V1.UserAppService.startStack(stackId)
.catch(reason => handleError("Failed to start stack", reason))
.then((resp) => {
if (!resp) return;

if (env?.auth?.gaTrackingId) {
ReactGA.event({
category: 'application',
action: 'start',
label: stack.key
});
}
console.log("Stack is now starting...");
refresh();
});
}, [env]);

useEffect(() => {
const transient = stacks.filter(stk => stk?.status?.endsWith('ing'));
if (transient.length && !autoRefresh) {
Expand All @@ -108,7 +134,45 @@ function MyAppsPage(props: any) {
setRefreshInterval(undefined);
}
}
}, [autoRefresh, refreshInterval, stacks, stacks.length]);

// If quickstart queryParam provided, start and navigate to the chosen app
if (quickstart) {
console.log('Quick-starting app: ', quickstart);
const existing = stacks.find(s => s.key === quickstart);

if (existing && existing?.status === 'started') {
const service = existing.services?.find(svc => svc.endpoints?.length);
const endpoint = service?.endpoints?.find(ep => ep.host);
navigate(existing, endpoint);
setQuickstart(undefined);
} else if (existing && existing?.status === 'stopped') {
console.log('Existing app found! Starting existing app: ', existing);
startStack(existing);
} else if (!existing && !quickStartThread) {
const timeout = setTimeout(() => {
// Stack exists for this app, start it up
console.log('Checking all specs to create new app: ', specs);

// Otherwise install a new app and start that up
const appSpec = specs.find(s => s.key === quickstart);
console.log('Found spec to create new app: ', appSpec);

if (!appSpec) {
console.log('No spec found, aborting: ', appSpec);
return;
}
console.log('Installing new app from spec: ', appSpec);
installUserapp(appSpec, specs).then(stk => {
if (stk) {
console.log('Starting new app: ', stk);
startStack(stk);
}
});
}, 3000);
setQuickStartThread(timeout);
}
}
}, [autoRefresh, refreshInterval, stacks, stacks.length, quickstart, setQuickstart, quickStartThread, specs, startStack]);

useEffect(() => {
if (!Object.keys(env).length) return;
Expand Down Expand Up @@ -153,25 +217,6 @@ function MyAppsPage(props: any) {
});
}

const startStack = (stack: V1.Stack) => {
const stackId = stack.id + "";
return V1.UserAppService.startStack(stackId)
.catch(reason => handleError("Failed to start stack", reason))
.then((resp) => {
if (!resp) return;

if (env?.auth?.gaTrackingId) {
ReactGA.event({
category: 'application',
action: 'start',
label: stack.key
});
}
console.log("Stack is now starting...");
refresh();
});
}

const stopStack = (stack: V1.Stack) => {
const stackId = stack.id + "";
return V1.UserAppService.stopStack(stackId)
Expand Down
Loading