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: Add localStorage for selected Project/Domain #774

Merged
merged 13 commits into from
Jun 22, 2023
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
16 changes: 14 additions & 2 deletions packages/console/src/components/Navigation/ProjectNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { matchPath, NavLinkProps, RouteComponentProps } from 'react-router-dom';
import { history } from 'routes/history';
import { Routes } from 'routes/routes';
import { MuiLaunchPlanIcon } from '@flyteorg/ui-atoms';
import {
LOCAL_PROJECT_DOMAIN,
setLocalStore,
} from 'components/common/LocalStoreDefaults';
import { primaryHighlightColor } from 'components/Theme/constants';
import { ProjectSelector } from './ProjectSelector';
import NavLinkWithSearch from './NavLinkWithSearch';
Expand Down Expand Up @@ -70,8 +74,16 @@ const ProjectNavigationImpl: React.FC<ProjectNavigationRouteParams> = ({
const commonStyles = useCommonStyles();
const project = useProject(projectId);
const projects = useProjects();
const onProjectSelected = (project: Project) =>
history.push(Routes.ProjectDetails.makeUrl(project.id, section));
const onProjectSelected = (project: Project) => {
const path = Routes.ProjectDetails.makeUrl(project.id, section);
const projectDomain = {
project: project.id,
domain: domainId || 'development',
};
/* Store user intent in localStorage */
setLocalStore(LOCAL_PROJECT_DOMAIN, projectDomain);
return history.push(path);
};

const routes: ProjectRoute[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ const useStyles = makeStyles((theme: Theme) => ({
top: theme.spacing(expanderGridHeight),
width: '100%',
},
viewProjects: {
display: 'flex',
padding: '.25rem',
justifyContent: 'flex-end',
color: theme.palette.text.primary,
fontSize: theme.typography.body1.fontSize,
textAlign: 'right',
textDecoration: 'none',
},
}));

export interface ProjectSelectorProps {
Expand Down
115 changes: 83 additions & 32 deletions packages/console/src/components/Navigation/SearchableProjectList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fade, Tooltip, Typography } from '@material-ui/core';
import { Box, Fade, Grid, Tooltip, Typography } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import classnames from 'classnames';
import { NoResults } from 'components/common/NoResults';
Expand All @@ -8,6 +8,7 @@ import { defaultProjectDescription } from 'components/SelectProject/constants';
import { primaryHighlightColor } from 'components/Theme/constants';
import { Project } from 'models/Project/types';
import * as React from 'react';
import { Routes } from 'routes';

const useStyles = makeStyles((theme: Theme) => ({
container: {
Expand Down Expand Up @@ -48,40 +49,90 @@ const SearchResults: React.FC<SearchResultsProps> = ({
onProjectSelected,
results,
}) => {
const viewAllProjects = {
id: Routes.SelectProject.id,
name: 'View All Projects',
description: 'View All Projects',
domains: [],
} as Project;

const commonStyles = useCommonStyles();
const styles = useStyles();
return results.length === 0 ? (
<NoResults />
) : (
<ul className={commonStyles.listUnstyled}>
{results.map(({ content, value }) => (
<Tooltip
TransitionComponent={Fade}
key={value.id}
placement="bottom-end"
enterDelay={500}
title={
<Typography variant="body1">
<div className={commonStyles.textMonospace}>{value.id}</div>
<div>
<em>{value.description || defaultProjectDescription}</em>
</div>
</Typography>
}
>
<div
className={styles.searchResult}
onClick={onProjectSelected.bind(null, value)}
>
<div
className={classnames(styles.itemName, commonStyles.textWrapped)}
>
{content}
return (
<>
<Tooltip
TransitionComponent={Fade}
placement="bottom-end"
enterDelay={500}
title={
<Typography variant="body1">
<div className={commonStyles.textMonospace}>
{viewAllProjects.description}
</div>
</div>
</Tooltip>
))}
</ul>
</Typography>
}
>
<Grid
container
justifyContent="space-between"
alignItems="center"
className={styles.searchResult}
onClick={() => onProjectSelected(viewAllProjects)}
>
<Grid item>
<Typography color="primary" className={styles.itemName}>
{viewAllProjects.name}…
</Typography>
</Grid>
</Grid>
</Tooltip>
{!results.length ? (
<NoResults />
) : (
<ul className={commonStyles.listUnstyled}>
<li>
{results.map(({ content, value }) => (
<Tooltip
TransitionComponent={Fade}
key={value.id}
placement="bottom-end"
enterDelay={500}
title={
<Typography variant="body1">
<div className={commonStyles.textMonospace}>{value.id}</div>
<div>
<em>{value.description || defaultProjectDescription}</em>
</div>
</Typography>
}
>
<div
className={styles.searchResult}
onClick={onProjectSelected.bind(null, value)}
>
<div
className={classnames(
styles.itemName,
commonStyles.textWrapped,
)}
>
<Grid
container
justifyContent="space-between"
alignItems="center"
>
<Grid item>
<Box>{content}</Box>
</Grid>
</Grid>
</div>
</div>
</Tooltip>
))}
</li>
</ul>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { makeStyles, Theme } from '@material-ui/core/styles';
import * as React from 'react';
import React from 'react';
import { Typography } from '@material-ui/core';
import {
useTaskNameList,
Expand Down
18 changes: 18 additions & 0 deletions packages/console/src/components/SelectProject/ProjectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import {
} from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { ButtonLink } from 'components/common/ButtonLink';
import {
LocalStorageProjectDomain,
LOCAL_PROJECT_DOMAIN,
setLocalStore,
} from 'components/common/LocalStoreDefaults';
import { useCommonStyles } from 'components/common/styles';
import { Project } from 'models/Project/types';
import * as React from 'react';
Expand Down Expand Up @@ -59,6 +64,19 @@ const ProjectCard: React.FC<{ project: Project }> = ({ project }) => {
color="primary"
key={domainId}
component={ButtonLink}
onClick={() => {
/**
* The last project/domain selected by a user is saved here and used by
* ApplicationRouter to bypass the project select UX when reopening the
* application if this value
* exists
*/
const projectDomain: LocalStorageProjectDomain = {
domain: domainId,
project: project.name,
};
setLocalStore(LOCAL_PROJECT_DOMAIN, projectDomain);
}}
to={Routes.ProjectDetails.sections.dashboard.makeUrl(
project.id,
domainId,
Expand Down
58 changes: 58 additions & 0 deletions packages/console/src/components/common/LocalStoreDefaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export const LOCAL_STORE_DEFAULTS = 'flyteDefaults';
export const LOCAL_PROJECT_DOMAIN = 'projectDomain';

export interface LocalStorageRecord {
[key: string]: any;
}

/**
* Use: skipping project select page is value is present
*/
export type LocalStorageProjectDomain = {
project: string;
domain: string;
};

/**
* Generic localStorage interface
*/
export interface LocalStoreDefaults {
projectDomain?: LocalStorageProjectDomain;
}

/**
* Queries localStorage for flye values. If a query key is provided it will
* check for only that key (and return false otherwise) else return the entire
* JSON
*
* @param key Optional param to return just one key from localStorage
* @returns value or false
*/
export const getLocalStore = (key: string | null = null): any | false => {
const localStoreDefaults = localStorage.getItem(LOCAL_STORE_DEFAULTS);
if (!localStoreDefaults) {
return false;
}
const localJSON = JSON.parse(localStoreDefaults) as LocalStoreDefaults;
if (key) {
if (localJSON[key]) {
return localJSON[key];
} else {
return false;
}
} else {
return localJSON;
}
};

/**
* Sets values to 'flyteDefaults' for use in persisting various user defaults.
*/
export const setLocalStore = (key: string, value: any) => {
const localStoreDefaults = localStorage.getItem(LOCAL_STORE_DEFAULTS) || '{}';
const storeDefaultsJSON = JSON.parse(
localStoreDefaults,
) as LocalStoreDefaults;
storeDefaultsJSON[key] = value;
localStorage.setItem(LOCAL_STORE_DEFAULTS, JSON.stringify(storeDefaultsJSON));
};
34 changes: 32 additions & 2 deletions packages/console/src/routes/ApplicationRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import {
ContentContainerProps,
} from 'components/common/ContentContainer';
import { withSideNavigation } from 'components/Navigation/withSideNavigation';
import * as React from 'react';
import { Route, Switch } from 'react-router-dom';
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { useExternalConfigurationContext } from 'basics/ExternalConfigurationProvider';
import { Toolbar } from '@material-ui/core';
import { styled } from '@material-ui/core/styles';
import { subnavBarContentId } from 'common/constants';
import { subnavBackgroundColor } from 'components/Theme/constants';
import { makeRoute } from '@flyteorg/common';
import {
getLocalStore,
LocalStorageProjectDomain,
LOCAL_PROJECT_DOMAIN,
} from 'components/common/LocalStoreDefaults';
import { components } from './components';
import { Routes } from './routes';

Expand Down Expand Up @@ -47,6 +53,10 @@ export function withContentContainer<P extends {}>(
}

export const ApplicationRouter: React.FC = () => {
const localProjectDomain = getLocalStore(
LOCAL_PROJECT_DOMAIN,
) as LocalStorageProjectDomain;

const additionalRoutes =
useExternalConfigurationContext()?.registry?.additionalRoutes || null;
return (
Expand Down Expand Up @@ -88,6 +98,26 @@ export const ApplicationRouter: React.FC = () => {
exact={true}
component={withContentContainer(components.selectProject)}
/>
<Route
path={makeRoute('/')}
render={() => {
/**
* If LocalStoreDefaults exist, we direct them to the project detail view
* for those values.
*/
if (localProjectDomain) {
return (
<Redirect
to={`${makeRoute('/')}/projects/${
localProjectDomain.project
}/executions?domain=${localProjectDomain.domain}&duration=all`}
/>
);
} else {
return <Redirect to={Routes.SelectProject.path} />;
}
}}
/>
<Route component={withContentContainer(components.notFound)} />
</Switch>
);
Expand Down
27 changes: 17 additions & 10 deletions packages/console/src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,30 @@ export const makeProjectDomainBoundPath = (

export class Routes {
static NotFound = {};

// Landing page
static SelectProject = {
id: '__FLYTE__VIEW_ALL_PROJECTS__',
path: makeRoute('/select-project'),
};

// Projects
static ProjectDetails = {
makeUrl: (project: string, section?: string) =>
makeProjectBoundPath(project, section ? `/${section}` : ''),
makeUrl: (project: string, section?: string) => {
if (project === this.SelectProject.id) {
return this.SelectProject.path;
}
return makeProjectBoundPath(project, section ? `/${section}` : '');
},
path: projectBasePath,
sections: {
dashboard: {
makeUrl: (project: string, domain?: string) =>
makeProjectBoundPath(
makeUrl: (project: string, domain?: string) => {
return makeProjectBoundPath(
project,
`/executions${domain ? `?domain=${domain}` : ''}`,
),
);
},
path: `${projectBasePath}/executions`,
},
tasks: {
Expand Down Expand Up @@ -125,9 +137,4 @@ export class Routes {
makeProjectDomainBoundPath(project, domain, `/executions/${name}`),
path: `${projectDomainBasePath}/executions/:executionId`,
};

// Landing page
static SelectProject = {
path: makeRoute('/'),
};
}