Skip to content

Commit

Permalink
H-3362: Support links which link to other links in the types graph vi…
Browse files Browse the repository at this point in the history
…sualization (#5234)
  • Loading branch information
CiaranMn authored Sep 26, 2024
1 parent 988a4fe commit f3d984a
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const persistEntitiesAction: FlowActionActivity = async ({ inputs }) => {
* This assumes that there are no link entities which link to other link entities, which require being able to
* create multiple entities at once in a single transaction (since they refer to each other).
*
* @todo handle links pointing to other links via creating many entities at once, unblocked by H-1178
* @todo handle links pointing to other links via creating many entities at once, unblocked by H-1178. See also entity-result-table
*/
if (
(a.sourceEntityId && b.sourceEntityId) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ type BaseCreateTypeIfNotExistsParameters = {
migrationState: MigrationState;
};

const generateSystemDataTypeSchema = ({
export const generateSystemDataTypeSchema = ({
dataTypeId,
...rest
}: ConstructDataTypeParams & { dataTypeId: VersionedUrl }): CustomDataType => {
Expand Down
1 change: 1 addition & 0 deletions apps/hash-frontend/src/pages/shared/entities-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const EntitiesTable: FunctionComponent<{

const [filterState, setFilterState] = useState<FilterState>({
includeGlobal: false,
limitToWebs: false,
});
const [showSearch, setShowSearch] = useState<boolean>(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export const GraphDataLoader = ({

for (const edge of edges) {
graph.addEdgeWithKey(edge.edgeId, edge.source, edge.target, {
color: "rgba(230, 230, 230, 1)",
color: "rgba(50, 50, 50, 0.5)",
label: edge.label,
size: edge.size,
type: "arrow",
Expand Down
38 changes: 30 additions & 8 deletions apps/hash-frontend/src/pages/shared/type-graph-visualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,17 @@ export const TypeGraphVisualizer = ({
size: 18,
};

/**
* Link types can appear multiple times in the visualization (one per each destination combination).
* We need to track all the occurrences of a link type, so that if we encounter a link A which links to a link B,
* we can link from link A to all the occurrences of link B.
*/
const linkNodesByEntityTypeId: Record<VersionedUrl, string[]> = {};

for (const { schema } of types) {
if (schema.kind !== "entityType") {
/**
* Don't yet add property or data types to the graph.
* We don't yet support visualizing property or data types to the graph.
*/
continue;
}
Expand All @@ -59,7 +66,7 @@ export const TypeGraphVisualizer = ({
const isLink = isSpecialEntityTypeLookup?.[entityTypeId]?.isLink;
if (isLink) {
/**
* We'll add the links as we process each entity type.
* We'll add the links as we process each entity type – this means that any link types which are unused won't appear in the graph.
*/
continue;
}
Expand Down Expand Up @@ -112,14 +119,29 @@ export const TypeGraphVisualizer = ({
});
addedNodeIds.add(linkNodeId);

linkNodesByEntityTypeId[linkTypeId] ??= [];
linkNodesByEntityTypeId[linkTypeId].push(linkNodeId);

if (destinationTypeIds) {
for (const destinationTypeId of destinationTypeIds) {
edgesToAdd.push({
edgeId: `${linkNodeId}~${destinationTypeId}`,
size: 3,
source: linkNodeId,
target: destinationTypeId,
});
let targetNodeIds: string[] = [destinationTypeId];

if (isSpecialEntityTypeLookup?.[destinationTypeId]?.isLink) {
/**
* If the destination is itself a link, we need to account for the multiple places the destination link may appear.
*/
targetNodeIds =
linkNodesByEntityTypeId[destinationTypeId] ?? [];
}

for (const targetNodeId of targetNodeIds) {
edgesToAdd.push({
edgeId: `${linkNodeId}~${targetNodeId}`,
size: 3,
source: linkNodeId,
target: targetNodeId,
});
}
}
} else {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export const TypesTable: FunctionComponent<{
const [filterState, setFilterState] = useState<FilterState>({
includeArchived: false,
includeGlobal: false,
limitToWebs: false,
});

const [selectedEntityTypeId, setSelectedEntityTypeId] =
Expand Down Expand Up @@ -201,68 +202,85 @@ export const TypesTable: FunctionComponent<{
];
}, [authenticatedUser]);

const filteredTypes = useMemo(() => {
const filtered: ((
| EntityTypeWithMetadata
| PropertyTypeWithMetadata
| DataTypeWithMetadata
) & { isExternal: boolean; webShortname?: string; archived: boolean })[] =
[];

for (const type of types ?? []) {
const isExternal = isExternalOntologyElementMetadata(type.metadata)
? true
: !internalWebIds.includes(type.metadata.ownedById);

const namespaceOwnedById = isExternalOntologyElementMetadata(
type.metadata,
)
? undefined
: type.metadata.ownedById;

const webShortname = namespaces?.find(
(workspace) => extractOwnedById(workspace) === namespaceOwnedById,
)?.shortname;

const isArchived = isTypeArchived(type);

if (
(filterState.includeGlobal ? true : !isExternal) &&
(filterState.includeArchived ? true : !isArchived) &&
(filterState.limitToWebs
? webShortname && filterState.limitToWebs.includes(webShortname)
: true)
) {
filtered.push({
...type,
isExternal,
webShortname,
archived: isArchived,
});
}
}

return filtered;
}, [types, filterState, namespaces, internalWebIds]);

const filteredRows = useMemo<TypesTableRow[] | undefined>(
() =>
types
?.map((type) => {
const isExternal = isExternalOntologyElementMetadata(type.metadata)
? true
: !internalWebIds.includes(type.metadata.ownedById);

const namespaceOwnedById = isExternalOntologyElementMetadata(
type.metadata,
)
? undefined
: type.metadata.ownedById;

const webShortname = namespaces?.find(
(workspace) => extractOwnedById(workspace) === namespaceOwnedById,
)?.shortname;

const lastEdited = format(
new Date(
type.metadata.temporalVersioning.transactionTime.start.limit,
),
"yyyy-MM-dd HH:mm",
);

const lastEditedBy = actors?.find(
({ accountId }) =>
accountId === type.metadata.provenance.edition.createdById,
);

return {
rowId: type.schema.$id,
typeId: type.schema.$id,
title: type.schema.title,
lastEdited,
lastEditedBy,
kind:
type.schema.kind === "entityType"
? isSpecialEntityTypeLookup?.[type.schema.$id]?.isFile
? "link-type"
: "entity-type"
: type.schema.kind === "propertyType"
? "property-type"
: "data-type",
external: isExternal,
webShortname,
archived: isTypeArchived(type),
} as const;
})
.filter(
({ external, archived }) =>
(filterState.includeGlobal ? true : !external) &&
(filterState.includeArchived ? true : !archived),
),
[
actors,
internalWebIds,
isSpecialEntityTypeLookup,
types,
namespaces,
filterState,
],
filteredTypes.map((type) => {
const lastEdited = format(
new Date(
type.metadata.temporalVersioning.transactionTime.start.limit,
),
"yyyy-MM-dd HH:mm",
);

const lastEditedBy = actors?.find(
({ accountId }) =>
accountId === type.metadata.provenance.edition.createdById,
);

return {
rowId: type.schema.$id,
typeId: type.schema.$id,
title: type.schema.title,
lastEdited,
lastEditedBy,
kind:
type.schema.kind === "entityType"
? isSpecialEntityTypeLookup?.[type.schema.$id]?.isFile
? "link-type"
: "entity-type"
: type.schema.kind === "propertyType"
? "property-type"
: "data-type",
external: type.isExternal,
webShortname: type.webShortname,
archived: type.archived,
} as const;
}),
[actors, isSpecialEntityTypeLookup, filteredTypes],
);

const sortRows = useCallback<
Expand Down Expand Up @@ -513,7 +531,7 @@ export const TypesTable: FunctionComponent<{
<Box height={maxTableHeight}>
<TypeGraphVisualizer
onTypeClick={setSelectedEntityTypeId}
types={types ?? []}
types={filteredTypes}
/>
</Box>
)}
Expand Down
1 change: 1 addition & 0 deletions apps/hash-frontend/src/shared/table-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const NoMaxWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
export type FilterState = {
includeArchived?: boolean;
includeGlobal: boolean;
limitToWebs: string[] | false;
};

export type GetAdditionalCsvDataFunction = () => Promise<{
Expand Down

0 comments on commit f3d984a

Please sign in to comment.