diff --git a/package.json b/package.json index 5dc6477c85..afa0574df2 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,13 @@ "build:lib": "parcel build --target lib", "typedefs": "npx -p typescript tsc ui/**/*.ts ui/**/*.tsx --resolveJsonModule --skipLibCheck --esModuleInterop --jsx react-jsx --declaration --allowJs --emitDeclarationOnly --outDir dist", "start": "parcel serve --port 4567 ui/index.html", - "lint": "eslint ui", + "lint": "eslint ui && npm run typecheck", "test": "jest", "watch": "jest --runInBand --watch", "coverage": "jest --coverage", "storybook": "start-storybook -s ui/ -p 6006", - "build-storybook": "build-storybook ui/assets" + "build-storybook": "build-storybook ui/assets", + "typecheck": "tsc --noemit" }, "repository": { "type": "git", diff --git a/tsconfig.json b/tsconfig.json index a95a4e2cd8..8046da858b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "esModuleInterop": true, "jsx": "react", - "resolveJsonModule": true - } + "resolveJsonModule": true, + "skipLibCheck": true, + }, } diff --git a/ui/App.tsx b/ui/App.tsx index eec2ee76bf..8821066e77 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -26,6 +26,7 @@ import Automations from "./pages/v2/Automations"; import BucketDetail from "./pages/v2/BucketDetail"; import FluxRuntime from "./pages/v2/FluxRuntime"; import GitRepositoryDetail from "./pages/v2/GitRepositoryDetail"; +import HelmChartDetail from "./pages/v2/HelmChartDetail"; import HelmReleaseDetail from "./pages/v2/HelmReleaseDetail"; import HelmRepositoryDetail from "./pages/v2/HelmRepositoryDetail"; import KustomizationDetail from "./pages/v2/KustomizationDetail"; @@ -73,6 +74,11 @@ const App = () => ( path={V2Routes.HelmRelease} component={withName(HelmReleaseDetail)} /> + diff --git a/ui/components/AutomationsTable.tsx b/ui/components/AutomationsTable.tsx index 9ad284bdfe..3039713042 100644 --- a/ui/components/AutomationsTable.tsx +++ b/ui/components/AutomationsTable.tsx @@ -4,10 +4,8 @@ import { Automation } from "../hooks/automations"; import { HelmRelease, SourceRefSourceKind } from "../lib/api/core/types.pb"; import { formatURL } from "../lib/nav"; import { AutomationType, V2Routes } from "../lib/types"; -import DataTable, { SortType } from "./DataTable"; +import DataTable, { Field, SortType } from "./DataTable"; import FilterableTable, { filterConfigForType } from "./FilterableTable"; -import FilterDialogButton from "./FilterDialogButton"; -import Flex from "./Flex"; import KubeStatusIndicator, { computeMessage, computeReady, @@ -22,13 +20,11 @@ type Props = { }; function AutomationsTable({ className, automations }: Props) { - const [filterDialogOpen, setFilterDialog] = React.useState(false); - const initialFilterState = { ...filterConfigForType(automations), }; - const fields = [ + const fields: Field[] = [ { label: "Name", value: (k) => { @@ -47,9 +43,9 @@ function AutomationsTable({ className, automations }: Props) { ); }, - sortType: SortType.string, sortValue: ({ name }) => name, width: 64, + textSearchable: true, }, { label: "Type", @@ -84,6 +80,7 @@ function AutomationsTable({ className, automations }: Props) { ); }, + sortValue: (a: Automation) => a.sourceRef?.name, width: 160, }, { @@ -104,6 +101,7 @@ function AutomationsTable({ className, automations }: Props) { label: "Message", value: (a: Automation) => computeMessage(a.conditions), width: 360, + sortValue: ({ conditions }) => computeMessage(conditions), }, { label: "Revision", @@ -115,17 +113,10 @@ function AutomationsTable({ className, automations }: Props) { return (
- - setFilterDialog(!filterDialogOpen)} - /> - setFilterDialog(false)} />
); diff --git a/ui/components/DataTable.tsx b/ui/components/DataTable.tsx index 58a6d3d3d2..2f3dc8f367 100644 --- a/ui/components/DataTable.tsx +++ b/ui/components/DataTable.tsx @@ -32,6 +32,7 @@ export type Field = { sortType?: SortType; sortValue?: Sorter; width?: number; + textSearchable?: boolean; }; /** DataTable Properties */ diff --git a/ui/components/FilterableTable.tsx b/ui/components/FilterableTable.tsx index 7995355019..d8875d40b4 100644 --- a/ui/components/FilterableTable.tsx +++ b/ui/components/FilterableTable.tsx @@ -9,7 +9,9 @@ import FilterDialog, { formStateToFilters, initialFormState, } from "./FilterDialog"; +import FilterDialogButton from "./FilterDialogButton"; import Flex from "./Flex"; +import SearchField from "./SearchField"; type Props = { className?: string; @@ -58,15 +60,53 @@ export function filterRows(rows: T[], filters: FilterConfig) { }); } -function toPairs(state: DialogFormState): string[] { - const result = _.map(state, (val, key) => (val ? key : null)); +function filterText(rows, fields: Field[], textFilters: State["textFilters"]) { + if (textFilters.length === 0) { + return rows; + } + + return _.filter(rows, (row) => { + let matches = false; + for (const colName in row) { + const value = row[colName]; + + const field = _.find(fields, (f) => { + if (typeof f.value === "string") { + return f.value === colName; + } + + if (f.sortValue) { + return f.sortValue(row) === value; + } + }); + + if (!field || !field.textSearchable) { + continue; + } + + // This allows us to look for a fragment in the string. + // For example, if the user searches for "pod", the "podinfo" kustomization should be returned. + for (const filterString of textFilters) { + if (_.includes(value, filterString)) { + matches = true; + } + } + } + + return matches; + }); +} + +function toPairs(state: State): string[] { + const result = _.map(state.formState, (val, key) => (val ? key : null)); const out = _.compact(result); - return out; + return _.concat(out, state.textFilters); } type State = { filters: FilterConfig; formState: DialogFormState; + textFilters: string[]; }; function FilterableTable({ @@ -75,13 +115,16 @@ function FilterableTable({ rows, filters, dialogOpen, - onDialogClose, }: Props) { + const [filterDialogOpen, setFilterDialog] = React.useState(dialogOpen); const [filterState, setFilterState] = React.useState({ filters, formState: initialFormState(filters), + textFilters: [], }); - const filtered = filterRows(rows, filterState.filters); + let filtered = filterRows(rows, filterState.filters); + filtered = filterText(filtered, fields, filterState.textFilters); + const chips = toPairs(filterState); const handleChipRemove = (chips: string[]) => { const next = { @@ -94,28 +137,56 @@ function FilterableTable({ const filters = formStateToFilters(next.formState); - setFilterState({ formState: next.formState, filters }); + const textFilters = _.filter( + next.textFilters, + (f) => !_.includes(chips, f) + ); + + setFilterState({ formState: next.formState, filters, textFilters }); + }; + + const handleTextSearchSubmit = (val: string) => { + setFilterState({ + ...filterState, + textFilters: _.uniq(_.concat(filterState.textFilters, val)), + }); + }; + + const handleClearAll = () => { + setFilterState({ + filters: {}, + formState: initialFormState(filters), + textFilters: [], + }); + }; + + const handleFilterSelect = (filters, formState) => { + setFilterState({ ...filterState, filters, formState }); }; return (
- - setFilterState({ filters: {}, formState: initialFormState(filters) }) - } - /> + + + + + setFilterDialog(!filterDialogOpen)} + /> + + { - setFilterState({ filters, formState }); - }} + onClose={() => setFilterDialog(!filterDialogOpen)} + onFilterSelect={handleFilterSelect} filterList={filters} formState={filterState.formState} - open={dialogOpen} + open={filterDialogOpen} />
diff --git a/ui/components/Link.tsx b/ui/components/Link.tsx index 4f6194dbbf..09bc0c4c6d 100644 --- a/ui/components/Link.tsx +++ b/ui/components/Link.tsx @@ -11,7 +11,7 @@ type Props = { href?: any; newTab?: boolean; textProps?: TextProps; - onClick?: () => void; + onClick?: (ev: any) => void; }; function Link({ diff --git a/ui/components/Page.tsx b/ui/components/Page.tsx index aafea68369..cb38774ee6 100644 --- a/ui/components/Page.tsx +++ b/ui/components/Page.tsx @@ -25,6 +25,7 @@ export const Content = styled.div` box-sizing: border-box; margin: 0 auto; min-width: 1260px; + min-height: 480px; padding-bottom: ${(props) => props.theme.spacing.medium}; padding-left: ${(props) => props.theme.spacing.large}; padding-top: ${(props) => props.theme.spacing.large}; diff --git a/ui/components/SearchField.tsx b/ui/components/SearchField.tsx new file mode 100644 index 0000000000..c6b972aa45 --- /dev/null +++ b/ui/components/SearchField.tsx @@ -0,0 +1,77 @@ +import * as React from "react"; +import styled from "styled-components"; +import { Button } from ".."; +import Flex from "./Flex"; +import Icon, { IconType } from "./Icon"; +import Input from "./Input"; + +type Props = { + className?: string; + + onSubmit: (val: string) => void; +}; + +const Expander = styled(({ expanded, className, children }) => ( +
{children}
+))` + width: 0px; + transition: width 0.3s ease-in-out; + + &.expanded { + width: 200px; + } +`; + +function SearchField({ className, onSubmit }: Props) { + const inputRef = React.createRef(); + const [expanded, setExpanded] = React.useState(false); + const [value, setValue] = React.useState(""); + + const handleExpand = (ev) => { + ev.preventDefault(); + + if (!expanded) { + inputRef.current.focus(); + } else { + inputRef.current.blur(); + } + setValue(""); + setExpanded(!expanded); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + setValue(""); + onSubmit(value); + }; + + return ( + + + +
+ setValue(ev.target.value)} + /> +
+
+
+ ); +} + +export default styled(SearchField).attrs({ className: SearchField.name })``; diff --git a/ui/components/SourceDetail.tsx b/ui/components/SourceDetail.tsx index ce40a9e9fb..34c5dc3ae9 100644 --- a/ui/components/SourceDetail.tsx +++ b/ui/components/SourceDetail.tsx @@ -31,7 +31,7 @@ function SourceDetail({ className, name, info, type }: Props) { return ; } - const s = _.find(sources, { name }); + const s = _.find(sources, { name, type }); if (!s) { return ( @@ -70,12 +70,6 @@ function SourceDetail({ className, name, info, type }: Props) { {error && ( )} -
- {s.type} -
-
- -
diff --git a/ui/components/SourcesTable.tsx b/ui/components/SourcesTable.tsx index 02b3a77609..1717c99c3c 100644 --- a/ui/components/SourcesTable.tsx +++ b/ui/components/SourcesTable.tsx @@ -13,8 +13,6 @@ import { Source } from "../lib/types"; import { convertGitURLToGitProvider } from "../lib/utils"; import { SortType } from "./DataTable"; import FilterableTable, { filterConfigForType } from "./FilterableTable"; -import FilterDialogButton from "./FilterDialogButton"; -import Flex from "./Flex"; import KubeStatusIndicator, { computeMessage } from "./KubeStatusIndicator"; import Link from "./Link"; import Timestamp from "./Timestamp"; @@ -36,11 +34,6 @@ function SourcesTable({ className, sources }: Props) { return (
- - setFilterDialog(!filterDialogOpen)} - /> - s.name || "", width: 96, + textSearchable: true, }, { label: "Type", value: "type", width: 96 }, { diff --git a/ui/components/__tests__/FilterableTable.test.tsx b/ui/components/__tests__/FilterableTable.test.tsx index bdac916b03..6d3a52f57e 100644 --- a/ui/components/__tests__/FilterableTable.test.tsx +++ b/ui/components/__tests__/FilterableTable.test.tsx @@ -3,12 +3,18 @@ import "jest-styled-components"; import _ from "lodash"; import React from "react"; import { withTheme } from "../../lib/test-utils"; -import { SortType } from "../DataTable"; +import { Field } from "../DataTable"; import FilterableTable, { filterConfigForType, filterRows, } from "../FilterableTable"; -import Icon, { IconType } from "../Icon"; + +const addTextSearchInput = (term: string) => { + const input = document.getElementById("table-search"); + fireEvent.input(input, { target: { value: term } }); + const form = document.getElementsByTagName("form")[0]; + fireEvent.submit(form); +}; describe("FilterableTable", () => { const rows = [ @@ -38,10 +44,11 @@ describe("FilterableTable", () => { }, ]; - const fields = [ + const fields: Field[] = [ { label: "Name", value: "name", + textSearchable: true, }, { label: "Type", @@ -50,18 +57,6 @@ describe("FilterableTable", () => { { label: "Status", value: "success", - filterType: SortType.bool, - displayText: (r) => - r.success ? ( - - ) : ( - - ), }, { label: "Qty", @@ -245,4 +240,122 @@ describe("FilterableTable", () => { expect(tableRows3).toHaveLength(rows.length); }); + + it("should add a text filter", () => { + const initialFilterState = { + ...filterConfigForType(rows), + }; + render( + withTheme( + + ) + ); + + const searchTerms = "my-criterion"; + addTextSearchInput(searchTerms); + + const textChip = screen.queryByText(searchTerms); + expect(textChip).toBeTruthy(); + expect(textChip.innerHTML).toContain(searchTerms); + }); + it("should remove a text filter", () => { + const initialFilterState = { + ...filterConfigForType(rows), + }; + render( + withTheme( + + ) + ); + + const searchTerms = "my-criterion"; + addTextSearchInput(searchTerms); + + const textChip = screen.queryByText(searchTerms); + + const svgButton = textChip.parentElement.getElementsByTagName("svg")[0]; + fireEvent.click(svgButton); + + expect(screen.queryByText(searchTerms)).toBeFalsy(); + }); + it("filters by a text field", () => { + const initialFilterState = { + ...filterConfigForType(rows), + }; + + render( + withTheme( + + ) + ); + + const searchTerms = rows[0].name; + addTextSearchInput(searchTerms); + + const tableRows = document.querySelectorAll("tbody tr"); + expect(tableRows).toHaveLength(1); + expect(tableRows[0].innerHTML).toContain(searchTerms); + }); + it("filters by multiple text fields", () => { + render( + withTheme( + + ) + ); + + const term1 = rows[0].name; + addTextSearchInput(term1); + + const term2 = rows[3].name; + addTextSearchInput(term2); + + const tableRows = document.querySelectorAll("tbody tr"); + expect(tableRows).toHaveLength(2); + expect(tableRows[0].innerHTML).toContain(term1); + expect(tableRows[1].innerHTML).toContain(term2); + }); + it("filters by fragments of text fields", () => { + render( + withTheme( + + ) + ); + + const row = rows[0]; + const term = row.name.slice(0, 2); + addTextSearchInput(term); + + const tableRows = document.querySelectorAll("tbody tr"); + expect(tableRows).toHaveLength(1); + expect(tableRows[0].innerHTML).toContain(row.name); + }); }); diff --git a/ui/components/__tests__/__snapshots__/Page.test.tsx.snap b/ui/components/__tests__/__snapshots__/Page.test.tsx.snap index 270820e03c..24d1466f77 100644 --- a/ui/components/__tests__/__snapshots__/Page.test.tsx.snap +++ b/ui/components/__tests__/__snapshots__/Page.test.tsx.snap @@ -58,6 +58,7 @@ exports[`Page snapshots default 1`] = ` box-sizing: border-box; margin: 0 auto; min-width: 1260px; + min-height: 480px; padding-bottom: 24px; padding-left: 32px; padding-top: 32px; diff --git a/ui/hooks/automations.ts b/ui/hooks/automations.ts index 9f5d493ac8..d2d3422aae 100644 --- a/ui/hooks/automations.ts +++ b/ui/hooks/automations.ts @@ -24,7 +24,10 @@ export function useListAutomations(namespace = WeGONamespace) { api.ListHelmReleases({ namespace }), ]; - return Promise.all(p).then((result) => { + // The typescript CLI complains about Promise.all, + // but VSCode does not. Supress the CLI error here. + // useQuery will still give us the correct types. + return Promise.all(p).then((result) => { const [kustRes, helmRes] = result; const kustomizations = (kustRes as ListKustomizationsResponse) diff --git a/ui/hooks/sources.ts b/ui/hooks/sources.ts index fe739d1e25..6eb70af2ec 100644 --- a/ui/hooks/sources.ts +++ b/ui/hooks/sources.ts @@ -5,6 +5,7 @@ import { AppContext } from "../contexts/AppContext"; import { ListBucketsResponse, ListGitRepositoriesResponse, + ListHelmChartsResponse, ListHelmRepositoriesResponse, } from "../lib/api/core/core.pb"; import { SourceRefSourceKind } from "../lib/api/core/types.pb"; @@ -23,13 +24,15 @@ export function useListSources( api.ListGitRepositories({ namespace }), api.ListHelmRepositories({ namespace }), api.ListBuckets({ namespace }), + api.ListHelmCharts({ namespace }), ]; - return Promise.all(p).then((result) => { - const [repoRes, helmReleases, bucketsRes] = result; + return Promise.all(p).then((result) => { + const [repoRes, helmReleases, bucketsRes, chartRes] = result; const repos = (repoRes as ListGitRepositoriesResponse).gitRepositories; const hrs = (helmReleases as ListHelmRepositoriesResponse) .helmRepositories; const buckets = (bucketsRes as ListBucketsResponse).buckets; + const charts = (chartRes as ListHelmChartsResponse).helmCharts; return [ ..._.map(repos, (r) => ({ @@ -44,6 +47,10 @@ export function useListSources( ...b, type: SourceRefSourceKind.Bucket, })), + ..._.map(charts, (ch) => ({ + ...ch, + type: SourceRefSourceKind.HelmChart, + })), ]; }); }, diff --git a/ui/index.ts b/ui/index.ts index 38275524a4..c367c793de 100644 --- a/ui/index.ts +++ b/ui/index.ts @@ -1,24 +1,23 @@ import Button from "./components/Button"; import Footer from "./components/Footer"; import GithubDeviceAuthModal from "./components/GithubDeviceAuthModal"; +import Icon, { IconType } from "./components/Icon"; import LoadingPage from "./components/LoadingPage"; import RepoInputWithAuth from "./components/RepoInputWithAuth"; import UserSettings from "./components/UserSettings"; -import Icon, { IconType } from "./components/Icon"; import AppContextProvider from "./contexts/AppContext"; import AuthContextProvider, { Auth, AuthCheck } from "./contexts/AuthContext"; +import CallbackStateContextProvider from "./contexts/CallbackStateContext"; import FeatureFlagsContextProvider, { FeatureFlags, } from "./contexts/FeatureFlags"; -import CallbackStateContextProvider from "./contexts/CallbackStateContext"; -import useApplications from "./hooks/applications"; import { Applications as applicationsClient } from "./lib/api/applications/applications.pb"; import { clearCallbackState, getCallbackState, getProviderToken, } from "./lib/storage"; -import { theme, muiTheme } from "./lib/theme"; +import { muiTheme, theme } from "./lib/theme"; import OAuthCallback from "./pages/OAuthCallback"; import SignIn from "./pages/SignIn"; @@ -34,7 +33,6 @@ export { LoadingPage, theme, muiTheme, - useApplications, Footer, RepoInputWithAuth, CallbackStateContextProvider, diff --git a/ui/pages/v2/BucketDetail.tsx b/ui/pages/v2/BucketDetail.tsx index f8025a6456..7f16b1b12e 100644 --- a/ui/pages/v2/BucketDetail.tsx +++ b/ui/pages/v2/BucketDetail.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import Interval from "../../components/Interval"; import Page, { Content, TitleBar } from "../../components/Page"; import SourceDetail from "../../components/SourceDetail"; +import Timestamp from "../../components/Timestamp"; import { Bucket, SourceRefSourceKind } from "../../lib/api/core/types.pb"; type Props = { @@ -22,9 +23,9 @@ function BucketDetail({ className, name, namespace }: Props) { info={(b: Bucket = {}) => [ ["Endpoint", b.endpoint], ["Bucket Name", b.name], - ["Last Updated", ""], + ["Last Updated", ], ["Interval", ], - ["Cluster", ""], + ["Cluster", "Default"], ["Namespace", b.namespace], ]} /> diff --git a/ui/pages/v2/GitRepositoryDetail.tsx b/ui/pages/v2/GitRepositoryDetail.tsx index f209c4df9d..9e09ca486d 100644 --- a/ui/pages/v2/GitRepositoryDetail.tsx +++ b/ui/pages/v2/GitRepositoryDetail.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import styled from "styled-components"; import Page, { Content, TitleBar } from "../../components/Page"; import SourceDetail from "../../components/SourceDetail"; +import Timestamp from "../../components/Timestamp"; import { GitRepository, SourceRefSourceKind, @@ -23,8 +24,8 @@ function GitRepositoryDetail({ className, name, namespace }: Props) { info={(s: GitRepository) => [ ["URL", s.url], ["Ref", s.reference.branch], - ["Last Updated", ""], - ["Cluster", ""], + ["Last Updated", ], + ["Cluster", "Default"], ["Namespace", s.namespace], ]} /> diff --git a/ui/pages/v2/HelmChartDetail.tsx b/ui/pages/v2/HelmChartDetail.tsx new file mode 100644 index 0000000000..85b07c70f6 --- /dev/null +++ b/ui/pages/v2/HelmChartDetail.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; +import styled from "styled-components"; +import Interval from "../../components/Interval"; +import Page, { Content, TitleBar } from "../../components/Page"; +import SourceDetail from "../../components/SourceDetail"; +import Timestamp from "../../components/Timestamp"; +import { HelmChart, SourceRefSourceKind } from "../../lib/api/core/types.pb"; + +type Props = { + className?: string; + name: string; + namespace: string; +}; + +function HelmChartDetail({ className, name, namespace }: Props) { + return ( + + [ + ["Chart", ch?.chart], + ["Ref", ch?.sourceRef?.name], + ["Last Updated", ], + ["Interval", ], + ["Namespace", ch?.namespace], + ]} + /> + + ); +} + +export default styled(HelmChartDetail).attrs({ + className: HelmChartDetail.name, +})` + ${TitleBar} { + margin-bottom: 0; + } + + ${Content} { + padding-top: 0; + } +`; diff --git a/ui/pages/v2/HelmReleaseDetail.tsx b/ui/pages/v2/HelmReleaseDetail.tsx index ce05eafa64..284c916ae6 100644 --- a/ui/pages/v2/HelmReleaseDetail.tsx +++ b/ui/pages/v2/HelmReleaseDetail.tsx @@ -4,10 +4,6 @@ import Flex from "../../components/Flex"; import Heading from "../../components/Heading"; import InfoList from "../../components/InfoList"; import Interval from "../../components/Interval"; -import { - computeMessage, - computeReady, -} from "../../components/KubeStatusIndicator"; import Page from "../../components/Page"; import PageStatus from "../../components/PageStatus"; import ReconciledObjectsTable from "../../components/ReconciledObjectsTable"; @@ -31,8 +27,6 @@ const Info = styled.div` function HelmReleaseDetail({ className, name }: Props) { const { data, isLoading, error } = useGetHelmRelease(name); const helmRelease = data?.helmRelease; - const ok = computeReady(helmRelease?.conditions); - const msg = computeMessage(helmRelease?.conditions); return ( @@ -47,7 +41,7 @@ function HelmReleaseDetail({ className, name }: Props) { , ], @@ -57,7 +51,7 @@ function HelmReleaseDetail({ className, name }: Props) { ]} /> - + [ ["URL", hr.url], - ["Last Updated", ""], + ["Last Updated", ], ["Interval", ], - ["Cluster", ""], + ["Cluster", "Default"], ["Namespace", hr.namespace], ]} /> diff --git a/ui/pages/v2/KustomizationDetail.tsx b/ui/pages/v2/KustomizationDetail.tsx index b1fd69a113..0bcd4df77f 100644 --- a/ui/pages/v2/KustomizationDetail.tsx +++ b/ui/pages/v2/KustomizationDetail.tsx @@ -42,7 +42,7 @@ function KustomizationDetail({ className, name }: Props) { items={[ ["Source", ], ["Applied Revision", kustomization?.lastAppliedRevision], - ["Cluster", ""], + ["Cluster", "Default"], ["Path", kustomization?.path], ["Interval", ], ["Last Updated At", kustomization?.lastHandledReconciledAt], diff --git a/ui/stories/ChipGroup.stories.tsx b/ui/stories/ChipGroup.stories.tsx index ca52326e29..f59c82572c 100644 --- a/ui/stories/ChipGroup.stories.tsx +++ b/ui/stories/ChipGroup.stories.tsx @@ -23,11 +23,7 @@ const Template: Story = (args) => { ]); return ( - + ); };