Skip to content

Commit

Permalink
Genericize filterObjects and add to tui common
Browse files Browse the repository at this point in the history
  • Loading branch information
nathandf committed Sep 28, 2024
1 parent 9677bea commit 761cfe3
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 109 deletions.
9 changes: 9 additions & 0 deletions packages/tapisui-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ import {
useSystem,
type SystemContextType,
} from './context';
import {
type PropsOfObjectWithValuesOfType,
type OrderBy,
filterObjects,
} from './utils/filterObject';

export {
// Generic UI
Expand Down Expand Up @@ -185,4 +190,8 @@ export {
SystemContext,
useSystem,
type SystemContextType,
// Utils
type PropsOfObjectWithValuesOfType,
type OrderBy,
filterObjects,
};
36 changes: 36 additions & 0 deletions packages/tapisui-common/src/utils/filterObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Type that matches property values of type T if and only if the value of
// that property is a string or undefined
export type PropsOfObjectWithValuesOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

export type OrderBy = 'ASC' | 'DESC';

export const filterObjects = <T extends {}, U = string | undefined>(
objects: Array<T>,
groupBy: PropsOfObjectWithValuesOfType<T, U>,
orderGroupBy: OrderBy = 'ASC'
): Array<[string, Array<T>]> => {
let objectsByProp: { [key: string]: Array<T> } = {};
for (let obj of objects) {
// Cannot filter on values that are undefined.
if (obj[groupBy] === undefined) {
continue;
}

// Create groupBy key if it doesn't exist and add the object to an empty
// array
const key = obj[groupBy] as unknown as string;
if (objectsByProp[key] === undefined) {
objectsByProp[key] = [obj];
continue;
}

objectsByProp[key] = [...objectsByProp[key], obj];
}

const filteredObjects = Object.entries(objectsByProp).sort((a, b) =>
a[0].localeCompare(b[0])
);
return orderGroupBy === 'ASC' ? filteredObjects : filteredObjects.reverse();
};
198 changes: 89 additions & 109 deletions src/app/Systems/_components/SystemsNav/SystemsNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import React, { useCallback, useMemo, useState } from 'react';
import { useHistory, useRouteMatch } from 'react-router-dom';
import { Systems as Hooks } from '@tapis/tapisui-hooks';
import { Systems } from '@tapis/tapis-typescript';
import { QueryWrapper } from '@tapis/tapisui-common';
import {
QueryWrapper,
filterObjects,
PropsOfObjectWithValuesOfType,
} from '@tapis/tapisui-common';
import {
ListItemText,
ListItemIcon,
Expand Down Expand Up @@ -65,38 +69,17 @@ const SystemsNav: React.FC = () => {
[systems]
);

// Type that matches property values of type T if and only if the value of
// that property is a string or undefined
type StringKeysOf<T> = {
[K in keyof T]: T[K] extends string | undefined ? K : never;
}[keyof T];

// Creates an object in which the keys are the system host and the value
// is an array of systems for that host

const filterSystemByProp = useCallback(
(
prop: StringKeysOf<Systems.TapisSystem>,
systems: Array<Systems.TapisSystem>,
order: 'ASC' | 'DESC' = 'ASC'
) => {
if (state.filters.includes(prop!)) {
let systemsByProp: { [key: string]: Array<Systems.TapisSystem> } = {};
for (let system of systems) {
systemsByProp[system[prop!]!] =
systemsByProp[system[prop!]!] === undefined
? [system]
: [...systemsByProp[system[prop!]!], system];
}
const filteredSystems = Object.entries(systemsByProp).sort((a, b) =>
a[0].localeCompare(b[0])
);
return order === 'ASC' ? filteredSystems : filteredSystems.reverse();
}

return [];
},
[state.filters, systems]
const systemsByHost = useMemo(
() => filterObjects(systems, 'host', 'ASC'),
[systems]
);
const systemsByType = useMemo(
() => filterObjects(systems, 'systemType', 'ASC'),
[systems]
);
const systemsByAuth = useMemo(
() => filterObjects(systems, 'defaultAuthnMethod', 'ASC'),
[systems]
);

const renderFilteredSystemsList = useCallback(
Expand All @@ -108,7 +91,10 @@ const SystemsNav: React.FC = () => {
title: string,
icon: any,
itemIcon: any,
secondary: StringKeysOf<Systems.TapisSystem> = 'host'
secondary: PropsOfObjectWithValuesOfType<
Systems.TapisSystem,
string | undefined
> = 'host'
) => {
return (
<List
Expand Down Expand Up @@ -292,83 +278,77 @@ const SystemsNav: React.FC = () => {
</>
)}
{state.filters.includes('host') &&
filterSystemByProp('host', systems, 'ASC').map(
([host, systemsByHost]) => {
return renderFilteredSystemsList(
systemsByHost,
() => {
setState({
...state,
open: state.open.includes(host)
? state.open.filter((tab) => {
tab !== host;
})
: [...state.open, host],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
host,
host,
<Dns />,
<Dns />,
'host'
);
}
)}
systemsByHost.map(([host, systemsByHost]) => {
return renderFilteredSystemsList(
systemsByHost,
() => {
setState({
...state,
open: state.open.includes(host)
? state.open.filter((tab) => {
tab !== host;
})
: [...state.open, host],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
host,
host,
<Dns />,
<Dns />,
'host'
);
})}
{state.filters.includes('systemType') &&
filterSystemByProp('systemType', systems, 'ASC').map(
([type, systemsByType]) => {
return renderFilteredSystemsList(
systemsByType,
() => {
setState({
...state,
open: state.open.includes(type)
? state.open.filter((tab) => {
tab !== type;
})
: [...state.open, type],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
type,
type,
<Dns />,
<Dns />,
'host'
);
}
)}
systemsByType.map(([type, systemsByType]) => {
return renderFilteredSystemsList(
systemsByType,
() => {
setState({
...state,
open: state.open.includes(type)
? state.open.filter((tab) => {
tab !== type;
})
: [...state.open, type],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
type,
type,
<Dns />,
<Dns />,
'host'
);
})}
{state.filters.includes('defaultAuthnMethod') &&
filterSystemByProp('defaultAuthnMethod', systems, 'ASC').map(
([authn, systemsByType]) => {
return renderFilteredSystemsList(
systemsByType,
() => {
setState({
...state,
open: state.open.includes(authn)
? state.open.filter((tab) => {
tab !== authn;
})
: [...state.open, authn],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
authn,
authn,
<Dns />,
<Dns />,
'host'
);
}
)}
systemsByAuth.map(([authn, systemsByType]) => {
return renderFilteredSystemsList(
systemsByType,
() => {
setState({
...state,
open: state.open.includes(authn)
? state.open.filter((tab) => {
tab !== authn;
})
: [...state.open, authn],
});
},
(system) => {
history.push(`${url}/${system.id}`);
},
authn,
authn,
<Dns />,
<Dns />,
'host'
);
})}
{renderFilteredSystemsList(
deletedSystems,
() => {
Expand Down

0 comments on commit 761cfe3

Please sign in to comment.