diff --git a/client/web/src/enterprise/insights/components/insights-view-grid/components/SmartInsight.tsx b/client/web/src/enterprise/insights/components/insights-view-grid/components/SmartInsight.tsx index 659697d2258..c0b7073555c 100644 --- a/client/web/src/enterprise/insights/components/insights-view-grid/components/SmartInsight.tsx +++ b/client/web/src/enterprise/insights/components/insights-view-grid/components/SmartInsight.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect } from 'react' +import { HTMLAttributes, forwardRef, useEffect } from 'react' import { useMergeRefs } from 'use-callback-ref' @@ -10,17 +10,17 @@ import { Insight, isBackendInsight } from '../../../core' import { BackendInsightView } from './backend-insight/BackendInsight' import { BuiltInInsight } from './built-in-insight/BuiltInInsight' -export interface SmartInsightProps extends TelemetryProps, React.HTMLAttributes { +export interface SmartInsightProps extends TelemetryProps, HTMLAttributes { insight: Insight resizing?: boolean } /** - * Render smart insight with (gql or extension api) fetcher and independent mutation + * Render smart insight with ф(gql or extension api) fetcher and independent mutation * actions. */ export const SmartInsight = forwardRef((props, reference) => { - const { insight, resizing = false, telemetryService, ...otherProps } = props + const { insight, resizing = false, telemetryService, ...attributes } = props const mergedReference = useMergeRefs([reference]) const search = useSearchParameters() @@ -41,7 +41,7 @@ export const SmartInsight = forwardRef((props, r insight={insight} resizing={resizing} telemetryService={telemetryService} - {...otherProps} + {...attributes} /> ) } @@ -49,11 +49,11 @@ export const SmartInsight = forwardRef((props, r // Lang-stats insight is handled by built-in fetchers return ( ) }) diff --git a/client/web/src/enterprise/insights/components/insights-view-grid/components/backend-insight/BackendInsight.tsx b/client/web/src/enterprise/insights/components/insights-view-grid/components/backend-insight/BackendInsight.tsx index e6dde858e52..af0181053c8 100644 --- a/client/web/src/enterprise/insights/components/insights-view-grid/components/backend-insight/BackendInsight.tsx +++ b/client/web/src/enterprise/insights/components/insights-view-grid/components/backend-insight/BackendInsight.tsx @@ -41,7 +41,7 @@ interface BackendInsightProps extends TelemetryProps, HTMLAttributes((props, ref) => { - const { telemetryService, insight, resizing, children, ...otherProps } = props + const { telemetryService, insight, resizing, children, className, ...attributes } = props const { currentDashboard, dashboards } = useContext(InsightContext) const { createInsight, updateInsight } = useContext(CodeInsightsBackendContext) @@ -50,9 +50,11 @@ export const BackendInsightView = forwardRef(( const { wasEverVisible, isVisible } = useVisibility(cardElementRef) const seriesToggleState = useSeriesToggle() + // Original insight filters values that are stored in setting subject with insight // configuration object, They are updated whenever the user clicks update/save button const [originalInsightFilters, setOriginalInsightFilters] = useState(insight.filters) + // Live valid filters from filter form. They are updated whenever the user is changing // filter value in filters fields. const [filters, setFilters] = useState(originalInsightFilters) @@ -160,13 +162,14 @@ export const BackendInsightView = forwardRef(( return ( (( to={`${window.location.origin}/insights/insight/${insight.id}`} target="_blank" rel="noopener noreferrer" - aria-label="Go to the insight page" > {insight.title} diff --git a/client/web/src/enterprise/insights/components/insights-view-grid/components/built-in-insight/BuiltInInsight.tsx b/client/web/src/enterprise/insights/components/insights-view-grid/components/built-in-insight/BuiltInInsight.tsx index d63e9122d61..ebdd99b2b80 100644 --- a/client/web/src/enterprise/insights/components/insights-view-grid/components/built-in-insight/BuiltInInsight.tsx +++ b/client/web/src/enterprise/insights/components/insights-view-grid/components/built-in-insight/BuiltInInsight.tsx @@ -1,10 +1,10 @@ -import React, { Ref, useContext, useMemo, useRef, useState } from 'react' +import React, { Ref, useContext, useMemo, useState } from 'react' import { useMergeRefs } from 'use-callback-ref' import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts' import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { Link, useDeepMemo, ParentSize } from '@sourcegraph/wildcard' +import { Link, ParentSize, useDeepMemo } from '@sourcegraph/wildcard' import { useSeriesToggle } from '../../../../../../insights/utils/use-series-toggle' import { CodeInsightsBackendContext, LangStatsInsight } from '../../../../core' @@ -43,19 +43,16 @@ interface BuiltInInsightProps extends TelemetryProps, React.HTMLAttributes(null) - const mergedInsightCardReference = useMergeRefs([insightCardReference, innerRef]) - + const cardRef = useMergeRefs([innerRef]) const cachedInsight = useDeepMemo(insight) - const { state, isVisible } = useInsightData( useMemo(() => () => getBuiltInInsightData({ insight: cachedInsight }), [getBuiltInInsightData, cachedInsight]), - insightCardReference + cardRef ) // Visual line chart settings @@ -68,12 +65,13 @@ export function BuiltInInsight(props: BuiltInInsightProps): React.ReactElement { return ( {insight.title} @@ -140,7 +137,7 @@ export function BuiltInInsight(props: BuiltInInsightProps): React.ReactElement { { // Passing children props explicitly to render any top-level content like // resize-handler from the react-grid-layout library - isVisible && otherProps.children + isVisible && children } ) diff --git a/client/web/src/enterprise/insights/components/insights-view-grid/components/view-grid/ViewGrid.tsx b/client/web/src/enterprise/insights/components/insights-view-grid/components/view-grid/ViewGrid.tsx index 204d729b9ba..20dcd5c45b7 100644 --- a/client/web/src/enterprise/insights/components/insights-view-grid/components/view-grid/ViewGrid.tsx +++ b/client/web/src/enterprise/insights/components/insights-view-grid/components/view-grid/ViewGrid.tsx @@ -1,34 +1,30 @@ -import React, { PropsWithChildren, useCallback, useMemo } from 'react' +import { FC, PropsWithChildren, useCallback, useLayoutEffect, useMemo, useRef } from 'react' import classNames from 'classnames' import { noop } from 'lodash' import { - Layout, Layout as ReactGridLayout, - Layouts, Layouts as ReactGridLayouts, - Responsive, - WidthProvider, + Responsive as ResponsiveGridLayout, } from 'react-grid-layout' import { isFirefox } from '@sourcegraph/common' +import { useMeasure } from '@sourcegraph/wildcard' import styles from './ViewGrid.module.scss' -// TODO use a method to get width that also triggers when file explorer is closed -// (WidthProvider only listens to window resize events) -const ResponsiveGridLayout = WidthProvider(Responsive) - export const BREAKPOINTS_NAMES = ['xs', 'sm', 'md', 'lg'] as const - export type BreakpointName = typeof BREAKPOINTS_NAMES[number] /** Minimum size in px after which a breakpoint is active. */ -export const BREAKPOINTS: Record = { xs: 0, sm: 576, md: 768, lg: 992 } // no xl because TreePage's max-width is the xl breakpoint. +export const BREAKPOINTS: Record = { xs: 0, sm: 576, md: 768, lg: 992 } export const COLUMNS: Record = { xs: 1, sm: 6, md: 8, lg: 12 } export const DEFAULT_ITEMS_PER_ROW: Record = { xs: 1, sm: 2, md: 2, lg: 3 } export const MIN_WIDTHS: Record = { xs: 1, sm: 2, md: 3, lg: 3 } export const DEFAULT_HEIGHT = 3.25 +export const ROW_HEIGHT = 6 * 16 // 6rem +export const CONTAINER_PADDING: [number, number] = [0, 0] +export const GRID_MARGIN: [number, number] = [12, 12] const DEFAULT_VIEWS_LAYOUT_GENERATOR = (viewIds: string[]): ReactGridLayouts => Object.fromEntries( @@ -64,9 +60,7 @@ export type ViewGridProps = layouts?: never } | { - /** - * Sets custom layout for react-grid-layout library. - */ + /** Sets custom layout for react-grid-layout library. */ layouts: ReactGridLayouts viewIds?: never } @@ -75,21 +69,17 @@ interface ViewGridCommonProps { /** Custom classname for root element of the grid. */ className?: string - onLayoutChange?: (currentLayout: Layout[], allLayouts: Layouts) => void - onResizeStart?: (newItem: Layout) => void - onResizeStop?: (newItem: Layout) => void - onDragStart?: (newItem: Layout) => void + onLayoutChange?: (currentLayout: ReactGridLayout[], allLayouts: ReactGridLayouts) => void + onResizeStart?: (newItem: ReactGridLayout) => void + onResizeStop?: (newItem: ReactGridLayout) => void + onDragStart?: (newItem: ReactGridLayout) => void } -/** - * Renders drag and drop and resizable views grid. - */ -export const ViewGrid: React.FunctionComponent< - React.PropsWithChildren> -> = props => { +/** Renders drag and drop and resizable views grid. */ +export const ViewGrid: FC> = props => { const { layouts, - viewIds = [], + viewIds, children, className, onLayoutChange, @@ -98,6 +88,11 @@ export const ViewGrid: React.FunctionComponent< onDragStart = noop, } = props + const gridRef = useRef(null) + const [, { width }] = useMeasure(gridRef.current) + + const gridLayouts = useMemo(() => layouts ?? DEFAULT_VIEWS_LAYOUT_GENERATOR(viewIds), [layouts, viewIds]) + const handleResizeStart: ReactGridLayout.ItemCallback = useCallback( (_layout, item, newItem) => onResizeStart(newItem), [onResizeStart] @@ -122,25 +117,32 @@ export const ViewGrid: React.FunctionComponent< // Back to css transforms when this bug will be resolved in Firefox. const useCSSTransforms = useMemo(() => !isFirefox(), []) + useLayoutEffect(() => { + // React grid layout doesn't expose API in order to override rendered elements + // (like as='ul' prop). Internally it always renders div element, we can't just + // render UL element, so we have to tune aria-role attribute manually here + gridRef.current?.setAttribute('role', 'list') + }, []) + return ( -
- - {children} - -
+ + {width && children} + ) } diff --git a/client/web/src/integration/insights/insight/insight-chart-focus.test.ts b/client/web/src/integration/insights/insight/insight-chart-focus.test.ts index 0f44c9649df..8949f4c387e 100644 --- a/client/web/src/integration/insights/insight/insight-chart-focus.test.ts +++ b/client/web/src/integration/insights/insight/insight-chart-focus.test.ts @@ -44,12 +44,12 @@ describe('Code insights [Insight Card] should has a proper focus management ', ( await driver.page.goto(driver.sourcegraphBaseUrl + '/insights/dashboards/DASHBOARD_WITH_SEARCH') - await driver.page.waitForSelector('[aria-label="Insight card"]') - await driver.page.focus('[aria-label="Insight card"]') + await driver.page.waitForSelector('[aria-label="Search Based insight"]') + await driver.page.focus('[aria-label="Search Based insight"]') await driver.page.keyboard.press(Key.Tab) assert.strictEqual( - await hasFocus(driver, '[aria-label="Go to the insight page"]'), + await hasFocus(driver, '[aria-label="Search Based insight"] h2 a'), true, 'Insight title should be focused' ) @@ -98,12 +98,12 @@ describe('Code insights [Insight Card] should has a proper focus management ', ( await driver.page.goto(driver.sourcegraphBaseUrl + '/insights/dashboards/DASHBOARD_WITH_LANG_INSIGHT') - await driver.page.waitForSelector('[aria-label="Insight card"]') - await driver.page.focus('[aria-label="Insight card"]') + await driver.page.waitForSelector('[aria-label="Lang Stats insight"]') + await driver.page.focus('[aria-label="Lang Stats insight"]') await driver.page.keyboard.press(Key.Tab) assert.strictEqual( - await hasFocus(driver, '[aria-label="Go to the insight page"]'), + await hasFocus(driver, '[aria-label="Lang Stats insight"] h2 a'), true, 'Insight title should be focused' ) diff --git a/package.json b/package.json index 13b16307c30..5699289f6f8 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,7 @@ "@types/react-calendar": "^3.5.2", "@types/react-circular-progressbar": "1.0.2", "@types/react-dom": "18.0.2", - "@types/react-grid-layout": "1.3.0", + "@types/react-grid-layout": "1.3.2", "@types/react-router": "^5.1.18", "@types/react-router-dom": "^5.3.3", "@types/recharts": "1.8.23", @@ -459,7 +459,7 @@ "react-dom-confetti": "^0.1.4", "react-elm-components": "^1.1.0", "react-focus-lock": "^2.7.1", - "react-grid-layout": "1.3.0", + "react-grid-layout": "1.3.4", "react-resizable": "^3.0.4", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index a6da4d72fab..404ab9ec879 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8397,12 +8397,12 @@ __metadata: languageName: node linkType: hard -"@types/react-grid-layout@npm:1.3.0": - version: 1.3.0 - resolution: "@types/react-grid-layout@npm:1.3.0" +"@types/react-grid-layout@npm:1.3.2": + version: 1.3.2 + resolution: "@types/react-grid-layout@npm:1.3.2" dependencies: "@types/react": "*" - checksum: 94243bce2c7005dafad165f6f9329e4e77e09eed3ab8380b010474d75e4314856da43ff006f90c26a3f69e3d5398bbec145dbbd8619361abbc068ed413b791a6 + checksum: 190492acb69186c651bb99f19028dcd4c65129eae7de6efd41f51b6a6711af490e7b99ad7156b8731b11ecf2ec7c22bcf13c782bfe65be4b4e5c3362b97095bf languageName: node linkType: hard @@ -12307,7 +12307,7 @@ __metadata: languageName: node linkType: hard -"classnames@npm:2.3.1, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1": +"classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.1": version: 2.3.1 resolution: "classnames@npm:2.3.1" checksum: 14db8889d56c267a591f08b0834989fe542d47fac659af5a539e110cc4266694e8de86e4e3bbd271157dbd831361310a8293e0167141e80b0f03a0f175c80960 @@ -12561,6 +12561,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^1.1.1": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -26674,14 +26681,14 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.1, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2": - version: 15.7.2 - resolution: "prop-types@npm:15.7.2" +"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.1, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" dependencies: loose-envify: ^1.4.0 object-assign: ^4.1.1 - react-is: ^16.8.1 - checksum: 5eef82fdda64252c7e75aa5c8cc28a24bbdece0f540adb60ce67c205cf978a5bd56b83e4f269f91c6e4dcfd80b36f2a2dec24d362e278913db2086ca9c6f9430 + react-is: ^16.13.1 + checksum: c056d3f1c057cb7ff8344c645450e14f088a915d078dcda795041765047fa080d38e5d626560ccaac94a4e16e3aa15f3557c1a9a8d1174530955e992c675e459 languageName: node linkType: hard @@ -27173,19 +27180,19 @@ pvutils@latest: languageName: node linkType: hard -"react-grid-layout@npm:1.3.0": - version: 1.3.0 - resolution: "react-grid-layout@npm:1.3.0" +"react-grid-layout@npm:1.3.4": + version: 1.3.4 + resolution: "react-grid-layout@npm:1.3.4" dependencies: - classnames: 2.3.1 + clsx: ^1.1.1 lodash.isequal: ^4.0.0 - prop-types: ^15.0.0 + prop-types: ^15.8.1 react-draggable: ^4.0.0 react-resizable: ^3.0.4 peerDependencies: react: ">= 16.3.0" react-dom: ">= 16.3.0" - checksum: 955c135dcc14e86f573141e75b211e08e68f1d4f4ea2d0849652410dfc41715d89507abf1d28c2de4326b201f2e4138a2c2796561e917a2ee8b932adf40864bb + checksum: f56c8c452acd9588edf1dc6996a4ea14d9f669d77f6b2ebd50146eaeeb9325c83f5ca44b66bac2b8c24f9cb2ec7ed49396350435991255c3b31e21b8a2e3d243 languageName: node linkType: hard @@ -27220,7 +27227,7 @@ pvutils@latest: languageName: node linkType: hard -"react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6, react-is@npm:^16.9.0": +"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.6, react-is@npm:^16.9.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -28699,7 +28706,7 @@ pvutils@latest: "@types/react-calendar": ^3.5.2 "@types/react-circular-progressbar": 1.0.2 "@types/react-dom": 18.0.2 - "@types/react-grid-layout": 1.3.0 + "@types/react-grid-layout": 1.3.2 "@types/react-resizable": ^3.0.2 "@types/react-router": ^5.1.18 "@types/react-router-dom": ^5.3.3 @@ -28881,7 +28888,7 @@ pvutils@latest: react-dom-confetti: ^0.1.4 react-elm-components: ^1.1.0 react-focus-lock: ^2.7.1 - react-grid-layout: 1.3.0 + react-grid-layout: 1.3.4 react-refresh: ^0.10.0 react-resizable: ^3.0.4 react-router: ^5.2.0