diff --git a/pkg/server/combined_statement_stats.go b/pkg/server/combined_statement_stats.go index 43b53b0407dd..e785baaaf3a4 100644 --- a/pkg/server/combined_statement_stats.go +++ b/pkg/server/combined_statement_stats.go @@ -563,6 +563,7 @@ func getStatementDetailsPerAggregatedTs( GROUP BY aggregated_ts, aggregation_interval + ORDER BY aggregated_ts ASC LIMIT $%d`, whereClause, len(args)+1) args = append(args, limit) diff --git a/pkg/ui/workspaces/cluster-ui/BUILD.bazel b/pkg/ui/workspaces/cluster-ui/BUILD.bazel index 93230f70c465..5ea6bf6a6ab1 100644 --- a/pkg/ui/workspaces/cluster-ui/BUILD.bazel +++ b/pkg/ui/workspaces/cluster-ui/BUILD.bazel @@ -81,6 +81,7 @@ DEPENDENCIES = [ "@npm//highlight.js", "@npm//http-proxy-middleware", "@npm//identity-obj-proxy", + "@npm//jest-canvas-mock", "@npm//jest-environment-enzyme", "@npm//jest-enzyme", "@npm//jest-fetch-mock", diff --git a/pkg/ui/workspaces/cluster-ui/jest.config.js b/pkg/ui/workspaces/cluster-ui/jest.config.js index 7ee8b57dca91..15f63754496c 100644 --- a/pkg/ui/workspaces/cluster-ui/jest.config.js +++ b/pkg/ui/workspaces/cluster-ui/jest.config.js @@ -43,7 +43,7 @@ module.exports = { ], roots: ["/src"], testEnvironment: "enzyme", - setupFilesAfterEnv: ["./enzyme.setup.js", "jest-enzyme"], + setupFilesAfterEnv: ["./enzyme.setup.js", "./src/test-utils/matchMedia.mock.js", "jest-enzyme", "jest-canvas-mock"], transform: { "^.+\\.tsx?$": "ts-jest", "^.+\\.jsx?$": ['babel-jest', { configFile: path.resolve(__dirname, 'babel.config.js') }], diff --git a/pkg/ui/workspaces/cluster-ui/package.json b/pkg/ui/workspaces/cluster-ui/package.json index 2c982c3de224..d79f876a550f 100644 --- a/pkg/ui/workspaces/cluster-ui/package.json +++ b/pkg/ui/workspaces/cluster-ui/package.json @@ -103,6 +103,7 @@ "http-proxy-middleware": "^1.0.5", "identity-obj-proxy": "^3.0.0", "jest": "^27.5.1", + "jest-canvas-mock": "^2.4.0", "jest-cli": "^27.5.1", "jest-environment-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2", diff --git a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/bars.ts b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/bars.ts index 14e4f0c9b410..f69305e734ca 100644 --- a/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/bars.ts +++ b/pkg/ui/workspaces/cluster-ui/src/graphs/bargraph/bars.ts @@ -14,8 +14,8 @@ import { AxisUnits, AxisDomain } from "../utils/domain"; import { barTooltipPlugin } from "./plugins"; const seriesPalette = [ - "#475872", - "#FFCD02", + "#003EBD", + "#2AAF44", "#F16969", "#4E9FD1", "#49D990", @@ -24,7 +24,7 @@ const seriesPalette = [ "#A3415B", "#B59153", "#C9DB6D", - "#203D9B", + "#475872", "#748BF2", "#91C8F2", "#FF9696", @@ -158,6 +158,9 @@ export const getBarChartOpts = ( x: { range: () => [xAxisDomain.extent[0], xAxisDomain.extent[1]], }, + yAxis: { + range: () => [yAxisDomain.extent[0], yAxisDomain.extent[1]], + }, }, axes: [ { diff --git a/pkg/ui/workspaces/cluster-ui/src/pageConfig/pageConfig.module.scss b/pkg/ui/workspaces/cluster-ui/src/pageConfig/pageConfig.module.scss index 58eac363b887..3e010f09be21 100644 --- a/pkg/ui/workspaces/cluster-ui/src/pageConfig/pageConfig.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/pageConfig/pageConfig.module.scss @@ -14,7 +14,7 @@ margin-bottom: -10px; margin-top: -10px; - z-index: 1; + z-index: 2; background-color: $colors--background; &__list { diff --git a/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.module.scss b/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.module.scss index e63b530b9d24..4d0f5c827ae5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/sortedtable/sortedtable.module.scss @@ -61,7 +61,8 @@ position: relative; padding: 11px; - &:after, &:before { + &:after, + &:before { content: ""; position: absolute; right: 5px; @@ -94,15 +95,14 @@ } } } - } .break-line { - white-space:pre-wrap !important; + white-space: pre-wrap !important; } .cl-table-container { - padding: 18px 24px 18px 0px; + padding: 20px 24px 18px 0px; } .cl-table-wrapper { padding: 9.55px 20px 17px; @@ -116,7 +116,8 @@ align-items: center; flex-direction: column; padding: 50px 0; - &--title, &--description { + &--title, + &--description { font-family: $font-family--base; margin: 0; } diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx index 40e1e53f0662..b6044ddef6d4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx @@ -8,19 +8,22 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +import React, { ReactNode } from "react"; import { Col, Row, Tabs } from "antd"; -import { Text, Heading, InlineAlert } from "@cockroachlabs/ui-components"; -import { PageConfig, PageConfigItem } from "src/pageConfig"; +import { cockroach, google } from "@cockroachlabs/crdb-protobuf-client"; +import { Text, InlineAlert } from "@cockroachlabs/ui-components"; +import { ArrowLeft } from "@cockroachlabs/icons"; +import { Location } from "history"; import _ from "lodash"; -import React, { ReactNode } from "react"; +import Long from "long"; +import { format as d3Format } from "d3-format"; import { Helmet } from "react-helmet"; import { Link, RouteComponentProps } from "react-router-dom"; import classNames from "classnames/bind"; -import { format as d3Format } from "d3-format"; -import { ArrowLeft } from "@cockroachlabs/icons"; -import { cockroach, google } from "@cockroachlabs/crdb-protobuf-client"; -import Long from "long"; -import { Location } from "history"; +import { PageConfig, PageConfigItem } from "src/pageConfig"; +import { BarGraphTimeSeries } from "../graphs/bargraph"; +import { AxisUnits } from "../graphs"; +import { AlignedData, Options } from "uplot"; import { NumericStat, @@ -28,9 +31,7 @@ import { Bytes, Duration, FixLong, - longToInt, stdDev, - formatNumberForDisplay, unique, queryByName, appAttr, @@ -44,11 +45,7 @@ import { SortSetting } from "src/sortedtable"; import { Tooltip } from "@cockroachlabs/ui-components"; import { PlanDetails } from "./planDetails"; import { SummaryCard } from "src/summaryCard"; -import { - latencyBreakdown, - genericBarChart, - formatTwoPlaces, -} from "src/barCharts"; +import { latencyBreakdown, genericBarChart } from "src/barCharts"; import { DiagnosticsView } from "./diagnostics/diagnosticsView"; import sortedTableStyles from "src/sortedtable/sortedtable.module.scss"; import summaryCardStyles from "src/summaryCard/summaryCard.module.scss"; @@ -68,6 +65,13 @@ import { ActivateDiagnosticsModalRef, ActivateStatementDiagnosticsModal, } from "../statementsDiagnostics"; +import { + generateExecCountTimeseries, + generateExecRetriesTimeseries, + generateExecuteAndPlanningTimeseries, + generateRowsProcessedTimeseries, + generateContentionTimeseries, +} from "./timeseriesUtils"; type IDuration = google.protobuf.IDuration; type StatementDetailsResponse = cockroach.server.serverpb.StatementDetailsResponse; type IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; @@ -332,15 +336,6 @@ export class StatementDetails extends React.Component< hasDiagnosticReports = (): boolean => this.props.diagnosticsReports.length > 0; - changeSortSetting = (ss: SortSetting): void => { - this.setState({ - sortSetting: ss, - }); - if (this.props.onSortingChange) { - this.props.onSortingChange("Stats By Node", ss.columnTitle, ss.ascending); - } - }; - refreshStatementDetails = ( timeScale: TimeScale, statementFingerprintID: string, @@ -470,13 +465,13 @@ export class StatementDetails extends React.Component< Statements

- Statement Details + Statement Fingerprint

@@ -582,22 +577,15 @@ export class StatementDetails extends React.Component< const { app_names, databases, - dist_sql_count, failed_count, full_scan_count, vec_count, total_count, implicit_txn, } = this.props.statementDetails.statement.metadata; - - const { statement } = this.props.statementDetails; - - const totalCountBarChart = longToInt(statement.stats.count); - const firstAttemptsBarChart = longToInt( - statement.stats.first_attempt_count, - ); - const retriesBarChart = totalCountBarChart - firstAttemptsBarChart; - const maxRetriesBarChart = longToInt(statement.stats.max_retries); + const { + statement_statistics_per_aggregated_ts, + } = this.props.statementDetails; const nodes: string[] = unique( (stats.nodes || []).map(node => node.toString()), @@ -606,7 +594,6 @@ export class StatementDetails extends React.Component< (stats.nodes || []).map(node => nodeRegions[node.toString()]), ).sort(); - const duration = (v: number) => Duration(v * 1e9); const lastExec = stats.last_exec_timestamp && moment(stats.last_exec_timestamp.seconds.low * 1e3).format( @@ -614,25 +601,76 @@ export class StatementDetails extends React.Component< ); const statementSampled = stats.exec_stats.count > Long.fromNumber(0); const unavailableTooltip = !statementSampled && ( - - This metric is part of the statement execution and therefore will - not be available until the statement is sampled via tracing. -

- } - > - unavailable -
+
+ This metric is part of the statement execution and therefore will not be + available until the statement is sampled. +
); + const noSamples = statementSampled ? "" : " (no samples)"; const db = databases ? ( {databases} ) : ( (unset) ); + + const statsPerAggregatedTs = statement_statistics_per_aggregated_ts.sort( + (a, b) => + a.aggregated_ts.seconds < b.aggregated_ts.seconds + ? -1 + : a.aggregated_ts.seconds > b.aggregated_ts.seconds + ? 1 + : 0, + ); + + const executionAndPlanningTimeseries: AlignedData = generateExecuteAndPlanningTimeseries( + statsPerAggregatedTs, + ); + const executionAndPlanningOps: Partial = { + axes: [{}, { label: "Time Spent" }], + series: [{}, { label: "Execution" }, { label: "Planning" }], + width: 735, + }; + + const rowsProcessedTimeseries: AlignedData = generateRowsProcessedTimeseries( + statsPerAggregatedTs, + ); + const rowsProcessedOps: Partial = { + axes: [{}, { label: "Rows" }], + series: [{}, { label: "Rows Read" }, { label: "Rows Written" }], + width: 735, + }; + + const execRetriesTimeseries: AlignedData = generateExecRetriesTimeseries( + statsPerAggregatedTs, + ); + const execRetriesOps: Partial = { + axes: [{}, { label: "Retries" }], + series: [{}, { label: "Retries" }], + legend: { show: false }, + width: 735, + }; + + const execCountTimeseries: AlignedData = generateExecCountTimeseries( + statsPerAggregatedTs, + ); + const execCountOps: Partial = { + axes: [{}, { label: "Execution Counts" }], + series: [{}, { label: "Execution Counts" }], + legend: { show: false }, + width: 735, + }; + + const contentionTimeseries: AlignedData = generateContentionTimeseries( + statsPerAggregatedTs, + ); + const contentionOps: Partial = { + axes: [{}, { label: "Contention" }], + series: [{}, { label: "Contention" }], + legend: { show: false }, + width: 735, + }; + return ( <> @@ -655,110 +693,6 @@ export class StatementDetails extends React.Component< - - -
- Mean statement time - - {formatNumberForDisplay( - stats.service_lat.mean, - duration, - )} - -
-
- Planning time - - {formatNumberForDisplay(stats.plan_lat.mean, duration)} - -
-

-

- Execution time - - {formatNumberForDisplay(stats.run_lat.mean, duration)} - -
-

- - - - - - -

- Resource usage -
-
- Mean rows/bytes read - {statementSampled && ( - - {formatNumberForDisplay( - stats.rows_read.mean, - formatTwoPlaces, - )} - {" / "} - {formatNumberForDisplay(stats.bytes_read.mean, Bytes)} - - )} - {unavailableTooltip} -
-
- Mean rows written - - {formatNumberForDisplay( - stats.rows_written?.mean, - formatTwoPlaces, - )} - -
-
- Max memory usage - {statementSampled && ( - - {formatNumberForDisplay( - stats.exec_stats.max_mem_usage.mean, - Bytes, - )} - - )} - {unavailableTooltip} -
-
- Network usage - {statementSampled && ( - - {formatNumberForDisplay( - stats.exec_stats.network_bytes.mean, - Bytes, - )} - - )} - {unavailableTooltip} -
-
- Max scratch disk usage - {statementSampled && ( - - {formatNumberForDisplay( - stats.exec_stats.max_disk_usage.mean, - Bytes, - )} - - )} - {unavailableTooltip} -
- -
-
- - - - Statement details {!isTenant && (
@@ -776,16 +710,10 @@ export class StatementDetails extends React.Component<
)} -
Database {db}
-

App @@ -795,16 +723,16 @@ export class StatementDetails extends React.Component< )}
+
+ + +
Failed? {RenderCount(failed_count, total_count)}
- Distributed execution? - {RenderCount(dist_sql_count, total_count)} -
-
- Full Scan? + Full scan? {RenderCount(full_scan_count, total_count)}
@@ -819,50 +747,57 @@ export class StatementDetails extends React.Component< Last execution time {lastExec}
-

- Execution counts -

- First attempts - {firstAttemptsBarChart} -
-
- Total executions - {totalCountBarChart} -
-
- Retries - 0, - }, - )} - > - {retriesBarChart} - -
-
- Max retries - 0, - }, - )} - > - {maxRetriesBarChart} - -
+

+ + + + + + + + + + + + + + + + + + + + +

); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/timeseriesUtils.ts b/pkg/ui/workspaces/cluster-ui/src/statementDetails/timeseriesUtils.ts new file mode 100644 index 000000000000..313b89db7595 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/timeseriesUtils.ts @@ -0,0 +1,92 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; +import { AlignedData } from "uplot"; +import { longToInt, TimestampToNumber } from "../util"; + +type statementStatisticsPerAggregatedTs = cockroach.server.serverpb.StatementDetailsResponse.ICollectedStatementGroupedByAggregatedTs; + +export function generateExecuteAndPlanningTimeseries( + stats: statementStatisticsPerAggregatedTs[], +): AlignedData { + const ts: Array = []; + const execution: Array = []; + const planning: Array = []; + + stats.forEach(function(stat: statementStatisticsPerAggregatedTs) { + ts.push(TimestampToNumber(stat.aggregated_ts) * 1e3); + execution.push(stat.stats.run_lat.mean * 1e9); + planning.push(stat.stats.plan_lat.mean * 1e9); + }); + + return [ts, execution, planning]; +} + +export function generateRowsProcessedTimeseries( + stats: statementStatisticsPerAggregatedTs[], +): AlignedData { + const ts: Array = []; + const read: Array = []; + const written: Array = []; + + stats.forEach(function(stat: statementStatisticsPerAggregatedTs) { + ts.push(TimestampToNumber(stat.aggregated_ts) * 1e3); + read.push(stat.stats.rows_read?.mean); + written.push(stat.stats.rows_written?.mean); + }); + + return [ts, read, written]; +} + +export function generateExecRetriesTimeseries( + stats: statementStatisticsPerAggregatedTs[], +): AlignedData { + const ts: Array = []; + const retries: Array = []; + + stats.forEach(function(stat: statementStatisticsPerAggregatedTs) { + ts.push(TimestampToNumber(stat.aggregated_ts) * 1e3); + + const totalCountBarChart = longToInt(stat.stats.count); + const firstAttemptsBarChart = longToInt(stat.stats.first_attempt_count); + retries.push(totalCountBarChart - firstAttemptsBarChart); + }); + + return [ts, retries]; +} + +export function generateExecCountTimeseries( + stats: statementStatisticsPerAggregatedTs[], +): AlignedData { + const ts: Array = []; + const count: Array = []; + + stats.forEach(function(stat: statementStatisticsPerAggregatedTs) { + ts.push(TimestampToNumber(stat.aggregated_ts) * 1e3); + count.push(longToInt(stat.stats.count)); + }); + + return [ts, count]; +} + +export function generateContentionTimeseries( + stats: statementStatisticsPerAggregatedTs[], +): AlignedData { + const ts: Array = []; + const count: Array = []; + + stats.forEach(function(stat: statementStatisticsPerAggregatedTs) { + ts.push(TimestampToNumber(stat.aggregated_ts) * 1e3); + count.push(stat.stats.exec_stats.contention_time.mean * 1e9); + }); + + return [ts, count]; +} diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss index c0697df4a4f2..de6dc09a9fb7 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.module.scss @@ -49,7 +49,7 @@ .table-area { position: relative; overflow-x: scroll; - min-height: 480px; + min-height: 590px; } .reset-btn-area { diff --git a/pkg/ui/workspaces/cluster-ui/src/test-utils/matchMedia.mock.js b/pkg/ui/workspaces/cluster-ui/src/test-utils/matchMedia.mock.js new file mode 100644 index 000000000000..6889b882f76a --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/test-utils/matchMedia.mock.js @@ -0,0 +1,29 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +// Jest uses jsdom to simulate a browser, which doesn’t include window.matchMedia. +// This property was created and added to `setupFilesAfterEnv` on jest.config.js to +// handle test cases where window.matchMedia and its functions (e.g. addEventListener) +// are used. +Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +export {}; diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx index 160570c49def..d4d860ecc1e9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx @@ -28,7 +28,7 @@ describe("timescale utils", (): void => { }; const [start, end] = toRoundedDateRange(ts); assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); - assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); + assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 13:59:59"); }); it("already rounded values", () => { @@ -40,7 +40,7 @@ describe("timescale utils", (): void => { }; const [start, end] = toRoundedDateRange(ts); assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); - assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); + assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 13:59:59"); }); }); diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.ts b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.ts index fab8b50db684..eea81a368279 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.ts +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.ts @@ -88,10 +88,10 @@ export const toDateRange = (ts: TimeScale): [moment.Moment, moment.Moment] => { }; // toRoundedDateRange round the TimeScale selected, with the start -// rounded down and end rounded up one hour. +// rounded down and end rounded up to the limit before the next hour. // e.g. // start: 17:45:23 -> 17:00:00 -// end: 20:14:32 -> 21:00:00 +// end: 20:14:32 -> 20:59:59 export const toRoundedDateRange = ( ts: TimeScale, ): [moment.Moment, moment.Moment] => { @@ -99,7 +99,8 @@ export const toRoundedDateRange = ( const startRounded = start.set({ minute: 0, second: 0, millisecond: 0 }); const endRounded = end .set({ minute: 0, second: 0, millisecond: 0 }) - .add(1, "hours"); + .add(59, "minutes") + .add(59, "seconds"); return [startRounded, endRounded]; }; diff --git a/pkg/ui/workspaces/db-console/styl/base/layout-vars.styl b/pkg/ui/workspaces/db-console/styl/base/layout-vars.styl index 03012aeaa1eb..1149c7a58aac 100644 --- a/pkg/ui/workspaces/db-console/styl/base/layout-vars.styl +++ b/pkg/ui/workspaces/db-console/styl/base/layout-vars.styl @@ -20,4 +20,4 @@ $max-window-width = 1350px $z-index-tooltip = 4 $z-index-banner = 3 $z-index-navigation = 2 -$z-index-page-config = 1 +$z-index-page-config = 2 diff --git a/pkg/ui/yarn-vendor b/pkg/ui/yarn-vendor index e647b312256b..8d6039256dff 160000 --- a/pkg/ui/yarn-vendor +++ b/pkg/ui/yarn-vendor @@ -1 +1 @@ -Subproject commit e647b312256b749d2ed9dcdbc12acf7b04d32523 +Subproject commit 8d6039256dff7d456d81d59e10edf34961b0515f diff --git a/pkg/ui/yarn.lock b/pkg/ui/yarn.lock index 32a209bb37f0..bfdab91d60f4 100644 --- a/pkg/ui/yarn.lock +++ b/pkg/ui/yarn.lock @@ -1586,7 +1586,7 @@ integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cockroachlabs/cluster-ui@link:workspaces/cluster-ui": - version "22.1.0-prerelease-3" + version "22.1.3" dependencies: "@babel/runtime" "^7.12.13" @@ -5955,7 +5955,7 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -6505,6 +6505,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfontparser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" + integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= + csso@1.3.x: version "1.3.12" resolved "https://registry.yarnpkg.com/csso/-/csso-1.3.12.tgz#fc628694a2d38938aaac4996753218fd311cdb9e" @@ -10513,6 +10518,14 @@ iterm2-version@^4.1.0: app-path "^3.2.0" plist "^3.0.1" +jest-canvas-mock@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341" + integrity sha512-mmMpZzpmLzn5vepIaHk5HoH3Ka4WykbSoLuG/EKoJd0x0ID/t+INo1l8ByfcUJuDM+RIsL4QDg/gDnBbrj2/IQ== + dependencies: + cssfontparser "^1.2.1" + moo-color "^1.0.2" + jest-changed-files@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" @@ -12387,6 +12400,13 @@ moment@2.x, moment@^2.19.3, moment@^2.24.0, moment@^2.27.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +moo-color@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.3.tgz#d56435f8359c8284d83ac58016df7427febece74" + integrity sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ== + dependencies: + color-name "^1.1.4" + moo@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"