Skip to content

Commit

Permalink
Merge pull request #1089 from equinor/master
Browse files Browse the repository at this point in the history
Release manually scaled components (#1077)
  • Loading branch information
Richard87 authored Sep 16, 2024
2 parents ba01d51 + a2c3630 commit 68e55c3
Show file tree
Hide file tree
Showing 12 changed files with 429 additions and 171 deletions.
4 changes: 2 additions & 2 deletions proxy/server.dev.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ server {
proxy_read_timeout 10;

location /api/ {
# proxy_pass https://server-radix-api-qa.dev.radix.equinor.com;
proxy_pass http://172.19.0.1:3002;
proxy_pass https://server-radix-api-qa.dev.radix.equinor.com;
# proxy_pass http://172.19.0.1:3002;
proxy_set_header Authorization "Bearer $http_x_forwarded_access_token";
proxy_set_header x-forwarded-access-token "";
}
Expand Down
122 changes: 0 additions & 122 deletions src/components/component/toolbar.tsx

This file was deleted.

5 changes: 3 additions & 2 deletions src/components/global-top-nav/styled-toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,16 @@ export function successToast<T>(

export function handlePromiseWithToast<TArgs extends Array<unknown>, TReturn>(
fn: (...args: TArgs) => TReturn,
successContent = 'Saved'
successContent = 'Saved',
errorContent = 'Error while saving'
) {
return async (...args: TArgs): Promise<Awaited<TReturn> | undefined> => {
try {
const ret = await fn(...args);
successToast(successContent);
return ret;
} catch (e) {
errorToast(`Error while saving. ${getFetchErrorMessage(e)}`);
errorToast(`${errorContent}. ${getFetchErrorMessage(e)}`);
return undefined;
}
};
Expand Down
26 changes: 19 additions & 7 deletions src/components/page-active-component/active-component-overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,27 @@ import { getEnvsUrl } from '../../utils/routing';
import AsyncResource from '../async-resource/async-resource';
import { Breadcrumb } from '../breadcrumb';
import { ActiveComponentSecrets } from '../component/secrets/active-component-secrets';
import { Toolbar } from '../component/toolbar';
import { EnvironmentVariables } from '../environment-variables';

import { routeWithParams } from '../../utils/string';
import './style.css';
import { ActiveComponentToolbar } from './active-component-toolbar';
import { ComponentStatus } from './component-status';

export const ActiveComponentOverview: FunctionComponent<{
appName: string;
envName: string;
componentName: string;
}> = ({ appName, envName, componentName }) => {
const { data: application, refetch } = useGetApplicationQuery(
const { data: application } = useGetApplicationQuery(
{ appName },
{ skip: !appName, pollingInterval }
);
const { data: environment, ...envState } = useGetEnvironmentQuery(
const {
data: environment,
refetch,
...envState
} = useGetEnvironmentQuery(
{ appName, envName },
{ skip: !appName || !envName, pollingInterval }
);
Expand Down Expand Up @@ -68,14 +73,21 @@ export const ActiveComponentOverview: FunctionComponent<{
<AsyncResource asyncState={envState}>
{component && (
<>
<Toolbar
<ActiveComponentToolbar
component={component}
appName={appName}
envName={envName}
component={component}
startEnabled
stopEnabled
refetch={refetch}
/>

<ComponentStatus
appName={appName}
envName={envName}
componentName={componentName}
refetch={refetch}
component={component}
/>

<Overview
appAlias={appAlias}
dnsAliases={componentDNSAliases}
Expand Down
175 changes: 175 additions & 0 deletions src/components/page-active-component/active-component-toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import {
Button,
CircularProgress,
Dialog,
Slider,
Typography,
} from '@equinor/eds-core-react';
import { useState } from 'react';
import {
type Component,
useRestartComponentMutation,
useScaleComponentMutation,
useStopComponentMutation,
} from '../../store/radix-api';
import { handlePromiseWithToast } from '../global-top-nav/styled-toaster';
import './style.css';
import { useDurationInterval } from '../../effects/use-interval';

type Props = {
component: Component;
appName: string;
envName: string;
refetch: () => unknown;
};

export function ActiveComponentToolbar({
component,
appName,
envName,
refetch,
}: Props) {
const [scaleTrigger, scaleState] = useScaleComponentMutation();
const [stopTrigger, stopState] = useStopComponentMutation();
const [restartTrigger, restartState] = useRestartComponentMutation();
const startRefetch = useDurationInterval(2_000, 10_000, refetch);

const isStopped = component?.status === 'Stopped';
const isWorking =
restartState.isLoading ||
scaleState.isLoading ||
stopState.isLoading ||
component?.status === 'Reconciling' ||
component?.status === 'Restarting';

const onStop = handlePromiseWithToast(
async () => {
await stopTrigger({
appName,
envName,
componentName: component.name,
}).unwrap();
startRefetch();
},
'Stopping component',
'Failed to stop component'
);

const onRestart = handlePromiseWithToast(
async () => {
await restartTrigger({
appName,
envName,
componentName: component.name,
}).unwrap();
startRefetch();
},
'Restarting component',
'Failed to restart component'
);

const onScale = handlePromiseWithToast(
async (replicas: number) => {
await scaleTrigger({
appName,
envName,
componentName: component.name,
replicas: replicas.toFixed(),
}).unwrap();
startRefetch();
},
'Scaling component',
'Failed to scale component'
);
return (
<>
<div className="grid grid--gap-small">
<div className="grid grid--gap-small grid--auto-columns">
<ScaleButtonPopup
onScale={onScale}
disabled={scaleState.isLoading}
currentReplicas={
component.replicasOverride ?? component.replicaList.length
}
/>
<Button
disabled={isStopped || stopState.isLoading}
variant="outlined"
onClick={onStop}
>
Stop
</Button>

<Button
disabled={isStopped || restartState.isLoading}
variant="outlined"
onClick={onRestart}
>
Restart
</Button>
{isWorking && <CircularProgress size={32} />}
</div>
</div>
</>
);
}

type ScaleProps = {
disabled: boolean;
currentReplicas: number;
onScale: (replicas: number) => unknown;
};

function ScaleButtonPopup({ disabled, currentReplicas, onScale }: ScaleProps) {
const [replicas, setReplicas] = useState<number | null>(null);
const [visibleScrim, setVisibleScrim] = useState<boolean>(false);
const current = replicas ?? currentReplicas;

const onLocalScale = async () => {
await onScale(current);
setVisibleScrim(false);
setReplicas(null);
};

return (
<div>
<Button onClick={() => setVisibleScrim(true)}>Scale</Button>

<Dialog
title={'Scale Component'}
open={!!visibleScrim}
onClose={() => setVisibleScrim(false)}
isDismissable
style={{ width: '400px' }}
>
<Dialog.Header>
<Dialog.Title>Scale Component</Dialog.Title>
</Dialog.Header>
<Dialog.Content>
<Typography>
This will disable any automatic scaling until manual scaling is
reset.
</Typography>
<Slider
value={current}
min={0}
max={20}
onChange={(_, values) => setReplicas(values[0])}
aria-labelledby="simple-slider"
/>
</Dialog.Content>

<Dialog.Actions>
<div className={'scale-component-popup-actions-wrapper'}>
<Button disabled={disabled} onClick={onLocalScale}>
{current === 0 ? 'Stop component' : `Scale to ${current}`}
</Button>
<Button variant="outlined" onClick={() => setVisibleScrim(false)}>
Cancel
</Button>
</div>
</Dialog.Actions>
</Dialog>
</div>
);
}
Loading

0 comments on commit 68e55c3

Please sign in to comment.