From 7dfc4416f26cf8ffab307574a21d04da23856848 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Wed, 14 Apr 2021 13:04:19 +0300 Subject: [PATCH] Add Code Insights independent MVP (#19600) * Add sandboxes directory for MVPs * Change code insights imports in consumers * Add new API context approach for mocking data in MVP --- client/README.md | 1 + client/sandboxes/code-insights/.eslintrc.js | 13 ++ .../sandboxes/code-insights/.stylelintrc.json | 3 + .../sandboxes/code-insights/babel.config.js | 9 + client/sandboxes/code-insights/package.json | 13 ++ client/sandboxes/code-insights/src/demo.tsx | 50 ++++++ client/sandboxes/code-insights/src/index.html | 11 ++ .../sandboxes/code-insights/src/mock-api.ts | 160 ++++++++++++++++++ client/sandboxes/code-insights/tsconfig.json | 28 +++ .../sandboxes/code-insights/webpack.config.js | 47 +++++ client/web/src/SourcegraphWebApp.scss | 1 + client/web/src/SourcegraphWebApp.tsx | 2 +- client/web/src/insights/backend.ts | 116 ------------- .../{icon.tsx => components/Icons.tsx} | 0 .../InsightsNavLink}/InsightsNavLink.tsx | 5 +- .../InsightsViewGrid/InsightsViewGrid.scss} | 4 +- .../InsightsViewGrid/InsightsViewGrid.tsx} | 24 +-- client/web/src/insights/components/index.ts | 4 + .../src/insights/{ => core}/analytics.test.ts | 0 .../web/src/insights/{ => core}/analytics.ts | 0 .../src/insights/core/backend/api-provider.ts | 6 + .../src/insights/core/backend/insights-api.ts | 60 +++++++ .../requests/fetch-backend-insights.ts | 36 ++++ client/web/src/insights/core/backend/types.ts | 24 +++ .../core/backend/utils/create-view-content.ts | 34 ++++ client/web/src/insights/index.ts | 10 ++ .../web/src/insights/pages/InsightsPage.scss | 1 + .../src/insights/{ => pages}/InsightsPage.tsx | 36 ++-- client/web/src/nav/MenuNavItem.story.tsx | 2 +- client/web/src/nav/NavLinks.tsx | 2 +- client/web/src/repo/tree/TreePage.scss | 2 +- client/web/src/repo/tree/TreePage.tsx | 11 +- client/web/src/routes.tsx | 2 +- client/web/src/search/input/SearchPage.tsx | 10 +- client/web/src/views/ViewContent.tsx | 3 +- package.json | 13 +- yarn.lock | 119 +++++++++++-- 37 files changed, 675 insertions(+), 187 deletions(-) create mode 100644 client/sandboxes/code-insights/.eslintrc.js create mode 100644 client/sandboxes/code-insights/.stylelintrc.json create mode 100644 client/sandboxes/code-insights/babel.config.js create mode 100644 client/sandboxes/code-insights/package.json create mode 100644 client/sandboxes/code-insights/src/demo.tsx create mode 100644 client/sandboxes/code-insights/src/index.html create mode 100644 client/sandboxes/code-insights/src/mock-api.ts create mode 100644 client/sandboxes/code-insights/tsconfig.json create mode 100644 client/sandboxes/code-insights/webpack.config.js delete mode 100644 client/web/src/insights/backend.ts rename client/web/src/insights/{icon.tsx => components/Icons.tsx} (100%) rename client/web/src/insights/{ => components/InsightsNavLink}/InsightsNavLink.tsx (73%) rename client/web/src/{repo/tree/ViewGrid.scss => insights/components/InsightsViewGrid/InsightsViewGrid.scss} (95%) rename client/web/src/{repo/tree/ViewGrid.tsx => insights/components/InsightsViewGrid/InsightsViewGrid.tsx} (88%) create mode 100644 client/web/src/insights/components/index.ts rename client/web/src/insights/{ => core}/analytics.test.ts (100%) rename client/web/src/insights/{ => core}/analytics.ts (100%) create mode 100644 client/web/src/insights/core/backend/api-provider.ts create mode 100644 client/web/src/insights/core/backend/insights-api.ts create mode 100644 client/web/src/insights/core/backend/requests/fetch-backend-insights.ts create mode 100644 client/web/src/insights/core/backend/types.ts create mode 100644 client/web/src/insights/core/backend/utils/create-view-content.ts create mode 100644 client/web/src/insights/index.ts create mode 100644 client/web/src/insights/pages/InsightsPage.scss rename client/web/src/insights/{ => pages}/InsightsPage.tsx (70%) diff --git a/client/README.md b/client/README.md index da611af749c..5b55f0d04f0 100644 --- a/client/README.md +++ b/client/README.md @@ -7,6 +7,7 @@ - **eslint-plugin-sourcegraph**: Not published package with custom ESLint rules for Sourcegraph. Isn't intended for reuse by other repositories in the Sourcegraph org. - **extension-api**: The Sourcegraph extension API types for the _Sourcegraph extensions_. Published as `sourcegraph`. - **extension-api-types**: The Sourcegraph extension API types for _client applications_ that embed Sourcegraph extensions and need to communicate with them. Published as `@sourcegraph/extension-api-types`. +- **sandboxes**: All demos-mvp (minimum viable product) for the Sourcegraph web application. - **shared**: Contains common TypeScript/React/SCSS client code shared between the browser extension and the web app. Everything in this package is code-host agnostic. - **branded**: Contains React components and implements the visual design language we use across our web app and e.g. in the options menu of the browser extension. Over time, components from `shared` and `branded` packages should be moved into the `wildcard` package. - **wildcard**: Package that encapsulates storybook configuration and contains our Wildcard design system components. If we're using a component in two or more different areas (e.g. `web-app` and `browser-extension`) then it should live in the `wildcard` package. Otherwise the components should be better colocated with the code where they're actually used. diff --git a/client/sandboxes/code-insights/.eslintrc.js b/client/sandboxes/code-insights/.eslintrc.js new file mode 100644 index 00000000000..22f74b5b564 --- /dev/null +++ b/client/sandboxes/code-insights/.eslintrc.js @@ -0,0 +1,13 @@ +// @ts-check + +const baseConfig = require('../../../.eslintrc.js') + +module.exports = { + extends: '../../../.eslintrc.js', + parserOptions: { + ...baseConfig.parserOptions, + project: [__dirname + '/tsconfig.json'], + }, + rules: {}, + overrides: baseConfig.overrides, +} diff --git a/client/sandboxes/code-insights/.stylelintrc.json b/client/sandboxes/code-insights/.stylelintrc.json new file mode 100644 index 00000000000..c68ef76bfe2 --- /dev/null +++ b/client/sandboxes/code-insights/.stylelintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@sourcegraph/stylelint-config"] +} diff --git a/client/sandboxes/code-insights/babel.config.js b/client/sandboxes/code-insights/babel.config.js new file mode 100644 index 00000000000..12969df7922 --- /dev/null +++ b/client/sandboxes/code-insights/babel.config.js @@ -0,0 +1,9 @@ +// @ts-check + +/** @type {import('@babel/core').TransformOptions} */ +const config = { + extends: '../../../babel.config.js', + plugins: ['react-refresh/babel'], +} + +module.exports = config diff --git a/client/sandboxes/code-insights/package.json b/client/sandboxes/code-insights/package.json new file mode 100644 index 00000000000..d9f07a36cd5 --- /dev/null +++ b/client/sandboxes/code-insights/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "name": "@sourcegraph/code-insights-demo", + "version": "0.0.1", + "description": "Sourcegraph code insight mvp", + "sideEffects": false, + "license": "Apache-2.0", + "scripts": { + "eslint": "eslint --cache '**/*.[jt]s?(x)'", + "stylelint": "stylelint 'src/**/*.scss' --quiet", + "serve": "webpack serve --hot" + } +} diff --git a/client/sandboxes/code-insights/src/demo.tsx b/client/sandboxes/code-insights/src/demo.tsx new file mode 100644 index 00000000000..831158d9089 --- /dev/null +++ b/client/sandboxes/code-insights/src/demo.tsx @@ -0,0 +1,50 @@ +import { createBrowserHistory } from 'history' +import React, { ReactElement, useState } from 'react' +import { render } from 'react-dom' +import { BrowserRouter } from 'react-router-dom' + +import { setLinkComponent } from '@sourcegraph/shared/src/components/Link' +import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations' +import { EMPTY_SETTINGS_CASCADE } from '@sourcegraph/shared/src/settings/settings' +import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' +import { RouterLinkOrAnchor } from '@sourcegraph/web/src/components/RouterLinkOrAnchor' +import { InsightsApiContext, InsightsPage } from '@sourcegraph/web/src/insights' + +import { MockInsightsApi } from './mock-api' + +import '@sourcegraph/web/src/SourcegraphWebApp.scss' + +const history = createBrowserHistory() +const mockAPI = new MockInsightsApi() + +setLinkComponent(RouterLinkOrAnchor) + +export function App(): ReactElement { + const [patternType, setPatterType] = useState(SearchPatternType.literal) + const [caseSensitive, setCaseSensitive] = useState(false) + + return ( + + + + + + ) +} + +render(, document.querySelector('#root')) diff --git a/client/sandboxes/code-insights/src/index.html b/client/sandboxes/code-insights/src/index.html new file mode 100644 index 00000000000..299859a3ef0 --- /dev/null +++ b/client/sandboxes/code-insights/src/index.html @@ -0,0 +1,11 @@ + + + + + Code insights magic developer environment + + + +
+ + diff --git a/client/sandboxes/code-insights/src/mock-api.ts b/client/sandboxes/code-insights/src/mock-api.ts new file mode 100644 index 00000000000..2c4d2b8768c --- /dev/null +++ b/client/sandboxes/code-insights/src/mock-api.ts @@ -0,0 +1,160 @@ +import { Observable, of } from 'rxjs' + +import { InsightsAPI } from '@sourcegraph/web/src/insights/core/backend/insights-api' +import { + ViewInsightProviderResult, + ViewInsightProviderSourceType, +} from '@sourcegraph/web/src/insights/core/backend/types' + +export const MOCK_VIEWS = [ + { + id: 'searchInsights.searchInsights.insight.graphQLTypesMigration.insightsPage', + view: { + title: 'Migration to new GraphQL TS types', + content: [ + { + chart: 'line' as const, + data: [ + { + date: 1595624400000, + 'Imports of old GQL.* types': 259, + 'Imports of new graphql-operations types': 7, + }, + { + date: 1599253200000, + 'Imports of old GQL.* types': 190, + 'Imports of new graphql-operations types': 191, + }, + { + date: 1602882000000, + 'Imports of old GQL.* types': 182, + 'Imports of new graphql-operations types': 210, + }, + { + date: 1606510800000, + 'Imports of old GQL.* types': 179, + 'Imports of new graphql-operations types': 256, + }, + { + date: 1610139600000, + 'Imports of old GQL.* types': 139, + 'Imports of new graphql-operations types': 335, + }, + { + date: 1613768400000, + 'Imports of old GQL.* types': 139, + 'Imports of new graphql-operations types': 352, + }, + { + date: 1617397200000, + 'Imports of old GQL.* types': 139, + 'Imports of new graphql-operations types': 362, + }, + ], + series: [ + { + dataKey: 'Imports of old GQL.* types', + name: 'Imports of old GQL.* types', + stroke: 'var(--oc-red-7)', + linkURLs: [ + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-06-13T00%3A00%3A00%2B03%3A00+before%3A2020-07-25T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-07-25T00%3A00%3A00%2B03%3A00+before%3A2020-09-05T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-09-05T00%3A00%3A00%2B03%3A00+before%3A2020-10-17T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-10-17T00%3A00%3A00%2B03%3A00+before%3A2020-11-28T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-11-28T00%3A00%3A00%2B03%3A00+before%3A2021-01-09T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2021-01-09T00%3A00%3A00%2B03%3A00+before%3A2021-02-20T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2021-02-20T00%3A00%3A00%2B03%3A00+before%3A2021-04-03T00%3A00%3A00%2B03%3A00+patternType%3Aregex+case%3Ayes+%5C*%5Csas%5CsGQL', + ], + }, + { + dataKey: 'Imports of new graphql-operations types', + name: 'Imports of new graphql-operations types', + stroke: 'var(--oc-blue-7)', + linkURLs: [ + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-06-13T00%3A00%3A00%2B03%3A00+before%3A2020-07-25T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-07-25T00%3A00%3A00%2B03%3A00+before%3A2020-09-05T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-09-05T00%3A00%3A00%2B03%3A00+before%3A2020-10-17T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-10-17T00%3A00%3A00%2B03%3A00+before%3A2020-11-28T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2020-11-28T00%3A00%3A00%2B03%3A00+before%3A2021-01-09T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2021-01-09T00%3A00%3A00%2B03%3A00+before%3A2021-02-20T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + 'https://sourcegraph.com/search?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24+type%3Adiff+after%3A2021-02-20T00%3A00%3A00%2B03%3A00+before%3A2021-04-03T00%3A00%3A00%2B03%3A00+patternType%3Aregexp+case%3Ayes+%2Fgraphql-operations%27', + ], + }, + ], + xAxis: { dataKey: 'date', type: 'number', scale: 'time' }, + }, + ], + }, + source: ViewInsightProviderSourceType.Extension, + }, + { + id: 'codeStatsInsights.languages.insightsPage', + view: { + title: 'Language usage', + content: [ + { + chart: 'pie', + pies: [ + { + data: [ + { + name: 'Go', + totalLines: 363432, + fill: '#00ADD8', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + { + name: 'HTML', + totalLines: 224961, + fill: '#e34c26', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + { + name: 'TypeScript', + totalLines: 155381, + fill: '#2b7489', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + { + name: 'Markdown', + totalLines: 46675, + fill: '#083fa1', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + { + name: 'YAML', + totalLines: 25412, + fill: '#cb171e', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + { + name: 'Other', + totalLines: 56846, + fill: 'gray', + linkURL: + 'https://sourcegraph.com/stats?q=repo%3A%5Egithub%5C.com%2Fsourcegraph%2Fsourcegraph%24', + }, + ], + dataKey: 'totalLines', + nameKey: 'name', + fillKey: 'fill', + linkURLKey: 'linkURL', + }, + ], + }, + ], + }, + source: 'Extension', + }, +] as ViewInsightProviderResult[] + +export class MockInsightsApi implements InsightsAPI { + public getCombinedViews = (): Observable => of(MOCK_VIEWS) + + public getInsightCombinedViews = (): Observable => this.getCombinedViews() +} diff --git a/client/sandboxes/code-insights/tsconfig.json b/client/sandboxes/code-insights/tsconfig.json new file mode 100644 index 00000000000..6b9ff7bf794 --- /dev/null +++ b/client/sandboxes/code-insights/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "baseUrl": ".", + "paths": { + "*": ["src/types/*", "../../shared/src/types/*", "*"], + }, + "jsx": "react", + "rootDir": ".", + "outDir": "out", + "plugins": [ + { + "name": "ts-graphql-plugin", + "schema": "../../../cmd/frontend/graphqlbackend/schema.graphql", + "tag": "gql", + }, + ], + }, + "references": [ + { "path": "../../web" }, + { "path": "../../shared" }, + { "path": "../../branded" }, + { "path": "../../../schema" }, + ], + "include": ["**/*", ".*", "./src/**/*.json"], + "exclude": ["../../node_modules", "./node_modules", "./out", "src/end-to-end", "src/regression", "src/integration"], +} diff --git a/client/sandboxes/code-insights/webpack.config.js b/client/sandboxes/code-insights/webpack.config.js new file mode 100644 index 00000000000..d4618a7b72f --- /dev/null +++ b/client/sandboxes/code-insights/webpack.config.js @@ -0,0 +1,47 @@ +const path = require('path') + +const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = { + entry: path.resolve(__dirname, './src/demo.tsx'), + output: { + path: path.resolve(__dirname, 'dist'), // Note: Physical files are only output by the production build task `npm run build`. + publicPath: '/', + filename: '[name].bundle.js', + }, + target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test + mode: 'development', + devtool: 'source-map', + resolve: { + alias: { react: require.resolve('react') }, + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /node_modules/, + use: ['babel-loader'], + }, + { + test: /\.(scss)$/i, + use: [ + 'style-loader', + { + loader: 'css-loader', + }, + 'sass-loader', + ], + }, + ], + }, + plugins: [ + new ReactRefreshPlugin({ + overlay: false, + }), + new HtmlWebpackPlugin({ + template: './src/index.html', + }), + ], +} diff --git a/client/web/src/SourcegraphWebApp.scss b/client/web/src/SourcegraphWebApp.scss index b42ef6f2154..6c0229892fb 100644 --- a/client/web/src/SourcegraphWebApp.scss +++ b/client/web/src/SourcegraphWebApp.scss @@ -121,6 +121,7 @@ body, @import './user/UserAvatar'; @import './user/area/UserArea'; @import './org/OrgsArea'; +@import 'insights/pages/InsightsPage'; @import './components/Badge'; @import './components/Collapsible'; @import './components/DismissibleAlert'; diff --git a/client/web/src/SourcegraphWebApp.tsx b/client/web/src/SourcegraphWebApp.tsx index f86454f8cc4..48ecb5857ca 100644 --- a/client/web/src/SourcegraphWebApp.tsx +++ b/client/web/src/SourcegraphWebApp.tsx @@ -32,7 +32,7 @@ import { ExtensionAreaRoute } from './extensions/extension/ExtensionArea' import { ExtensionAreaHeaderNavItem } from './extensions/extension/ExtensionAreaHeader' import { ExtensionsAreaRoute } from './extensions/ExtensionsArea' import { ExtensionsAreaHeaderActionButton } from './extensions/ExtensionsAreaHeader' -import { logCodeInsightsChanges } from './insights/analytics' +import { logCodeInsightsChanges } from './insights' import { KeyboardShortcutsProps } from './keyboardShortcuts/keyboardShortcuts' import { Layout, LayoutProps } from './Layout' import { updateUserSessionStores } from './marketing/util' diff --git a/client/web/src/insights/backend.ts b/client/web/src/insights/backend.ts deleted file mode 100644 index d96b7912c2f..00000000000 --- a/client/web/src/insights/backend.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { combineLatest, Observable, of } from 'rxjs' -import { catchError, map } from 'rxjs/operators' -import { LineChartContent } from 'sourcegraph' - -import { ViewProviderResult } from '@sourcegraph/shared/src/api/extension/extensionHostApi' -import { dataOrThrowErrors, gql } from '@sourcegraph/shared/src/graphql/graphql' -import { asError } from '@sourcegraph/shared/src/util/errors' - -import { requestGraphQL } from '../backend/graphql' -import { InsightsResult, InsightFields } from '../graphql-operations' - -const insightFieldsFragment = gql` - fragment InsightFields on Insight { - title - description - series { - label - points { - dateTime - value - } - } - } -` -function fetchBackendInsights(): Observable { - return requestGraphQL(gql` - query Insights { - insights { - nodes { - ...InsightFields - } - } - } - ${insightFieldsFragment} - `).pipe( - map(dataOrThrowErrors), - map(data => data.insights?.nodes ?? []) - ) -} - -export enum ViewInsightProviderSourceType { - Backend = 'Backend', - Extension = 'Extension', -} - -export interface ViewInsightProviderResult extends ViewProviderResult { - /** The source of view provider to distinguish between data from extension and data from backend */ - source: ViewInsightProviderSourceType -} - -export function getCombinedViews( - getExtensionsInsights: () => Observable -): Observable { - return combineLatest([ - getExtensionsInsights().pipe( - map(extensionInsights => - extensionInsights.map(insight => ({ ...insight, source: ViewInsightProviderSourceType.Extension })) - ) - ), - fetchBackendInsights().pipe( - map(backendInsights => - backendInsights.map( - (insight, index): ViewInsightProviderResult => ({ - id: `Backend insight ${index + 1}`, - view: { - title: insight.title, - subtitle: insight.description, - content: [backendInsightToViewContent(insight)], - }, - source: ViewInsightProviderSourceType.Backend, - }) - ) - ), - catchError(error => - of([ - { - id: 'Backend insight', - view: asError(error), - source: ViewInsightProviderSourceType.Backend, - }, - ]) - ) - ), - ]).pipe(map(([extensionViews, backendInsights]) => [...backendInsights, ...extensionViews])) -} - -function backendInsightToViewContent( - insight: InsightFields -): LineChartContent<{ dateTime: number; [seriesKey: string]: number }, 'dateTime'> { - const dataByXValue = new Map() - for (const [seriesIndex, series] of insight.series.entries()) { - for (const point of series.points) { - let dataObject = dataByXValue.get(point.dateTime) - if (!dataObject) { - dataObject = { - dateTime: Date.parse(point.dateTime), - } - dataByXValue.set(point.dateTime, dataObject) - } - dataObject[`series${seriesIndex}`] = point.value - } - } - return { - chart: 'line', - data: [...dataByXValue.values()], - series: insight.series.map((series, index) => ({ - name: series.label, - dataKey: `series${index}`, - })), - xAxis: { - dataKey: 'dateTime', - scale: 'time', - type: 'number', - }, - } -} diff --git a/client/web/src/insights/icon.tsx b/client/web/src/insights/components/Icons.tsx similarity index 100% rename from client/web/src/insights/icon.tsx rename to client/web/src/insights/components/Icons.tsx diff --git a/client/web/src/insights/InsightsNavLink.tsx b/client/web/src/insights/components/InsightsNavLink/InsightsNavLink.tsx similarity index 73% rename from client/web/src/insights/InsightsNavLink.tsx rename to client/web/src/insights/components/InsightsNavLink/InsightsNavLink.tsx index fd4174a4993..4bfd429c5de 100644 --- a/client/web/src/insights/InsightsNavLink.tsx +++ b/client/web/src/insights/components/InsightsNavLink/InsightsNavLink.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { LinkWithIcon } from '../components/LinkWithIcon' - -import { InsightsIcon } from './icon' +import { LinkWithIcon } from '../../../components/LinkWithIcon' +import { InsightsIcon } from '../Icons' export const InsightsNavItem: React.FunctionComponent = () => ( , TelemetryProps { views: ViewInsightProviderResult[] @@ -97,7 +97,7 @@ const getInsightViewIcon = (source: ViewInsightProviderSourceType): MdiReactIcon } } -export const ViewGrid: React.FunctionComponent = props => { +export const InsightsViewGrid: React.FunctionComponent = props => { const onResizeOrDragStart: ReactGridLayout.ItemCallback = useCallback( (_layout, item) => { try { @@ -110,7 +110,7 @@ export const ViewGrid: React.FunctionComponent = props => { ) return ( -
+
= props => { onDragStart={onResizeOrDragStart} > {props.views.map(({ id, view, source }) => ( -
+
= props => { ) : ( <> -

{view.title}

- {view.subtitle &&
{view.subtitle}
} +

{view.title}

+ {view.subtitle && ( +
{view.subtitle}
+ )} )} diff --git a/client/web/src/insights/components/index.ts b/client/web/src/insights/components/index.ts new file mode 100644 index 00000000000..55d73e50fca --- /dev/null +++ b/client/web/src/insights/components/index.ts @@ -0,0 +1,4 @@ +export { InsightsIcon } from './Icons' +export { InsightsNavItem } from './InsightsNavLink/InsightsNavLink' +export { InsightsViewGrid } from './InsightsViewGrid/InsightsViewGrid' +export type { InsightsViewGridProps } from './InsightsViewGrid/InsightsViewGrid' diff --git a/client/web/src/insights/analytics.test.ts b/client/web/src/insights/core/analytics.test.ts similarity index 100% rename from client/web/src/insights/analytics.test.ts rename to client/web/src/insights/core/analytics.test.ts diff --git a/client/web/src/insights/analytics.ts b/client/web/src/insights/core/analytics.ts similarity index 100% rename from client/web/src/insights/analytics.ts rename to client/web/src/insights/core/analytics.ts diff --git a/client/web/src/insights/core/backend/api-provider.ts b/client/web/src/insights/core/backend/api-provider.ts new file mode 100644 index 00000000000..3fb2a9cc319 --- /dev/null +++ b/client/web/src/insights/core/backend/api-provider.ts @@ -0,0 +1,6 @@ +import React from 'react' + +import { InsightsAPI } from './insights-api' +import { ApiService } from './types' + +export const InsightsApiContext = React.createContext(new InsightsAPI()) diff --git a/client/web/src/insights/core/backend/insights-api.ts b/client/web/src/insights/core/backend/insights-api.ts new file mode 100644 index 00000000000..f07e99bc7cf --- /dev/null +++ b/client/web/src/insights/core/backend/insights-api.ts @@ -0,0 +1,60 @@ +import { Remote } from 'comlink' +import { combineLatest, from, Observable, of } from 'rxjs' +import { catchError, map, switchMap } from 'rxjs/operators' + +import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' +import { FlatExtensionHostAPI } from '@sourcegraph/shared/src/api/contract' +import { ViewProviderResult } from '@sourcegraph/shared/src/api/extension/extensionHostApi' +import { asError } from '@sourcegraph/shared/src/util/errors' + +import { fetchBackendInsights } from './requests/fetch-backend-insights' +import { ApiService, ViewInsightProviderResult, ViewInsightProviderSourceType } from './types' +import { createViewContent } from './utils/create-view-content' + +/** Main API service to get data for code insights */ +export class InsightsAPI implements ApiService { + /** Get combined (backend and extensions) code insights */ + public getCombinedViews = ( + getExtensionsInsights: () => Observable + ): Observable => + combineLatest([ + getExtensionsInsights().pipe( + map(extensionInsights => + extensionInsights.map(insight => ({ ...insight, source: ViewInsightProviderSourceType.Extension })) + ) + ), + fetchBackendInsights().pipe( + map(backendInsights => + backendInsights.map( + (insight, index): ViewInsightProviderResult => ({ + id: `Backend insight ${index + 1}`, + view: { + title: insight.title, + subtitle: insight.description, + content: [createViewContent(insight)], + }, + source: ViewInsightProviderSourceType.Backend, + }) + ) + ), + catchError(error => + of([ + { + id: 'Backend insight', + view: asError(error), + source: ViewInsightProviderSourceType.Backend, + }, + ]) + ) + ), + ]).pipe(map(([extensionViews, backendInsights]) => [...backendInsights, ...extensionViews])) + + public getInsightCombinedViews = ( + extensionApi: Promise> + ): Observable => + this.getCombinedViews(() => + from(extensionApi).pipe( + switchMap(extensionHostAPI => wrapRemoteObservable(extensionHostAPI.getInsightsViews({}))) + ) + ) +} diff --git a/client/web/src/insights/core/backend/requests/fetch-backend-insights.ts b/client/web/src/insights/core/backend/requests/fetch-backend-insights.ts new file mode 100644 index 00000000000..5a8b82221e6 --- /dev/null +++ b/client/web/src/insights/core/backend/requests/fetch-backend-insights.ts @@ -0,0 +1,36 @@ +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +import { dataOrThrowErrors, gql } from '@sourcegraph/shared/src/graphql/graphql' + +import { requestGraphQL } from '../../../../backend/graphql' +import { InsightFields, InsightsResult } from '../../../../graphql-operations' + +const insightFieldsFragment = gql` + fragment InsightFields on Insight { + title + description + series { + label + points { + dateTime + value + } + } + } +` +export function fetchBackendInsights(): Observable { + return requestGraphQL(gql` + query Insights { + insights { + nodes { + ...InsightFields + } + } + } + ${insightFieldsFragment} + `).pipe( + map(dataOrThrowErrors), + map(data => data.insights?.nodes ?? []) + ) +} diff --git a/client/web/src/insights/core/backend/types.ts b/client/web/src/insights/core/backend/types.ts new file mode 100644 index 00000000000..1235acc914f --- /dev/null +++ b/client/web/src/insights/core/backend/types.ts @@ -0,0 +1,24 @@ +import { Remote } from 'comlink' +import { Observable } from 'rxjs' + +import { FlatExtensionHostAPI } from '@sourcegraph/shared/src/api/contract' +import { ViewProviderResult } from '@sourcegraph/shared/src/api/extension/extensionHostApi' + +export enum ViewInsightProviderSourceType { + Backend = 'Backend', + Extension = 'Extension', +} + +export interface ViewInsightProviderResult extends ViewProviderResult { + /** The source of view provider to distinguish between data from extension and data from backend */ + source: ViewInsightProviderSourceType +} + +export interface ApiService { + getCombinedViews: ( + getExtensionsInsights: () => Observable + ) => Observable + getInsightCombinedViews: ( + extensionApi: Promise> + ) => Observable +} diff --git a/client/web/src/insights/core/backend/utils/create-view-content.ts b/client/web/src/insights/core/backend/utils/create-view-content.ts new file mode 100644 index 00000000000..cbabe2604f8 --- /dev/null +++ b/client/web/src/insights/core/backend/utils/create-view-content.ts @@ -0,0 +1,34 @@ +import { LineChartContent } from 'sourcegraph' + +import { InsightFields } from '../../../../graphql-operations' + +export function createViewContent( + insight: InsightFields +): LineChartContent<{ dateTime: number; [seriesKey: string]: number }, 'dateTime'> { + const dataByXValue = new Map() + for (const [seriesIndex, series] of insight.series.entries()) { + for (const point of series.points) { + let dataObject = dataByXValue.get(point.dateTime) + if (!dataObject) { + dataObject = { + dateTime: Date.parse(point.dateTime), + } + dataByXValue.set(point.dateTime, dataObject) + } + dataObject[`series${seriesIndex}`] = point.value + } + } + return { + chart: 'line', + data: [...dataByXValue.values()], + series: insight.series.map((series, index) => ({ + name: series.label, + dataKey: `series${index}`, + })), + xAxis: { + dataKey: 'dateTime', + scale: 'time', + type: 'number', + }, + } +} diff --git a/client/web/src/insights/index.ts b/client/web/src/insights/index.ts new file mode 100644 index 00000000000..5dad5166bfb --- /dev/null +++ b/client/web/src/insights/index.ts @@ -0,0 +1,10 @@ +// Pages exports +export { InsightsPage } from './pages/InsightsPage' + +// Core insights exports +export { InsightsApiContext } from './core/backend/api-provider' +export * from './core/analytics' + +// Public Insights components +export { InsightsViewGrid } from './components' +export type { InsightsViewGridProps } from './components' diff --git a/client/web/src/insights/pages/InsightsPage.scss b/client/web/src/insights/pages/InsightsPage.scss new file mode 100644 index 00000000000..3f69bcc8539 --- /dev/null +++ b/client/web/src/insights/pages/InsightsPage.scss @@ -0,0 +1 @@ +@import '../components/InsightsViewGrid/InsightsViewGrid'; diff --git a/client/web/src/insights/InsightsPage.tsx b/client/web/src/insights/pages/InsightsPage.tsx similarity index 70% rename from client/web/src/insights/InsightsPage.tsx rename to client/web/src/insights/pages/InsightsPage.tsx index 40fdab5e9d1..869ceb9aad1 100644 --- a/client/web/src/insights/InsightsPage.tsx +++ b/client/web/src/insights/pages/InsightsPage.tsx @@ -1,37 +1,29 @@ import GearIcon from 'mdi-react/GearIcon' import PlusIcon from 'mdi-react/PlusIcon' -import React, { useCallback, useEffect, useMemo } from 'react' -import { from } from 'rxjs' -import { switchMap } from 'rxjs/operators' +import React, { useCallback, useEffect, useMemo, useContext } from 'react' import { LoadingSpinner } from '@sourcegraph/react-loading-spinner' -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' import { Link } from '@sourcegraph/shared/src/components/Link' import { ExtensionsControllerProps } from '@sourcegraph/shared/src/extensions/controller' import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { useObservable } from '@sourcegraph/shared/src/util/useObservable' -import { FeedbackBadge } from '../components/FeedbackBadge' -import { Page } from '../components/Page' -import { PageHeader } from '../components/PageHeader' -import { ViewGrid, ViewGridProps } from '../repo/tree/ViewGrid' +import { FeedbackBadge } from '../../components/FeedbackBadge' +import { Page } from '../../components/Page' +import { PageHeader } from '../../components/PageHeader' +import { InsightsIcon, InsightsViewGrid, InsightsViewGridProps } from '../components' +import { InsightsApiContext } from '../core/backend/api-provider' -import { getCombinedViews } from './backend' -import { InsightsIcon } from './icon' - -interface InsightsPageProps extends ExtensionsControllerProps, Omit, TelemetryProps {} +interface InsightsPageProps extends ExtensionsControllerProps, Omit, TelemetryProps {} export const InsightsPage: React.FunctionComponent = props => { + const { getInsightCombinedViews } = useContext(InsightsApiContext) + const views = useObservable( - useMemo( - () => - getCombinedViews(() => - from(props.extensionsController.extHostAPI).pipe( - switchMap(extensionHostAPI => wrapRemoteObservable(extensionHostAPI.getInsightsViews({}))) - ) - ), - [props.extensionsController] - ) + useMemo(() => getInsightCombinedViews(props.extensionsController?.extHostAPI), [ + props.extensionsController, + getInsightCombinedViews, + ]) ) useEffect(() => { @@ -73,7 +65,7 @@ export const InsightsPage: React.FunctionComponent = props =>
) : ( - + )}
diff --git a/client/web/src/nav/MenuNavItem.story.tsx b/client/web/src/nav/MenuNavItem.story.tsx index 008fcc535d3..1d9d338ab5d 100644 --- a/client/web/src/nav/MenuNavItem.story.tsx +++ b/client/web/src/nav/MenuNavItem.story.tsx @@ -4,7 +4,7 @@ import React from 'react' import { BatchChangesNavItem } from '../batches/BatchChangesNavItem' import { CodeMonitoringNavItem } from '../code-monitoring/CodeMonitoringNavItem' import { WebStory } from '../components/WebStory' -import { InsightsNavItem } from '../insights/InsightsNavLink' +import { InsightsNavItem } from '../insights/components/InsightsNavLink/InsightsNavLink' import { MenuNavItem } from './MenuNavItem' diff --git a/client/web/src/nav/NavLinks.tsx b/client/web/src/nav/NavLinks.tsx index 92f9e1c4d77..5cd446fc984 100644 --- a/client/web/src/nav/NavLinks.tsx +++ b/client/web/src/nav/NavLinks.tsx @@ -18,7 +18,7 @@ import { CodeMonitoringProps } from '../code-monitoring' import { CodeMonitoringNavItem } from '../code-monitoring/CodeMonitoringNavItem' import { LinkWithIcon } from '../components/LinkWithIcon' import { WebActionsNavItems, WebCommandListPopoverButton } from '../components/shared' -import { InsightsNavItem } from '../insights/InsightsNavLink' +import { InsightsNavItem } from '../insights/components/InsightsNavLink/InsightsNavLink' import { KeyboardShortcutsProps, KEYBOARD_SHORTCUT_SHOW_COMMAND_PALETTE, diff --git a/client/web/src/repo/tree/TreePage.scss b/client/web/src/repo/tree/TreePage.scss index 39eb22a2c6c..8643ede049e 100644 --- a/client/web/src/repo/tree/TreePage.scss +++ b/client/web/src/repo/tree/TreePage.scss @@ -1,5 +1,5 @@ @import '../../search/input/SearchButton'; -@import './ViewGrid'; +@import '../../insights/components/InsightsViewGrid/InsightsViewGrid'; @import './TreeEntriesSection'; .tree-page { diff --git a/client/web/src/repo/tree/TreePage.tsx b/client/web/src/repo/tree/TreePage.tsx index 6c1becd3d70..eac5940a641 100644 --- a/client/web/src/repo/tree/TreePage.tsx +++ b/client/web/src/repo/tree/TreePage.tsx @@ -8,7 +8,7 @@ import SourceCommitIcon from 'mdi-react/SourceCommitIcon' import SourceRepositoryIcon from 'mdi-react/SourceRepositoryIcon' import TagIcon from 'mdi-react/TagIcon' import UserIcon from 'mdi-react/UserIcon' -import React, { useState, useMemo, useCallback, useEffect } from 'react' +import React, { useState, useMemo, useCallback, useEffect, useContext } from 'react' import { Link, Redirect } from 'react-router-dom' import { Observable, EMPTY, from } from 'rxjs' import { catchError, map, switchMap } from 'rxjs/operators' @@ -42,7 +42,7 @@ import { BreadcrumbSetters } from '../../components/Breadcrumbs' import { FilteredConnection } from '../../components/FilteredConnection' import { PageTitle } from '../../components/PageTitle' import { GitCommitFields, Scalars, TreePageRepositoryFields } from '../../graphql-operations' -import { getCombinedViews } from '../../insights/backend' +import { InsightsApiContext, InsightsViewGrid } from '../../insights' import { Settings } from '../../schema/settings.schema' import { PatternTypeProps, CaseSensitivityProps, CopyQueryButtonProps, SearchContextProps } from '../../search' import { basename } from '../../util/path' @@ -52,7 +52,6 @@ import { gitCommitFragment } from '../commits/RepositoryCommitsPage' import { FilePathBreadcrumbs } from '../FilePathBreadcrumbs' import { TreeEntriesSection } from './TreeEntriesSection' -import { ViewGrid } from './ViewGrid' const fetchTreeCommits = memoizeObservable( (args: { @@ -263,6 +262,8 @@ export const TreePage: React.FunctionComponent = ({ [props.extensionsController] ) ) + + const { getCombinedViews } = useContext(InsightsApiContext) const views = useObservable( useMemo( () => @@ -287,7 +288,7 @@ export const TreePage: React.FunctionComponent = ({ ) ) : EMPTY, - [showCodeInsights, workspaceUri, uri, props.extensionsController] + [getCombinedViews, showCodeInsights, workspaceUri, uri, props.extensionsController] ) ) @@ -419,7 +420,7 @@ export const TreePage: React.FunctionComponent = ({ )} {views && ( - [] = [ { path: '/insights', exact: true, - render: lazyComponent(() => import('./insights/InsightsPage'), 'InsightsPage'), + render: lazyComponent(() => import('./insights/pages/InsightsPage'), 'InsightsPage'), condition: props => !isErrorLike(props.settingsCascade.final) && !!props.settingsCascade.final?.experimentalFeatures?.codeInsights && diff --git a/client/web/src/search/input/SearchPage.tsx b/client/web/src/search/input/SearchPage.tsx index a3e9c39d758..64e5262ad86 100644 --- a/client/web/src/search/input/SearchPage.tsx +++ b/client/web/src/search/input/SearchPage.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames' import * as H from 'history' -import React, { useCallback, useEffect, useMemo } from 'react' +import React, { useCallback, useContext, useEffect, useMemo } from 'react' import { EMPTY, from } from 'rxjs' import { switchMap } from 'rxjs/operators' @@ -30,9 +30,8 @@ import { import { AuthenticatedUser } from '../../auth' import { BrandLogo } from '../../components/branding/BrandLogo' import { SyntaxHighlightedSearchQuery } from '../../components/SyntaxHighlightedSearchQuery' -import { getCombinedViews } from '../../insights/backend' +import { InsightsApiContext, InsightsViewGrid } from '../../insights' import { KeyboardShortcutsProps } from '../../keyboardShortcuts/keyboardShortcuts' -import { ViewGrid } from '../../repo/tree/ViewGrid' import { repogroupList, homepageLanguageList } from '../../repogroups/HomepageConfig' import { Settings } from '../../schema/settings.schema' import { VersionContext } from '../../schema/site.schema' @@ -97,6 +96,7 @@ export const SearchPage: React.FunctionComponent = props => { !!props.settingsCascade.final?.experimentalFeatures?.codeInsights && props.settingsCascade.final['insights.displayLocation.homepage'] !== false + const { getCombinedViews } = useContext(InsightsApiContext) const views = useObservable( useMemo( () => @@ -107,7 +107,7 @@ export const SearchPage: React.FunctionComponent = props => { ) ) : EMPTY, - [showCodeInsights, props.extensionsController] + [getCombinedViews, showCodeInsights, props.extensionsController] ) ) return ( @@ -121,7 +121,7 @@ export const SearchPage: React.FunctionComponent = props => { })} > - {views && } + {views && }
{props.isSourcegraphDotCom && props.showRepogroupHomepage && diff --git a/client/web/src/views/ViewContent.tsx b/client/web/src/views/ViewContent.tsx index 1421747e809..0543bacc7c0 100644 --- a/client/web/src/views/ViewContent.tsx +++ b/client/web/src/views/ViewContent.tsx @@ -48,6 +48,7 @@ export const ViewContent: React.FunctionComponent = ({ }) => { // Track user intent to interact with extension-contributed views const viewContentReference = useRef(null) + useEffect(() => { let viewContentElement = viewContentReference.current @@ -70,7 +71,7 @@ export const ViewContent: React.FunctionComponent = ({ } // If containerClassName is specified, the element with this class is the element - // that embodies the view in the eyes of the user. e.g. ViewGrid + // that embodies the view in the eyes of the user. e.g. InsightsViewGrid if (containerClassName) { viewContentElement = viewContentElement?.closest(`.${containerClassName}`) as HTMLDivElement } diff --git a/package.json b/package.json index 060abba5d56..d9082e384c6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "graphql-lint": "graphql-schema-linter cmd/frontend/graphqlbackend/schema.graphql", "build-web": "yarn workspace @sourcegraph/web run build", "watch-web": "yarn workspace @sourcegraph/web run watch", + "code-insights-demo": "yarn workspace @sourcegraph/code-insights-demo run serve", "generate": "gulp generate", "watch-generate": "gulp watchGenerate", "test": "jest --testPathIgnorePatterns end-to-end regression integration storybook", @@ -76,7 +77,8 @@ }, "workspaces": { "packages": [ - "client/*" + "client/*", + "client/sandboxes/*" ] }, "devDependencies": { @@ -97,6 +99,7 @@ "@mermaid-js/mermaid-cli": "^8.7.0", "@octokit/rest": "^16.36.0", "@percy/puppeteer": "^1.1.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", "@pollyjs/adapter": "^5.0.0", "@pollyjs/core": "^5.1.0", "@pollyjs/persister-fs": "^5.0.0", @@ -133,6 +136,8 @@ "@types/d3-scale": "2.2.0", "@types/d3-selection": "1.4.1", "@types/d3-shape": "1.3.2", + "@types/d3-format": "^2.0.0", + "@types/d3-time-format": "^3.0.0", "@types/enzyme": "3.10.8", "@types/expect": "24.3.0", "@types/fancy-log": "1.3.1", @@ -243,8 +248,9 @@ "raw-loader": "^4.0.2", "react-docgen-typescript-webpack-plugin": "^1.1.0", "react-hot-loader": "^4.13.0", - "react-spring": "^9.0.0", + "react-refresh": "^0.10.0", "react-test-renderer": "^16.14.0", + "react-spring": "^9.0.0", "sass": "^1.32.4", "sass-loader": "^10.1.0", "shelljs": "^0.8.4", @@ -270,6 +276,7 @@ "web-ext": "^4.2.0", "webpack": "^4.44.2", "webpack-bundle-analyzer": "^3.9.0", + "webpack-cli": "^4.6.0", "webpack-dev-server": "^3.11.1", "worker-loader": "^3.0.8", "yarn-deduplicate": "^3.1.0" @@ -287,8 +294,6 @@ "@sourcegraph/extension-api-classes": "^1.1.0", "@sourcegraph/react-loading-spinner": "0.0.7", "@sqs/jsonc-parser": "^1.0.3", - "@types/d3-format": "^2.0.0", - "@types/d3-time-format": "^3.0.0", "@visx/annotation": "^1.7.2", "@visx/axis": "^1.7.0", "@visx/glyph": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index c7405c9d43b..5c934ecda60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1367,6 +1367,11 @@ enabled "2.0.x" kuler "^2.0.0" +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + "@dsherret/to-absolute-glob@^2.0.2": version "2.0.2" resolved "https://registry.npmjs.org/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" @@ -2408,7 +2413,7 @@ dependencies: esquery "^1.0.1" -"@pmmmwh/react-refresh-webpack-plugin@^0.4.2": +"@pmmmwh/react-refresh-webpack-plugin@^0.4.2", "@pmmmwh/react-refresh-webpack-plugin@^0.4.3": version "0.4.3" resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz#1eec460596d200c0236bf195b078a5d1df89b766" integrity sha512-br5Qwvh8D2OQqSXpd1g/xqXKnK0r+Jz6qVKBbWmpUcrbGOxUrf39V5oZ1876084CGn18uMdR5uvPqBv9UqtBjQ== @@ -5232,6 +5237,23 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@webpack-cli/configtest@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz#2a20812bfb3a2ebb0b27ee26a52eeb3e3f000836" + integrity sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA== + +"@webpack-cli/info@^1.2.3": + version "1.2.3" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz#ef819d10ace2976b6d134c7c823a3e79ee31a92c" + integrity sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.3.1": + version "1.3.1" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz#911d1b3ff4a843304b9c3bacf67bb34672418441" + integrity sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw== + "@webpack-contrib/schema-utils@^1.0.0-beta.0": version "1.0.0-beta.0" resolved "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz#bf9638c9464d177b48209e84209e23bee2eb4f65" @@ -5562,7 +5584,7 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" -ansi-colors@4.1.1: +ansi-colors@4.1.1, ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== @@ -5574,7 +5596,7 @@ ansi-colors@^1.0.1: dependencies: ansi-wrap "^0.1.0" -ansi-colors@^3.0.0, ansi-colors@^3.2.1: +ansi-colors@^3.0.0: version "3.2.3" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== @@ -7874,6 +7896,15 @@ clone-buffer@^1.0.0: resolved "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + clone-regexp@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f" @@ -8103,6 +8134,11 @@ commander@^6.0.0, commander@^6.1.0: resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + comment-parser@^0.7.6: version "0.7.6" resolved "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.6.tgz#0e743a53c8e646c899a1323db31f6cd337b10f12" @@ -9825,12 +9861,12 @@ enhanced-resolve@^4.3.0: memory-fs "^0.5.0" tapable "^1.0.0" -enquirer@^2.3.5: - version "2.3.5" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" - integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== +enquirer@^2.3.5, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: - ansi-colors "^3.2.1" + ansi-colors "^4.1.1" entities@^1.1.1, entities@~1.1.1: version "1.1.2" @@ -9850,6 +9886,11 @@ env-ci@^5.0.2: execa "^4.0.0" java-properties "^1.0.0" +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + enzyme-adapter-react-16@^1.15.4: version "1.15.4" resolved "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.4.tgz#328a782365a363ecb424f99283c4833dd92c0f21" @@ -13290,10 +13331,10 @@ interpret@^1.0.0, interpret@^1.1.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== -interpret@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/interpret/-/interpret-2.0.0.tgz#b783ffac0b8371503e9ab39561df223286aa5433" - integrity sha512-e0/LknJ8wpMMhTiWcjivB+ESwIuvHnBSlBbmP/pSb8CQJldoj1p2qv7xGZ/+BtbTziYRFSz8OsvdbiX45LtYQA== +interpret@^2.0.0, interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== invariant@2.2.4, invariant@^2.2.1, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" @@ -18983,6 +19024,11 @@ react-popper@^2.2.4: react-fast-compare "^3.0.1" warning "^4.0.2" +react-refresh@^0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" + integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ== + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -19380,6 +19426,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + recursive-readdir@2.2.2, recursive-readdir@^2.0.0: version "2.2.2" resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -19909,7 +19962,7 @@ resolve@1.1.7: resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -20408,6 +20461,13 @@ sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: inherits "^2.0.1" safe-buffer "^5.0.1" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shallowequal@1.1.0, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" @@ -23155,6 +23215,26 @@ webpack-bundle-analyzer@^3.9.0: opener "^1.5.1" ws "^6.0.0" +webpack-cli@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz#27ae86bfaec0cf393fcfd58abdc5a229ad32fd16" + integrity sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.2" + "@webpack-cli/info" "^1.2.3" + "@webpack-cli/serve" "^1.3.1" + colorette "^1.2.1" + commander "^7.0.0" + enquirer "^2.3.6" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^5.7.3" + webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: version "3.7.2" resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" @@ -23238,6 +23318,14 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" @@ -23405,6 +23493,11 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + windows-release@^3.1.0: version "3.2.0" resolved "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f"