diff --git a/.changeset/odd-plums-bake.md b/.changeset/odd-plums-bake.md new file mode 100644 index 0000000000..427ab776b5 --- /dev/null +++ b/.changeset/odd-plums-bake.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-adr-common': patch +'@backstage/plugin-adr': patch +--- + +use path attribute to fetch files instead of name and update the UI to navigate over subdirectories diff --git a/plugins/adr-common/src/index.ts b/plugins/adr-common/src/index.ts index c3a048d9d1..cf71367cba 100644 --- a/plugins/adr-common/src/index.ts +++ b/plugins/adr-common/src/index.ts @@ -77,7 +77,7 @@ export type AdrFilePathFilterFn = (path: string) => boolean; * @public */ export const madrFilePathFilter: AdrFilePathFilterFn = (path: string) => - /^\d{4}-.+\.md$/.test(path); + /^(?:.*\/)?\d{4}-.+\.md$/.test(path); /** * ADR indexable document interface diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 05414dd8fb..a7763ae881 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -35,6 +35,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", + "lodash": "^4.17.21", "react-markdown": "^8.0.0", "react-use": "^17.2.4", "remark-gfm": "^3.0.1" diff --git a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx index eb5b4aed21..d136de4717 100644 --- a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx +++ b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx @@ -16,9 +16,14 @@ import React, { useEffect, useState } from 'react'; import { Link, useSearchParams } from 'react-router-dom'; +import useAsync from 'react-use/lib/useAsync'; + +import groupBy from 'lodash/groupBy'; + import { Content, ContentHeader, + InfoCard, MissingAnnotationEmptyState, Progress, SupportButton, @@ -35,27 +40,119 @@ import { } from '@backstage/plugin-adr-common'; import { useEntity } from '@backstage/plugin-catalog-react'; import { + Box, Chip, + Collapse, Grid, List, ListItem, + ListItemIcon, ListItemText, makeStyles, Theme, Typography, } from '@material-ui/core'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import FolderIcon from '@material-ui/icons/Folder'; +import { adrApiRef, AdrFileInfo } from '../../api'; import { rootRouteRef } from '../../routes'; import { AdrContentDecorator, AdrReader } from '../AdrReader'; -import { adrApiRef } from '../../api'; -import useAsync from 'react-use/lib/useAsync'; const useStyles = makeStyles((theme: Theme) => ({ adrMenu: { backgroundColor: theme.palette.background.paper, }, + adrContainerTitle: { + color: theme.palette.grey[700], + marginBottom: theme.spacing(1), + }, + adrChip: { + position: 'absolute', + right: 0, + }, })); +const AdrListContainer = (props: { + adrs: AdrFileInfo[]; + selectedAdr: string; + title: string; +}) => { + const { adrs, selectedAdr, title } = props; + const classes = useStyles(); + const rootLink = useRouteRef(rootRouteRef); + const [open, setOpen] = React.useState(true); + + const getChipColor = (status: string) => { + switch (status) { + case 'accepted': + return 'primary'; + case 'rejected': + case 'deprecated': + return 'secondary'; + default: + return 'default'; + } + }; + + const handleClick = () => { + setOpen(!open); + }; + + return ( + <> + {title && ( + + + + + + {open ? : } + + )} + + + {adrs.map((adr, idx) => ( + + + {adr.date} + {adr.status && ( + + )} + + } + /> + + ))} + + + + ); +}; + /** * Component for browsing ADRs on an entity page. * @public @@ -67,10 +164,7 @@ export const EntityAdrContent = (props: { const { contentDecorators, filePathFilterFn } = props; const classes = useStyles(); const { entity } = useEntity(); - const rootLink = useRouteRef(rootRouteRef); - const [adrList, setAdrList] = useState< - { name: string; title?: string; status?: string; date?: string }[] - >([]); + const [adrList, setAdrList] = useState([]); const [searchParams, setSearchParams] = useSearchParams(); const scmIntegrations = useApi(scmIntegrationsApiRef); const adrApi = useApi(adrApiRef); @@ -82,10 +176,15 @@ export const EntityAdrContent = (props: { }, [entity, scmIntegrations]); const selectedAdr = - adrList.find(adr => adr.name === searchParams.get('record'))?.name ?? ''; + adrList.find(adr => adr.path === searchParams.get('record'))?.path ?? ''; + + const adrSubDirectoryFunc = (adr: AdrFileInfo) => { + return adr.path.split('/').slice(0, -1).join('/'); + }; + useEffect(() => { if (adrList.length && !selectedAdr) { - searchParams.set('record', adrList[0].name); + searchParams.set('record', adrList[0].path); setSearchParams(searchParams, { replace: true }); } }); @@ -95,29 +194,20 @@ export const EntityAdrContent = (props: { return; } - const adrs = value.data - .filter( - (item: { type: string; name: string }) => - item.type === 'file' && - (filePathFilterFn - ? filePathFilterFn(item.name) - : madrFilePathFilter(item.name)), - ) - .map(({ name, title, status, date }) => ({ name, title, status, date })); + const adrs: AdrFileInfo[] = value.data.filter( + (item: AdrFileInfo) => + item.type === 'file' && + (filePathFilterFn + ? filePathFilterFn(item.path) + : madrFilePathFilter(item.path)), + ); setAdrList(adrs); }, [filePathFilterFn, value]); - const getChipColor = (status: string) => { - switch (status) { - case 'accepted': - return 'primary'; - case 'rejected' || 'deprecated': - return 'secondary'; - default: - return 'default'; - } - }; + const adrListGrouped = Object.entries( + groupBy(adrList, adrSubDirectoryFunc), + ).sort(); return ( @@ -141,33 +231,18 @@ export const EntityAdrContent = (props: { (adrList.length ? ( - - {adrList.map((adr, idx) => ( - - + + {adrListGrouped.map(([title, adrs], idx) => ( + - {adr.status && ( - - )} - - ))} - + ))} + + diff --git a/yarn.lock b/yarn.lock index 75f4a41905..367a00c90e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4356,6 +4356,7 @@ __metadata: "@types/node": "*" "@types/react": ^16.13.1 || ^17.0.0 cross-fetch: ^3.1.5 + lodash: ^4.17.21 msw: ^1.0.0 react-markdown: ^8.0.0 react-use: ^17.2.4