Skip to content

Commit

Permalink
Added util to update page title and added empty data check on environ…
Browse files Browse the repository at this point in the history
…ment page (#4162)

* Added util to update page title and fixed env page loader

Signed-off-by: Hrishav <[email protected]>

* Fixed issues found while testing

Signed-off-by: Hrishav <[email protected]>

* Fixed UI issues found

Signed-off-by: Hrishav <[email protected]>

---------

Signed-off-by: Hrishav <[email protected]>
Co-authored-by: Saranya Jena <[email protected]>
  • Loading branch information
hrishavjha and Saranya-jena authored Sep 13, 2023
1 parent 7bcbd7c commit ac6ebe4
Show file tree
Hide file tree
Showing 37 changed files with 308 additions and 150 deletions.
2 changes: 1 addition & 1 deletion chaoscenter/web/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AppStoreProvider } from '@context';

export function AppWithAuthentication(): React.ReactElement {
return (
<AppStoreProvider>
<AppStoreProvider updateAppStore={() => void 0}>
<StringsContext.Provider value={{ data: strings as StringsMap }}>
<LitmusAPIProvider config={APIEndpoints}>
<RoutesWithAuthentication />
Expand Down
3 changes: 2 additions & 1 deletion chaoscenter/web/src/components/ProjectCard/ProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ProjectCardProps {
export default function ProjectCard({ data }: ProjectCardProps): React.ReactElement {
const { getString } = useStrings();
const history = useHistory();
const { projectID, currentUserInfo } = useAppStore();
const { projectID, currentUserInfo, updateAppStore } = useAppStore();

const isSelected = projectID === data.projectID;
const collaborators = data.members?.map(member => {
Expand All @@ -27,6 +27,7 @@ export default function ProjectCard({ data }: ProjectCardProps): React.ReactElem

const handleProjectSelect = (): void => {
const projectRole = data.members?.find(member => member.userID === currentUserInfo?.ID)?.role;
updateAppStore({ projectID: data.projectID, projectName: data.name });
setUserDetails({
projectRole,
projectID: data.projectID
Expand Down
2 changes: 1 addition & 1 deletion chaoscenter/web/src/components/SideNav/SideNav.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
}
}

.bottomContainer[data-isRoutePresent='false'] {
.bottomContainer[data-isroutepresent='false'] {
border-top: none;
}

Expand Down
19 changes: 12 additions & 7 deletions chaoscenter/web/src/components/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useLogout, useRouteWithBaseUrl } from '@hooks';
import { useStrings } from '@strings';
import ProjectSelectorController from '@controllers/ProjectSelector';
import NavExpandable from '@components/NavExpandable';
import { getUserDetails } from '@utils';
import { PermissionGroup } from '@models';
import css from './SideNav.module.scss';

interface SidebarLinkProps extends NavLinkProps {
Expand Down Expand Up @@ -59,8 +61,9 @@ export const SIDE_NAV_EXPAND_TIMER = 500;
export default function SideNav(): ReactElement {
const { getString } = useStrings();
const paths = useRouteWithBaseUrl();
const accountScopedPaths = useRouteWithBaseUrl('account');
const { forceLogout } = useLogout();
const { projectRole } = getUserDetails();
const accountScopedPaths = useRouteWithBaseUrl('account');
const collapseByDefault = false;
const [sideNavHovered, setSideNavhovered] = useState<boolean>(false);
const [sideNavExpanded, setSideNavExpanded] = useState<boolean>(!collapseByDefault);
Expand Down Expand Up @@ -109,15 +112,17 @@ export default function SideNav(): ReactElement {
<SidebarLink label={'Environments'} to={paths.toEnvironments()} />
<SidebarLink label={'Resilience Probes'} to={paths.toChaosProbes()} />
<SidebarLink label={'ChaosHubs'} to={paths.toChaosHubs()} />
<NavExpandable title="Project Setup" route={paths.toProjectSetup()}>
<SidebarLink label={'Members'} to={paths.toProjectMembers()} />
<SidebarLink label={'Gitops'} to={paths.toGitops()} />
<SidebarLink label={'Image Registry'} to={paths.toImageRegistry()} />
</NavExpandable>
{projectRole === PermissionGroup.OWNER && (
<NavExpandable title="Project Setup" route={paths.toProjectSetup()}>
<SidebarLink label={'Members'} to={paths.toProjectMembers()} />
<SidebarLink label={'GitOps'} to={paths.toGitops()} />
<SidebarLink label={'Image Registry'} to={paths.toImageRegistry()} />
</NavExpandable>
)}
</Layout.Vertical>
)}
</div>
<Container className={css.bottomContainer} data-isRoutePresent={isPathPresent('settings')}>
<Container className={css.bottomContainer} data-isroutepresent={isPathPresent('settings')}>
{!isPathPresent('settings') && (
<div className={css.iconContainer}>
<Icon className={css.icon} name={'chaos-litmuschaos'} size={200} />
Expand Down
8 changes: 6 additions & 2 deletions chaoscenter/web/src/context/AppStoreContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import { getUserDetails } from '../utils/userDetails';

export interface AppStoreContextProps {
readonly projectID?: string;
readonly projectName?: string;
readonly projectRole?: string;
readonly currentUserInfo?: UserInfo;
readonly matchPath?: string;
readonly renderUrl?: string;
updateAppStore?(
updateAppStore(
data: Partial<
Pick<AppStoreContextProps, 'projectID' | 'projectRole' | 'currentUserInfo' | 'matchPath' | 'renderUrl'>
Pick<
AppStoreContextProps,
'projectID' | 'projectName' | 'projectRole' | 'currentUserInfo' | 'matchPath' | 'renderUrl'
>
>
): void;
}
Expand Down
39 changes: 39 additions & 0 deletions chaoscenter/web/src/controllers/APITokens/APITokens.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { useToaster } from '@harnessio/uicore';
import ApiTokensView from '@views/ApiTokens';
import { useGetApiTokensQuery } from '@api/auth';
import { getUserDetails } from '@utils';

interface APITokensControllerProps {
setApiTokensCount: React.Dispatch<React.SetStateAction<number>>;
}

export default function APITokensController(props: APITokensControllerProps): React.ReactElement {
const { setApiTokensCount } = props;
const { showError } = useToaster();
const { accountID } = getUserDetails();

const {
data: apiTokensData,
isLoading: apiTokensLoading,
refetch: apiTokensRefetch
} = useGetApiTokensQuery(
{ user_id: accountID },
{
onError: error => {
showError(error.error);
},
onSuccess: data => {
setApiTokensCount(data.apiTokens.length);
}
}
);

return (
<ApiTokensView
apiTokensData={apiTokensData}
apiTokensRefetch={apiTokensRefetch}
getApiTokensQueryLoading={apiTokensLoading}
/>
);
}
3 changes: 3 additions & 0 deletions chaoscenter/web/src/controllers/APITokens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import APITokensController from './APITokens';

export default APITokensController;
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import {
useGetOwnerProjectsQuery,
useListInvitationsQuery,
useAcceptInvitationMutation,
GetUserWithProjectOkResponse,
useGetApiTokensQuery
GetUserWithProjectOkResponse
} from '@api/auth';
import UserCreatedProjectsView from '@views/UserCreatedProjects';
import ProjectsJoinedView from '@views/ProjectsJoined';
import ProjectInvitationsView from '@views/ProjectInvitations';
import { InvitationState } from '@models';
import { useAppStore } from '@context';
import ApiTokensView from '@views/ApiTokens';

interface AccountSettingsOverviewProjectsControllerProps {
getUserWithProjectsRefetch: <TPageData>(
Expand All @@ -23,17 +20,11 @@ interface AccountSettingsOverviewProjectsControllerProps {
export default function AccountSettingsOverviewProjectsController({
getUserWithProjectsRefetch
}: AccountSettingsOverviewProjectsControllerProps): React.ReactElement {
const { currentUserInfo } = useAppStore();
const {
data: ownerProjectsData,
isLoading: getOwnerProjectsLoading,
refetch: getProjectDataRefetch
} = useGetOwnerProjectsQuery({});
const {
data: apiTokensData,
isLoading: apiTokensLoading,
refetch: apiTokensRefetch
} = useGetApiTokensQuery({ user_id: currentUserInfo?.ID ?? '' });
const {
data: projectsJoinedData,
isLoading: projectsJoinedLoading,
Expand Down Expand Up @@ -62,11 +53,6 @@ export default function AccountSettingsOverviewProjectsController({
useGetOwnerProjectsQuery={getOwnerProjectsLoading}
getProjectDataRefetch={getProjectDataRefetch}
/>
<ApiTokensView
apiTokensData={apiTokensData}
useGetApiTokensQuery={apiTokensLoading}
apiTokensRefetch={apiTokensRefetch}
/>
<ProjectsJoinedView
joinedProjects={projectsJoinedData}
useGetUserWithProjectQueryLoading={projectsJoinedLoading}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import type { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
import { useToaster } from '@harnessio/uicore';
import { GetApiTokensOkResponse, useCreateApiTokenMutation } from '@api/auth';
import { ErrorModel, GetApiTokensResponse, useCreateApiTokenMutation } from '@api/auth';
import { useStrings } from '@strings';
import CreateNewTokenView from '@views/CreateNewToken';

interface CreateNewTokenControllerProps {
apiTokensRefetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<GetApiTokensOkResponse, unknown>>;
) => Promise<QueryObserverResult<GetApiTokensResponse, ErrorModel>>;
handleClose: () => void;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
import React from 'react';
import { GetApiTokensOkResponse, useRemoveApiTokenMutation } from '@api/auth';
import { ErrorModel, GetApiTokensResponse, useRemoveApiTokenMutation } from '@api/auth';
import DeleteApiTokenView from '@views/DeleteApiToken';

interface DeleteApiTokenControllerProps {
token: string | undefined;
apiTokensRefetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<GetApiTokensOkResponse, unknown>>;
) => Promise<QueryObserverResult<GetApiTokensResponse, ErrorModel>>;
handleClose: () => void;
}

Expand Down
2 changes: 1 addition & 1 deletion chaoscenter/web/src/controllers/InviteNewMembers/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface InviteUserDetails {
username: string;
id: string;
userID: string;
email: string;
name: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import React from 'react';
import { useParams } from 'react-router-dom';
import ProjectSelectorView from '@views/ProjectSelector';
import { useGetProjectQuery } from '@api/auth';
import { useAppStore } from '@context';

export default function ProjectSelectorController(): React.ReactElement {
const { projectID } = useParams<{ projectID: string }>();
const { updateAppStore } = useAppStore();

const { data: projectData } = useGetProjectQuery(
{
project_id: projectID
},
{
enabled: !!projectID
enabled: !!projectID,
onSuccess: data => {
updateAppStore({
projectName: data.data?.name
});
}
}
);

Expand Down
2 changes: 2 additions & 0 deletions chaoscenter/web/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export * from './useRouteDefinitionsMatch';
export * from './useRouteWithBaseUrl';
export * from './useLogout';
export * from './useTrackedRef';
export * from './useDocumentTitle';
export * from './useDeepCompareEffect';
42 changes: 42 additions & 0 deletions chaoscenter/web/src/hooks/useDeepCompareEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { isEqualWith } from 'lodash-es';

/**
* Custom version of isEqual to handle function comparison
*/
function isEqual(a: unknown, b: unknown): boolean {
return isEqualWith(a, b, (x: unknown, y: unknown): boolean | undefined => {
// Deal with the function comparison case
if (typeof x === 'function' && typeof y === 'function') {
return x.toString() === y.toString();
}

// Fallback on the method
return undefined;
});
}

function useDeepCompareMemoize(value: React.DependencyList): React.DependencyList | undefined {
const ref = React.useRef<React.DependencyList>();

if (!isEqual(value, ref.current)) {
ref.current = value;
}

return ref.current;
}

/**
* Accepts a function that contains imperative, possibly effectful code.
*
* This is the deepCompare version of the `React.useEffect` hooks (that is shallowed compare)
*
* @param effect Imperative function that can return a cleanup function
* @param deps If present, effect will only activate if the values in the list change.
*
* @see https://gist.github.com/kentcdodds/fb8540a05c43faf636dd68647747b074#gistcomment-2830503
*/
export function useDeepCompareEffect(effect: React.EffectCallback, deps: React.DependencyList): void {
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(effect, useDeepCompareMemoize(deps));
}
59 changes: 59 additions & 0 deletions chaoscenter/web/src/hooks/useDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useParams } from 'react-router-dom';
import { useStrings } from '@strings';
import { useAppStore } from '@context';
import { useDeepCompareEffect } from './useDeepCompareEffect';

export type Title = string | string[];

export interface UseDocumentTitleReturn {
updateTitle: (newTitle: Title) => void;
}

export function useDocumentTitle(title: Title): UseDocumentTitleReturn {
const { getString } = useStrings();
const { projectID: projectIdFromParams, accountID } = useParams<{ projectID: string; accountID: string }>();
const projectIdFromLocalStorage = localStorage.getItem('projectID');
const { projectName, currentUserInfo } = useAppStore();

const getStringFromTitle = (str: Title): string => (Array.isArray(str) ? str.filter(s => s).join(' | ') : str);

const updateTitle = (newTitle: Title): void => {
const titleArray = [getStringFromTitle(newTitle), getString('litmusChaos')];

if (accountID && projectIdFromParams) {
// If you're in project scoped routes, add project name to title from appStore
let projectTitle = '';
if (projectIdFromParams === projectIdFromLocalStorage) {
projectTitle = projectName || projectIdFromParams;
} else {
projectTitle = projectIdFromParams;
}

titleArray.splice(1, 0, projectTitle);
} else if (accountID && !projectIdFromParams) {
// If you're in account scoped routes, add account ID to title
let accountTitle = '';
if (accountID === currentUserInfo?.ID) {
accountTitle = currentUserInfo?.username || accountID;
} else {
accountTitle = accountID;
}
titleArray.splice(1, 0, accountTitle);
}

document.title = titleArray.filter(s => s).join(' | ');
};

useDeepCompareEffect(() => {
updateTitle(title);

return () => {
// reset title on unmount
document.title = getString('litmusChaos');
};
}, [title]);

return {
updateTitle
};
}
7 changes: 3 additions & 4 deletions chaoscenter/web/src/routes/RouteDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ export const paths: UseRouteDefinitionsProps = {
toKubernetesChaosInfrastructures: ({ environmentID }) => `/environments/${environmentID}/kubernetes`,
toKubernetesChaosInfrastructureDetails: ({ chaosInfrastructureID, environmentID }) =>
`/environments/${environmentID}/kubernetes/${chaosInfrastructureID}`,
// chaos image registry routes
toImageRegistry: () => `/image-registry`,
toGitops: () => `/gitops`,
// Account Scoped Routes
toAccountSettingsOverview: () => '/settings/overview',
// Project Setup Routes
toProjectSetup: () => '/setup',
toProjectMembers: () => '/setup/members'
toProjectMembers: () => '/setup/members',
toImageRegistry: () => `/setup/image-registry`,
toGitops: () => `/setup/gitops`
};
Loading

0 comments on commit ac6ebe4

Please sign in to comment.