diff --git a/src/components/IssueCard.tsx b/src/components/IssueCard.tsx index 8c48dc7..5e7a80c 100644 --- a/src/components/IssueCard.tsx +++ b/src/components/IssueCard.tsx @@ -5,6 +5,7 @@ import React, { useContext } from "react"; import { IssueDetail } from "../core/_types"; import RelationshipGraph from "./RelationshipGraph"; import CoreContext from "../core/CoreContext"; +import ProjectName from "./ProjectName"; type Props = { issue: IssueDetail }; @@ -15,7 +16,12 @@ const IssueCard = ({ issue }: Props) => { {issue.identifier} {issue.labels.length > 0 ? " — " + issue.labels.join(", ") : null} - {issue.projectName ? " — " + issue.projectName : null} + {issue.projectName ? ( + <> + {" — "} + + + ) : null}

{issue.title}

{ ? ` (${issuesMissingEffortComparisons})` : null} - Projects Settings
diff --git a/src/components/ProjectName.tsx b/src/components/ProjectName.tsx new file mode 100644 index 0000000..bf4cde6 --- /dev/null +++ b/src/components/ProjectName.tsx @@ -0,0 +1,15 @@ +import emoji from "node-emoji"; + +type Props = { + icon: string | undefined; + name: string; +}; + +const ProjectName = ({ icon, name }: Props) => ( + <> + {icon && emoji.hasEmoji(icon) ? emoji.get(icon) + " " : ""} + {name} + +); + +export default ProjectName; diff --git a/src/components/ProjectRelationshipGraph.css.ts b/src/components/ProjectRelationshipGraph.css.ts deleted file mode 100644 index d4b6e09..0000000 --- a/src/components/ProjectRelationshipGraph.css.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { style } from "@vanilla-extract/css"; - -export const card = style({ - borderRadius: 20, - boxShadow: "0px 0px 10px rgb(0, 0, 0, 0.5)", - overflow: "hidden", -}); diff --git a/src/components/ProjectRelationshipGraph.tsx b/src/components/ProjectRelationshipGraph.tsx deleted file mode 100644 index 7221e23..0000000 --- a/src/components/ProjectRelationshipGraph.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import { ParentSize } from "@visx/responsive"; -import { addEdge, addVertex, create } from "graph-fns"; -import emoji from "node-emoji"; -import { useEffect, useState } from "react"; -import NetworkGraph from "./NetworkGraph"; -import Layer from "./Layer"; -import { card } from "./ProjectRelationshipGraph.css"; -import { State, RelationSummary } from "../core/_types"; - -type Item = - | { type: "project"; id: string; label: string } - | { type: "issue"; id: string; label: string }; - -const getAncestors = ( - relations: Array, - identifiers: Array -): Array => { - const ancestors = relations - .filter((relation) => identifiers.includes(relation.relatedIssueIdentifier)) - .map((relation) => relation.issueIdentifier); - return ancestors.length > 0 - ? ancestors.concat( - ancestors.flatMap((ancestor) => getAncestors(relations, [ancestor])) - ) - : []; -}; - -const getDescendants = ( - relations: Array, - identifiers: Array -): Array => { - const descendants = relations - .filter((relation) => identifiers.includes(relation.issueIdentifier)) - .map((relation) => relation.relatedIssueIdentifier); - return descendants.length > 0 - ? descendants.concat( - descendants.flatMap((descendant) => - getDescendants(relations, [descendant]) - ) - ) - : []; -}; - -const ProjectRelationshipGraph = ({ - state, - projectId, -}: { - state: State; - projectId: string; -}) => { - const [showing, setShowing] = useState(false); - const [forceStrength, setForceStrength] = useState(1500); - - useEffect(() => { - const escapeListener = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setShowing(false); - } - }; - - document.addEventListener("keydown", escapeListener); - - return () => { - document.removeEventListener("keydown", escapeListener); - }; - }); - - const projectIssueIdentifiers = state.issueSummaries - .filter( - (issue) => - issue.state === "triage" || - issue.state === "backlog" || - issue.state === "unstarted" - ) - .filter((issue) => issue.projectId === projectId) - .map((issue) => issue.identifier); - - const blockingRelations = state.issueSummaries - .flatMap((issue) => - issue.relations.map( - (relation): RelationSummary => ({ - id: relation.id, - issueIdentifier: issue.identifier, - relatedIssueIdentifier: relation.identifier, - type: relation.type, - }) - ) - ) - .filter((relation) => relation.type === "blocks"); - - const ancestors = getAncestors(blockingRelations, projectIssueIdentifiers); - const descendants = getDescendants( - blockingRelations, - projectIssueIdentifiers - ); - - if (ancestors.length === 0 && descendants.length === 0) { - return

-

; - } - - const ancestorItems: Array = []; - - for (let ancestor of ancestors) { - const issue = state.issueSummaries.find( - (issue) => issue.identifier === ancestor - ); - if (!issue) continue; - if (issue.projectId !== undefined && issue.projectName !== undefined) { - ancestorItems.push({ - type: "project", - id: issue.projectId, - label: `${ - issue.projectIcon && emoji.hasEmoji(issue.projectIcon) - ? emoji.get(issue.projectIcon) + " " - : "" - }${issue.projectName}`, - }); - } else { - ancestorItems.push({ - type: "issue", - id: issue.identifier, - label: issue.title, - }); - } - } - - const descendantItems: Array = []; - - for (let descendant of descendants) { - const issue = state.issueSummaries.find( - (issue) => issue.identifier === descendant - ); - if (!issue) continue; - if (issue.projectId !== undefined && issue.projectName !== undefined) { - descendantItems.push({ - type: "project", - id: issue.projectId, - label: `${ - issue.projectIcon && emoji.hasEmoji(issue.projectIcon) - ? emoji.get(issue.projectIcon) + " " - : "" - }${issue.projectName}`, - }); - } else { - descendantItems.push({ - type: "issue", - id: issue.identifier, - label: issue.title, - }); - } - } - - const all = [...ancestors, ...projectIssueIdentifiers, ...descendants]; - - let graph = create(); - - const items: Array = []; - - for (let relation of blockingRelations) { - if ( - !all.includes(relation.issueIdentifier) || - !all.includes(relation.relatedIssueIdentifier) - ) { - continue; - } - - const issue = state.issueSummaries.find( - (issue) => issue.identifier === relation.issueIdentifier - ); - const relatedIssue = state.issueSummaries.find( - (issue) => issue.identifier === relation.relatedIssueIdentifier - ); - - if (!issue || !relatedIssue) continue; - if (issue.state === "completed" || relatedIssue.state === "completed") - continue; - if (issue.state === "canceled" || relatedIssue.state === "canceled") - continue; - - const issueItem: Item = - issue.projectId && issue.projectName - ? { - type: "project", - id: issue.projectId, - label: `${ - issue.projectIcon && emoji.hasEmoji(issue.projectIcon) - ? emoji.get(issue.projectIcon) + " " - : "" - }${issue.projectName}`, - } - : { - type: "issue", - id: issue.identifier, - label: issue.title - ? `[${issue.identifier}] ${issue.title}` - : issue.identifier, - }; - - const relatedIssueItem: Item = - relatedIssue.projectId && relatedIssue.projectName - ? { - type: "project", - id: relatedIssue.projectId, - label: `${ - relatedIssue.projectIcon && - emoji.hasEmoji(relatedIssue.projectIcon) - ? emoji.get(relatedIssue.projectIcon) + " " - : "" - }${relatedIssue.projectName}`, - } - : { - type: "issue", - id: relatedIssue.identifier, - label: relatedIssue.title - ? `[${relatedIssue.identifier}] ${relatedIssue.title}` - : relatedIssue.identifier, - }; - - if (issueItem.id === relatedIssueItem.id) continue; - - if (!items.some((item) => item.id === issueItem.id)) { - items.push(issueItem); - graph = addVertex(graph, issueItem.id); - } - - if (!items.some((item) => item.id === relatedIssueItem.id)) { - items.push(relatedIssueItem); - graph = addVertex(graph, relatedIssueItem.id); - } - - graph = addEdge(graph, [issueItem.id, relatedIssueItem.id]); - } - - if (items.length === 0) { - return

-

; - } - - return showing ? ( - -
-
- - {({ width, height }) => ( -
- { - const item = items.find((item) => item.id === id); - return item?.label ?? "Unknown"; - }} - color={(id) => { - // const item = items.find((item) => item.id === id); - return id === projectId - ? "#000" - : ancestorItems.some((item) => item.id === id) - ? "#900" - : "#090"; - }} - // label={(identifier) => { - // const issue = context.issueSummaries.find( - // (issue) => issue.identifier === identifier - // ); - // return issue?.title - // ? `[${identifier}] ${issue?.title} (${issue.projectName})` - // : identifier; - // }} - // color={(identifier) => { - // const issue = context.issueSummaries.find( - // (issue) => issue.identifier === identifier - // ); - // return issue?.state === "completed" || - // issue?.state === "canceled" - // ? "#999" - // : projectIssueIdentifiers.includes(identifier) - // ? "#000" - // : ancestors.includes(identifier) - // ? "#900" - // : "#090"; - // }} - // textDecoration={(identifier) => { - // const issue = context.issueSummaries.find( - // (issue) => issue.identifier === identifier - // ); - // return issue?.state === "completed" || - // issue?.state === "canceled" - // ? "line-through" - // : "none"; - // }} - /> -
- )} -
-
- - { - setForceStrength(parseInt(value, 10)); - }} - style={{ position: "absolute", left: 20, top: 20 }} - /> -
-
- ) : ( - - ); -}; - -export default ProjectRelationshipGraph; diff --git a/src/pages/index.page.tsx b/src/pages/index.page.tsx index d8c4a14..110f9e2 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/index.page.tsx @@ -2,6 +2,7 @@ import type { NextPage } from "next"; import { useContext, useState } from "react"; import ComparisonValue from "../components/ComparisonValue"; import Layout from "../components/Layout"; +import ProjectName from "../components/ProjectName"; import RelationshipGraph from "../components/RelationshipGraph"; import CoreContext from "../core/CoreContext"; @@ -160,7 +161,14 @@ const IndexPage: NextPage = () => { {issue.identifier} - {issue.projectName} + + {issue.projectName ? ( + + ) : null} + {issue.cycle} {issue.title} diff --git a/src/pages/projects.page.tsx b/src/pages/projects.page.tsx deleted file mode 100644 index 0796aa5..0000000 --- a/src/pages/projects.page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import emoji from "node-emoji"; -import { useContext } from "react"; -import Layout from "../components/Layout"; -import ProjectRelationshipGraph from "../components/ProjectRelationshipGraph"; -import CoreContext from "../core/CoreContext"; - -const ProjectsPage = () => { - const { state: data, updateIssueEstimate } = useContext(CoreContext); - - const projects: Array<{ id: string; name: string; issues: number }> = []; - - for (let issue of data.issueSummaries.filter( - (issue) => - issue.state === "triage" || - issue.state === "backlog" || - issue.state === "unstarted" - )) { - if (!issue.projectId || !issue.projectName) continue; - - const existingProject = projects.find( - (project) => project.id === issue.projectId - ); - - if (existingProject) { - existingProject.issues += 1; - } else { - projects.push({ - id: issue.projectId, - name: `${ - issue.projectIcon && emoji.hasEmoji(issue.projectIcon) - ? emoji.get(issue.projectIcon) + " " - : "" - }${issue.projectName}`, - issues: 1, - }); - } - } - - return ( - - - - - - - - - - - {projects - .sort((a, b) => - emoji.strip(a.name).localeCompare(emoji.strip(b.name)) - ) - .map((project) => { - return ( - - - - - - ); - })} - -
NameIssuesDependencies
{project.name}{project.issues} - -
-
- ); -}; - -export default ProjectsPage;