@@ -1,5 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-tech-radar': minor
|
||||
'@backstage/plugin-tech-radar': patch
|
||||
---
|
||||
|
||||
Added SearchBar to allow filtering and a scroll bar to display hidden tech
|
||||
Added `SearchBar` to allow filtering and a scroll bar to display hidden tech
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import Radar from '../components/Radar';
|
||||
import {
|
||||
RadarEntry,
|
||||
techRadarApiRef,
|
||||
TechRadarComponentProps,
|
||||
TechRadarLoaderResponse,
|
||||
} from '../api';
|
||||
import Radar from '../components/Radar';
|
||||
import { Entry } from '../utils/types';
|
||||
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
const useTechRadarLoader = (id: string | undefined) => {
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const techRadarApi = useApi(techRadarApiRef);
|
||||
@@ -45,37 +45,44 @@ const useTechRadarLoader = (id: string | undefined) => {
|
||||
return { loading, value, error };
|
||||
};
|
||||
|
||||
function matchFilter(filter?: string): (entry: RadarEntry) => boolean {
|
||||
const terms = filter
|
||||
?.toLocaleLowerCase('en-US')
|
||||
.split(/\s/)
|
||||
.map(e => e.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (!terms?.length) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
return entry => {
|
||||
const text = `${entry.title} ${
|
||||
entry.timeline[0]?.description || ''
|
||||
}`.toLocaleLowerCase('en-US');
|
||||
return terms.every(term => text.includes(term));
|
||||
};
|
||||
}
|
||||
|
||||
const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
|
||||
const { loading, error, value: data } = useTechRadarLoader(props.id);
|
||||
|
||||
const mapToEntries = (
|
||||
loaderResponse: TechRadarLoaderResponse | undefined,
|
||||
loaderResponse: TechRadarLoaderResponse,
|
||||
): Array<Entry> => {
|
||||
let filteredArray = loaderResponse!.entries;
|
||||
if (props.searchText) {
|
||||
// Compare the name or the description with the search input text
|
||||
filteredArray = loaderResponse!.entries.filter(
|
||||
element =>
|
||||
element.title
|
||||
.toLowerCase()
|
||||
.includes(props.searchText!.toLowerCase()) ||
|
||||
element.timeline[0].description
|
||||
?.toLowerCase()
|
||||
.includes(props.searchText!.toLowerCase()),
|
||||
);
|
||||
}
|
||||
return filteredArray.map(entry => {
|
||||
return {
|
||||
return loaderResponse.entries
|
||||
.filter(matchFilter(props.searchText))
|
||||
.map(entry => ({
|
||||
id: entry.key,
|
||||
quadrant: loaderResponse!.quadrants.find(q => q.id === entry.quadrant)!,
|
||||
quadrant: loaderResponse.quadrants.find(q => q.id === entry.quadrant)!,
|
||||
title: entry.title,
|
||||
ring: loaderResponse!.rings.find(
|
||||
ring: loaderResponse.rings.find(
|
||||
r => r.id === entry.timeline[0].ringId,
|
||||
)!,
|
||||
timeline: entry.timeline.map(e => {
|
||||
return {
|
||||
date: e.date,
|
||||
ring: loaderResponse!.rings.find(a => a.id === e.ringId)!,
|
||||
ring: loaderResponse.rings.find(a => a.id === e.ringId)!,
|
||||
description: e.description,
|
||||
moved: e.moved,
|
||||
};
|
||||
@@ -83,18 +90,17 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
|
||||
moved: entry.timeline[0].moved,
|
||||
description: entry.description || entry.timeline[0].description,
|
||||
url: entry.url,
|
||||
};
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Progress />}
|
||||
{!loading && !error && (
|
||||
{!loading && !error && data && (
|
||||
<Radar
|
||||
{...props}
|
||||
rings={data!.rings}
|
||||
quadrants={data!.quadrants}
|
||||
rings={data.rings}
|
||||
quadrants={data.quadrants}
|
||||
entries={mapToEntries(data)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
import type { Quadrant, Ring, Entry } from '../../utils/types';
|
||||
import React from 'react';
|
||||
import { WithLink } from '../../utils/components';
|
||||
import type { Entry, Quadrant, Ring } from '../../utils/types';
|
||||
import { RadarDescription } from '../RadarDescription';
|
||||
|
||||
type Segments = {
|
||||
@@ -41,13 +41,13 @@ const useStyles = makeStyles<Theme>(theme => ({
|
||||
width: '100%',
|
||||
overflow: 'scroll',
|
||||
scrollbarWidth: 'thin',
|
||||
// pointerEvents: 'none',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
quadrantHeading: {
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing(8 / (18 * 0.375)),
|
||||
marginBottom: theme.spacing(2),
|
||||
fontSize: '18px',
|
||||
},
|
||||
rings: {
|
||||
@@ -58,12 +58,16 @@ const useStyles = makeStyles<Theme>(theme => ({
|
||||
pageBreakInside: 'avoid',
|
||||
'-webkit-column-break-inside': 'avoid',
|
||||
fontSize: '12px',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
ringEmpty: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
ringHeading: {
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing(8 / (12 * 0.375)),
|
||||
marginBottom: theme.spacing(1),
|
||||
fontSize: '12px',
|
||||
fontWeight: 800,
|
||||
},
|
||||
@@ -177,19 +181,15 @@ const RadarLegend = (props: Props): JSX.Element => {
|
||||
<div data-testid="radar-ring" key={ring.id} className={classes.ring}>
|
||||
<h3 className={classes.ringHeading}>{ring.name}</h3>
|
||||
{entries.length === 0 ? (
|
||||
<p>(empty)</p>
|
||||
<p className={classes.ringEmpty}>(empty)</p>
|
||||
) : (
|
||||
<ol className={classes.ringList}>
|
||||
{entries.map(entry => (
|
||||
<li
|
||||
key={entry.id}
|
||||
value={(entry.index || 0) + 1}
|
||||
onMouseEnter={
|
||||
onEntryMouseEnter && (() => onEntryMouseEnter(entry))
|
||||
}
|
||||
onMouseLeave={
|
||||
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
|
||||
}
|
||||
onMouseEnter={() => onEntryMouseEnter?.(entry)}
|
||||
onMouseLeave={() => onEntryMouseLeave?.(entry)}
|
||||
>
|
||||
<RadarLegendLink
|
||||
url={entry.url}
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Grid, makeStyles, TextField } from '@material-ui/core';
|
||||
import RadarComponent from '../components/RadarComponent';
|
||||
import { TechRadarComponentProps } from '../api';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
Page,
|
||||
Header,
|
||||
Page,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import { Grid, Input, makeStyles } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { TechRadarComponentProps } from '../api';
|
||||
import RadarComponent from '../components/RadarComponent';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
overflowXScroll: {
|
||||
@@ -47,24 +47,16 @@ export const RadarPage = ({
|
||||
const classes = useStyles();
|
||||
const [searchText, setSearchText] = React.useState('');
|
||||
|
||||
const searchInput = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
setSearchText(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title={title} subtitle={subtitle} />
|
||||
<Content className={classes.overflowXScroll}>
|
||||
<ContentHeader title={pageTitle}>
|
||||
<TextField
|
||||
id="standard-search"
|
||||
label="Search field"
|
||||
<Input
|
||||
id="tech-radar-filter"
|
||||
type="search"
|
||||
onChange={e => {
|
||||
searchInput(e);
|
||||
}}
|
||||
placeholder="Filter"
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
/>
|
||||
<SupportButton>
|
||||
This is used for visualizing the official guidelines of different
|
||||
|
||||
@@ -55,12 +55,8 @@ const RadarPlot = (props: Props): JSX.Element => {
|
||||
quadrants={quadrants}
|
||||
rings={rings}
|
||||
entries={entries}
|
||||
onEntryMouseEnter={
|
||||
onEntryMouseEnter && (entry => onEntryMouseEnter(entry))
|
||||
}
|
||||
onEntryMouseLeave={
|
||||
onEntryMouseLeave && (entry => onEntryMouseLeave(entry))
|
||||
}
|
||||
onEntryMouseEnter={entry => onEntryMouseEnter?.(entry)}
|
||||
onEntryMouseLeave={entry => onEntryMouseLeave?.(entry)}
|
||||
/>
|
||||
<g transform={`translate(${width / 2}, ${height / 2})`}>
|
||||
<RadarGrid radius={radius} rings={rings} />
|
||||
@@ -76,8 +72,8 @@ const RadarPlot = (props: Props): JSX.Element => {
|
||||
description={entry.description}
|
||||
moved={entry.moved}
|
||||
title={entry.title}
|
||||
onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))}
|
||||
onMouseLeave={onEntryMouseLeave && (() => onEntryMouseLeave(entry))}
|
||||
onMouseEnter={() => onEntryMouseEnter?.(entry)}
|
||||
onMouseLeave={() => onEntryMouseLeave?.(entry)}
|
||||
/>
|
||||
))}
|
||||
<RadarBubble
|
||||
|
||||
Reference in New Issue
Block a user