From 1be957c5f99aff7689e31d008380c171dee40257 Mon Sep 17 00:00:00 2001 From: Min Kim <minkimcello@gmail.com> Date: Mon, 18 Nov 2024 20:01:01 -0500 Subject: [PATCH] Create search list item component for discussions with links to backstage user --- .../GithubDiscussionsSearchResultListItem.tsx | 128 ++++++++++++++++++ .../search/SearchResultCustomList.tsx | 125 +++++++++-------- yarn.lock | 25 +++- 3 files changed, 222 insertions(+), 56 deletions(-) create mode 100644 packages/app/src/components/search/GithubDiscussionsSearchResultListItem.tsx diff --git a/packages/app/src/components/search/GithubDiscussionsSearchResultListItem.tsx b/packages/app/src/components/search/GithubDiscussionsSearchResultListItem.tsx new file mode 100644 index 0000000..71f2ab2 --- /dev/null +++ b/packages/app/src/components/search/GithubDiscussionsSearchResultListItem.tsx @@ -0,0 +1,128 @@ +import React, { ReactNode } from 'react'; +import Box from '@material-ui/core/Box'; +import Chip from '@material-ui/core/Chip'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import { Link } from '@backstage/core-components'; +import { ResultHighlight } from '@backstage/plugin-search-common'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; +import { IndexableDocument } from '@backstage/plugin-search-common'; +import { EntityRefLink } from '@backstage/plugin-catalog-react'; + +export interface GithubDiscussionsDocument extends IndexableDocument { + author: string; + category: string; + labels: { + name: string; + color: string; + }[]; + comments: { + author: string; + bodyText: string; + replies: { + author: string; + bodyText: string; + }[]; + }[]; +} + +const useStyles = makeStyles(theme => ({ + item: { + display: 'flex', + }, + flexContainer: { + flexWrap: 'wrap', + }, + itemText: { + width: '100%', + wordBreak: 'break-all', + marginBottom: '1rem', + }, + user: { + display: 'inline-flex', + margin: theme.spacing(1), + }, +})); + +/** + * Props for {@link GithubDiscussionsSearchResultListItem}. + * + * @public + */ +export interface GithubDiscussionsSearchResultListItemProps { + icon?: ReactNode; + result?: GithubDiscussionsDocument; + highlight?: ResultHighlight; + lineClamp?: number; +} + +/** @public */ +export function GithubDiscussionsSearchResultListItem( + props: GithubDiscussionsSearchResultListItemProps, +) { + const { result, highlight, icon } = props; + const classes = useStyles(); + if (!result) return null; + + return ( + <div className={classes.item}> + {icon && <ListItemIcon>{icon}</ListItemIcon>} + <div className={classes.flexContainer}> + <ListItemText + className={classes.itemText} + primaryTypographyProps={{ variant: 'h6' }} + primary={ + <Link noTrack to={result.location}> + {highlight?.fields.title ? ( + <HighlightedSearchResultText + text={highlight.fields.title} + preTag={highlight.preTag} + postTag={highlight.postTag} + /> + ) : ( + result.title + )} + </Link> + } + secondary={ + <Typography + component="span" + style={{ + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: props.lineClamp, + overflow: 'hidden', + }} + color="textSecondary" + variant="body2" + > + {highlight?.fields.text ? ( + <HighlightedSearchResultText + text={highlight.fields.text} + preTag={highlight.preTag} + postTag={highlight.postTag} + /> + ) : ( + result.text + )} + </Typography> + } + /> + <Box> + {result.author && ( + <div className={classes.user}> + <EntityRefLink entityRef={`user:default/${result.author}`} /> + </div> + )} + {result.category && <Chip label={result.category} size="small" />} + {result.labels.length > 0 && + result.labels.map(({ name }) => { + return <Chip key={name} label={name} size="small" />; + })} + </Box> + </div> + </div> + ); +} diff --git a/packages/app/src/components/search/SearchResultCustomList.tsx b/packages/app/src/components/search/SearchResultCustomList.tsx index 6d0a3ee..918dff0 100644 --- a/packages/app/src/components/search/SearchResultCustomList.tsx +++ b/packages/app/src/components/search/SearchResultCustomList.tsx @@ -1,64 +1,81 @@ import React from 'react'; import { List } from '@material-ui/core'; -import { SearchResult, DefaultResultListItem } from '@backstage/plugin-search-react'; +import { + SearchResult, + DefaultResultListItem, +} from '@backstage/plugin-search-react'; import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; -import { StackOverflowSearchResultListItem, StackOverflowIcon } from '@backstage-community/plugin-stack-overflow'; +import { + StackOverflowSearchResultListItem, + StackOverflowIcon, +} from '@backstage-community/plugin-stack-overflow'; import { CatalogIcon, DocsIcon } from '@backstage/core-components'; import { TechDocsSearchResultCustomListItem } from './TechDocsSearchResultCustomListItem'; +import { + GithubDiscussionsSearchResultListItem, + GithubDiscussionsDocument, +} from './GithubDiscussionsSearchResultListItem'; const SearchResultCustomList = () => { - return ( - <SearchResult> - {({ results }) => ( - <List> - {results.map(({ type, document, highlight, rank }) => { - switch (type) { - case 'software-catalog': - return ( - <CatalogSearchResultListItem - key={document.location} - result={document} - highlight={highlight} - rank={rank} - icon={<CatalogIcon />} - /> - ); - case 'techdocs': - return ( - <TechDocsSearchResultCustomListItem - key={document.location} - result={document} - highlight={highlight} - rank={rank} - asListItem={true} - icon={<DocsIcon />} - /> - ); - case 'stack-overflow': - return ( - <StackOverflowSearchResultListItem - key={document.location} - result={document} - icon={<StackOverflowIcon />} - /> - ); - default: - return ( - <DefaultResultListItem - key={document.location} - result={document} - highlight={highlight} - rank={rank} - /> - ); - } - })} - </List> - )} - </SearchResult> - ) -} + return ( + <SearchResult> + {({ results }) => ( + <List> + {results.map(({ type, document, highlight, rank }) => { + switch (type) { + case 'software-catalog': + return ( + <CatalogSearchResultListItem + key={document.location} + result={document} + highlight={highlight} + rank={rank} + icon={<CatalogIcon />} + /> + ); + case 'techdocs': + return ( + <TechDocsSearchResultCustomListItem + key={document.location} + result={document} + highlight={highlight} + rank={rank} + asListItem + icon={<DocsIcon />} + /> + ); + case 'stack-overflow': + return ( + <StackOverflowSearchResultListItem + key={document.location} + result={document} + icon={<StackOverflowIcon />} + /> + ); + case 'github-discussions': + return ( + <GithubDiscussionsSearchResultListItem + result={document as GithubDiscussionsDocument} + highlight={highlight} + /> + ); + default: + return ( + <DefaultResultListItem + key={document.location} + result={document} + highlight={highlight} + rank={rank} + /> + ); + } + })} + </List> + )} + </SearchResult> + ); +}; -export const searchResultCustomList = <SearchResultCustomList />; \ No newline at end of file +export const searchResultCustomList = <SearchResultCustomList />; diff --git a/yarn.lock b/yarn.lock index 75594c2..8e5f1e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10628,13 +10628,20 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^18", "@types/react-dom@^18.0.0": +"@types/react-dom@*", "@types/react-dom@^18.0.0": version "18.3.1" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07" integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== dependencies: "@types/react" "*" +"@types/react-dom@<18.0.0": + version "17.0.25" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5" + integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA== + dependencies: + "@types/react" "^17" + "@types/react-redux@^7.1.20": version "7.1.34" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.34.tgz#83613e1957c481521e6776beeac4fd506d11bd0e" @@ -10666,7 +10673,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.13.1 || ^17.0.0", "@types/react@^16.13.1 || ^17.0.0 || ^18.0.0", "@types/react@^18": +"@types/react@*", "@types/react@^16.13.1 || ^17.0.0 || ^18.0.0": version "18.3.12" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60" integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== @@ -10674,6 +10681,15 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/react@^16.13.1 || ^17.0.0", "@types/react@^17": + version "17.0.83" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.83.tgz#b477c56387b74279281149dcf5ba2a1e2216d131" + integrity sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "^0.16" + csstype "^3.0.2" + "@types/request@^2.47.1", "@types/request@^2.48.8": version "2.48.12" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" @@ -10701,6 +10717,11 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/scheduler@^0.16": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== + "@types/semver@7.5.8", "@types/semver@^7.5.0": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"