Skip to content

Commit

Permalink
WIP: Add localStorage for selected Project/Domain (#774)
Browse files Browse the repository at this point in the history
* chore: init checkin

Signed-off-by: Jason Porter <[email protected]>

* feat: added support for persisting the last viewed project and domain

Signed-off-by: Jason Porter <[email protected]>

* feat: added a view all link to the project list, still missing styles

Signed-off-by: Jason Porter <[email protected]>

* feat: changed save to explicit project select

Signed-off-by: Jason Porter <[email protected]>

* fix: refactored to use static route for select

Signed-off-by: Jason Porter <[email protected]>

* chore: view all as styled list item

Signed-off-by: Frank Flitton <[email protected]>

* feat: refactored to make localStorage utils

Signed-off-by: Jason Porter <[email protected]>

* chore: purple text and new route

Signed-off-by: Frank Flitton <[email protected]>

* chore: fix null state condition

Signed-off-by: Frank Flitton <[email protected]>

* feat: addressed pr comments and fixed project dropdown select

Signed-off-by: Jason Porter <[email protected]>

* chore: add elipsis

Signed-off-by: Frank Flitton <[email protected]>

---------

Signed-off-by: Jason Porter <[email protected]>
Signed-off-by: Frank Flitton <[email protected]>
Co-authored-by: Frank Flitton <[email protected]>
Co-authored-by: Carina Ursu <[email protected]>
  • Loading branch information
3 people authored Jun 22, 2023
1 parent 4b57956 commit 007e636
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 47 deletions.
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('/'),
};
}

0 comments on commit 007e636

Please sign in to comment.