Skip to content
This repository was archived by the owner on Mar 13, 2023. It is now read-only.
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
3 changes: 3 additions & 0 deletions frontend/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { LoadInitialState} from '../model'
// UI Elements
import { SideBarIcons } from './SideBar';
import TopNavigation from "@awsui/components-react/top-navigation";
import { useQueryClient } from 'react-query';

function regions(selected: any) {
let supportedRegions = [
Expand Down Expand Up @@ -90,6 +91,7 @@ function regions(selected: any) {

export default function Topbar(props: any) {
let username = useState(['identity', 'attributes', 'email']);
const queryClient = useQueryClient();

const defaultRegion = useState(['aws', 'region']) || "DEFAULT";
const region = useState(['app', 'selectedRegion']) || defaultRegion;
Expand All @@ -102,6 +104,7 @@ export default function Topbar(props: any) {
let newRegion = region.detail.id;
setState(['app', 'selectedRegion'], newRegion);
LoadInitialState();
queryClient.invalidateQueries();
}

const profileActions = [
Expand Down
62 changes: 29 additions & 33 deletions frontend/src/old-pages/Clusters/Clusters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import React, { useEffect } from 'react';
import { NavigateFunction, useNavigate, useParams } from "react-router-dom"
import { ListClusters } from '../../model'
import { useState, getState, clearState, setState, isAdmin } from '../../store'
import { useState, clearState, setState, isAdmin } from '../../store'
import { selectCluster } from './util'
import { findFirst } from '../../util'
import { useTranslation } from 'react-i18next';
Expand All @@ -34,7 +34,6 @@ import Details from "./Details";
import { wizardShow } from '../Configure/Configure';
import AddIcon from '@mui/icons-material/Add';


export interface Cluster {
cloudformationStackArn: string,
cloudformationStackStatus: string,
Expand All @@ -44,44 +43,32 @@ export interface Cluster {
version: string
}

async function updateClusterList(navigate: NavigateFunction) {
const selectedClusterName = getState(['app', 'clusters', 'selected']);
const oldStatus = getState(['app', 'clusters', 'selectedStatus']);

try {
const clusterList = await ListClusters();
if(selectedClusterName) {
const selectedCluster = findFirst(clusterList, (c: Cluster) => c.clusterName === selectedClusterName);
if(selectedCluster) {
if(oldStatus !== selectedCluster.clusterStatus) {
setState(['app', 'clusters', 'selectedStatus'], selectedCluster.clusterStatus);
}
if((oldStatus === 'CREATE_IN_PROGRESS' && selectedCluster.clusterStatus === 'CREATE_COMPLETE') || (oldStatus === 'UPDATE_IN_PROGRESS' && selectedCluster.clusterStatus === 'UPDATE_COMPLETE')) {
selectCluster(selectedClusterName, null);
} else if (oldStatus === 'DELETE_IN_PROGRESS') {
clearState(['app', 'clusters', 'selected']);
navigate('/clusters');
}
}
}
} catch (error) {}
}

type ClusterListProps = {
clusters: Cluster[]
}

export function onClustersUpdate(selectedClusterName:string, clusters: Cluster[], oldStatus: string, navigate: NavigateFunction): void {
if(!selectedClusterName) {
return;
}
const selectedCluster = findFirst(clusters, (c: Cluster) => c.clusterName === selectedClusterName);
if(selectedCluster) {
if(oldStatus !== selectedCluster.clusterStatus) {
setState(['app', 'clusters', 'selectedStatus'], selectedCluster.clusterStatus);
}
if (oldStatus === 'DELETE_IN_PROGRESS') {
clearState(['app', 'clusters', 'selected']);
navigate('/clusters');
}
}
}

function ClusterList({ clusters }: ClusterListProps) {
const selectedClusterName = useState(['app', 'clusters', 'selected']);
let navigate = useNavigate();
let params = useParams();
const { t } = useTranslation();

React.useEffect(() => {
const timerId = (setInterval(() => updateClusterList(navigate), 5000));
return () => { clearInterval(timerId); }
}, [])

React.useEffect(() => {
if(params.clusterName && selectedClusterName !== params.clusterName)
selectCluster(params.clusterName, navigate);
Expand Down Expand Up @@ -165,10 +152,19 @@ function ClusterList({ clusters }: ClusterListProps) {

export default function Clusters () {
const clusterName = useState(['app', 'clusters', 'selected']);
const cluster = useState(['clusters', 'index', clusterName]);
const [ splitOpen, setSplitOpen ] = React.useState(true);
const { t } = useTranslation();
const { data } = useQuery('LIST_CLUSTERS', () => ListClusters());
const { data } = useQuery('LIST_CLUSTERS', () => ListClusters(), {
refetchInterval: 5000,
});
const selectedClusterName = useState(['app', 'clusters', 'selected']);
const oldStatus = useState(['app', 'clusters', 'selectedStatus']);
let navigate = useNavigate();

useEffect(
() => onClustersUpdate(selectedClusterName, data, oldStatus, navigate),
[selectedClusterName, oldStatus, data, navigate],
);

return (
<AppLayout
Expand Down
53 changes: 44 additions & 9 deletions frontend/src/old-pages/Clusters/__tests__/Clusters.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ThemeProvider } from '@emotion/react'
import { createTheme } from '@mui/material'
import { render, waitFor, screen, prettyDOM } from '@testing-library/react'
import { render, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { I18nextProvider } from 'react-i18next'
import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux'
import { BrowserRouter, useNavigate } from 'react-router-dom'
import { BrowserRouter } from 'react-router-dom'
import i18n from '../../../i18n'
import { ListClusters } from '../../../model'
import { store, isAdmin } from '../../../store'
import Clusters from '../Clusters'
import { store, clearState, setState } from '../../../store'
import Clusters, { onClustersUpdate } from '../Clusters'


const queryClient = new QueryClient();
Expand Down Expand Up @@ -43,21 +43,23 @@ jest.mock('../../../model', () => ({
jest.mock('../../../store', () => ({
...jest.requireActual('../../../store') as any,
isAdmin: () => true,
setState: jest.fn(),
clearState: jest.fn(),
}));

const mockedUseNavigate = jest.fn();
const mockNavigate = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom') as any,
useNavigate: () => mockedUseNavigate,
useNavigate: () => mockNavigate,
}));

describe('given a component to show the clusters list', () => {

describe('when the clusters list is available', () => {
beforeEach(() => {
(ListClusters as jest.Mock).mockResolvedValue(mockClusters);
mockedUseNavigate.mockReset();
mockNavigate.mockReset();
});

it('should render the clusters', async () => {
Expand All @@ -81,7 +83,7 @@ describe('given a component to show the clusters list', () => {
))

await userEvent.click(output.getByRole('radio'))
expect(mockedUseNavigate).toHaveBeenCalledWith('/clusters/test-cluster')
expect(mockNavigate).toHaveBeenCalledWith('/clusters/test-cluster')
})
})

Expand All @@ -94,7 +96,7 @@ describe('given a component to show the clusters list', () => {
))

await userEvent.click(output.getByText('Create Cluster'))
expect(mockedUseNavigate).toHaveBeenCalledWith('/configure')
expect(mockNavigate).toHaveBeenCalledWith('/configure')
})
})
})
Expand All @@ -115,3 +117,36 @@ describe('given a component to show the clusters list', () => {
})
})
})

describe("Given a list of clusters", () => {
beforeEach(() => jest.resetAllMocks());
describe("when a cluster is selected and the list is updated", () => {
describe("when the cluster has a new status", () => {
it("should be saved", () => {
onClustersUpdate("test-cluster", mockClusters, "CREATE_IN_PROGRESS", mockNavigate);

expect(setState).toHaveBeenCalledWith(['app', 'clusters', 'selectedStatus'], "CREATE_COMPLETE");
});
});

describe("when the cluster has the same status", () => {
it("should not be updated", () => {
onClustersUpdate("test-cluster", mockClusters, "CREATE_COMPLETE", mockNavigate);

expect(setState).not.toHaveBeenCalled();
});
});

describe("when a cluster is deleted", () => {
beforeEach(() => {
onClustersUpdate("test-cluster", mockClusters, "DELETE_IN_PROGRESS", mockNavigate);
});
it("should become unselected", () => {
expect(clearState).toHaveBeenCalledWith(['app', 'clusters', 'selected']);
});
it("should navigate to the clusters list", () => {
expect(mockNavigate).toHaveBeenCalledWith('/clusters');
});
});
})
});