+
}
- >
- {groupProfileDetail && (
-
-
-
-
-
- )}
-
-
-
+
+
+ {groupProfileDetail && (
+
+
+
+
+
+
+
+ )}
+
+
+
)
}
diff --git a/ui/lib/apps/ContinuousProfiling/pages/List.tsx b/ui/lib/apps/ContinuousProfiling/pages/List.tsx
index 8368f1d9e2..7a2bb2ebbd 100644
--- a/ui/lib/apps/ContinuousProfiling/pages/List.tsx
+++ b/ui/lib/apps/ContinuousProfiling/pages/List.tsx
@@ -15,6 +15,7 @@ import { useNavigate } from 'react-router-dom'
import { useMemoizedFn, useSessionStorageState } from 'ahooks'
import {
LoadingOutlined,
+ QuestionCircleOutlined,
ReloadOutlined,
SettingOutlined,
} from '@ant-design/icons'
@@ -31,6 +32,7 @@ import ConProfSettingForm from './ConProfSettingForm'
import styles from './List.module.less'
import { telemetry } from '../utils/telemetry'
+import { isDistro } from '@lib/utils/distroStringsRes'
export default function Page() {
const [endTime, setEndTime] = useSessionStorageState
(
@@ -225,6 +227,8 @@ export default function Page() {
@@ -235,6 +239,8 @@ export default function Page() {
)}
@@ -245,6 +251,20 @@ export default function Page() {
}}
/>
+ {!isDistro && (
+
+ {
+ window.open(t('conprof.settings.help_url'), '_blank')
+ }}
+ />
+
+ )}
@@ -264,15 +284,26 @@ export default function Page() {
title={t('conprof.settings.disabled_result.title')}
subTitle={t('conprof.settings.disabled_result.sub_title')}
extra={
-
+
+
+ {!isDistro && (
+
+ )}
+
}
/>
) : (
@@ -280,6 +311,7 @@ export default function Page() {
record.target.display_name,
},
{
name: t('instance_profiling.detail.table.columns.content'),
key: 'content',
- minWidth: 150,
- maxWidth: 300,
+ minWidth: 100,
+ maxWidth: 100,
onRender: (record) => {
if (record.profiling_type === 'cpu') {
return `CPU - ${profileDuration}s`
@@ -226,43 +220,37 @@ export default function Page() {
{
name: t('instance_profiling.detail.table.columns.status'),
key: 'status',
- minWidth: 150,
- maxWidth: 200,
+ minWidth: 100,
+ maxWidth: 150,
onRender: (record) => {
if (record.state === taskState.Running) {
return (
-
+
)
} else if (record.state === taskState.Error) {
return (
-
-
-
+
)
} else if (record.state === taskState.Skipped) {
- let tooltipTransKey =
- 'instance_profiling.detail.table.tooltip.skipped'
- if (record.profiling_type === 'heap') {
- tooltipTransKey =
- 'instance_profiling.detail.table.tooltip.to_be_supported'
- }
return (
-
+
+
+
+
)
} else {
@@ -276,24 +264,60 @@ export default function Page() {
},
},
{
- name: t('instance_profiling.detail.table.columns.selection.actions'),
- key: 'output_type',
- minWidth: 150,
- maxWidth: 200,
+ name: t('instance_profiling.detail.table.columns.view_as.title'),
+ key: 'view_as',
+ minWidth: 250,
+ maxWidth: 400,
onRender: (record) => {
+ if (record.state === taskState.Error) {
+ return (
+ {
+ Modal.error({
+ title: 'Profile Error',
+ content: record.error,
+ })
+ }}
+ >
+ {t('instance_profiling.detail.table.columns.view_as.error')}
+
+ )
+ }
+
+ if (record.state === taskState.Running) {
+ return (
+
+ )
+ }
+
+ if (record.state !== taskState.Success) {
+ return <>>
+ }
+
const rec = record as IRecord
- const actions = rec.view_options.map((key) => ({
- key,
- text: t(
- `instance_profiling.detail.table.columns.selection.types.${key}`
- ),
- }))
return (
- openResult(act, rec)}
- />
+
+ {rec.view_options.map((action) => {
+ return (
+ openResult(action, record)}
+ key={action}
+ >
+ {t(
+ `instance_profiling.detail.table.columns.view_as.${action}`
+ )}
+
+ )
+ })}
+
)
},
},
@@ -328,23 +352,28 @@ export default function Page() {
{t('instance_profiling.detail.download')}
}
- >
- {respData && (
-
-
-
-
-
- )}
-
+ />
+ {respData && (
+
+
+
+
+
+
+
+ )}
diff --git a/ui/lib/apps/InstanceProfiling/pages/List.tsx b/ui/lib/apps/InstanceProfiling/pages/List.tsx
index aa671abe19..51a9294ca6 100644
--- a/ui/lib/apps/InstanceProfiling/pages/List.tsx
+++ b/ui/lib/apps/InstanceProfiling/pages/List.tsx
@@ -1,4 +1,4 @@
-import { Badge, Button, Form, Select, Modal, Alert } from 'antd'
+import { Badge, Button, Form, Select, Modal, Alert, Space, Tooltip } from 'antd'
import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'
import React, { useMemo, useState, useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
@@ -15,6 +15,7 @@ import {
InstanceSelect,
IInstanceSelectRefProps,
MultiSelect,
+ Toolbar,
} from '@lib/components'
import DateTime from '@lib/components/DateTime'
import openLink from '@lib/utils/openLink'
@@ -23,6 +24,8 @@ import { combineTargetStats } from '../utils'
import styles from './List.module.less'
import { upperFirst } from 'lodash'
+import { QuestionCircleOutlined } from '@ant-design/icons'
+import { isDistro } from '@lib/utils/distroStringsRes'
const profilingDurationsSec = [10, 30, 60, 120]
const defaultProfilingDuration = 30
@@ -201,72 +204,108 @@ export default function Page() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isDistro && (
+
+ {
+ window.open(
+ t('instance_profiling.settings.help_url'),
+ '_blank'
+ )
+ }}
+ />
+
+ )}
+
+
{conprofEnable && (
@@ -276,6 +315,7 @@ export default function Page() {
{
title={t('keyviz.settings.disabled_result.title')}
subTitle={t('keyviz.settings.disabled_result.sub_title')}
extra={
-
+
+
+ {!isDistro && (
+
+ )}
+
}
/>
)
diff --git a/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx b/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx
index 1c0092dc27..ed5d732a90 100644
--- a/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx
+++ b/ui/lib/apps/KeyViz/components/KeyVizToolbar.tsx
@@ -6,6 +6,7 @@ import {
ClockCircleOutlined,
DownOutlined,
LoadingOutlined,
+ QuestionCircleOutlined,
SettingOutlined,
} from '@ant-design/icons'
import { Slider, Spin, Select, Dropdown, Button, Tooltip, Space } from 'antd'
@@ -13,6 +14,7 @@ import { withTranslation, WithTranslation } from 'react-i18next'
import Flexbox from '@g07cha/flexbox-react'
import { AutoRefreshButton, Card, Toolbar } from '@lib/components'
import { getValueFormat } from '@baurine/grafana-value-formats'
+import { isDistro } from '@lib/utils/distroStringsRes'
export interface IKeyVizToolbarProps {
enabled: boolean
@@ -190,9 +192,27 @@ class KeyVizToolbar extends Component {
-
+
+ {!isDistro && (
+
+ {
+ window.open(t('keyviz.settings.help_url'), '_blank')
+ }}
+ />
+
+ )}
diff --git a/ui/lib/apps/KeyViz/translations/en.yaml b/ui/lib/apps/KeyViz/translations/en.yaml
index a1b045a731..b5c53263fb 100644
--- a/ui/lib/apps/KeyViz/translations/en.yaml
+++ b/ui/lib/apps/KeyViz/translations/en.yaml
@@ -33,3 +33,5 @@ keyviz:
save: Save
close: Disable
cancel: Cancel
+ help: Help
+ help_url: https://docs.pingcap.com/tidb/dev/dashboard-key-visualizer
diff --git a/ui/lib/apps/KeyViz/translations/zh.yaml b/ui/lib/apps/KeyViz/translations/zh.yaml
index 527190a92c..f6bc78921c 100644
--- a/ui/lib/apps/KeyViz/translations/zh.yaml
+++ b/ui/lib/apps/KeyViz/translations/zh.yaml
@@ -33,3 +33,5 @@ keyviz:
save: 保存
close: 确认
cancel: 取消
+ help: 帮助
+ help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-key-visualizer
diff --git a/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx b/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx
index 51f1fe522a..de227fcd87 100644
--- a/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx
+++ b/ui/lib/apps/Ngm/components/Error/NgmNotStarted.tsx
@@ -30,7 +30,7 @@ for (const key in translations) {
export function NgmNotStarted() {
const { t } = useTranslation()
return (
-
+
.buttons {
margin-top: 12px;
diff --git a/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx b/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx
index 6c982e10cf..32960cc0f0 100644
--- a/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx
+++ b/ui/lib/apps/SearchLogs/pages/LogSearchHistory.tsx
@@ -215,6 +215,7 @@ export default function LogSearchingHistory() {
{
}
function SlowQueriesTable({ controller, ...restProps }: Props) {
- const {
- loadingSlowQueries,
- tableColumns,
- slowQueries,
- orderOptions: { orderBy, desc },
- changeOrder,
- errors,
- visibleColumnKeys,
-
- saveClickedItemIndex,
- getClickedItemIndex,
- } = controller
-
const navigate = useNavigate()
const handleRowClick = useMemoizedFn(
(rec, idx, ev: React.MouseEvent) => {
- saveClickedItemIndex(idx)
+ controller.saveClickedItemIndex(idx)
const qs = DetailPage.buildQuery({
digest: rec.digest,
connectId: rec.connection_id,
@@ -42,16 +29,16 @@ function SlowQueriesTable({ controller, ...restProps }: Props) {
return (
diff --git a/ui/lib/apps/SlowQuery/pages/List/index.tsx b/ui/lib/apps/SlowQuery/pages/List/index.tsx
index cd37870bf6..ed32d555c8 100644
--- a/ui/lib/apps/SlowQuery/pages/List/index.tsx
+++ b/ui/lib/apps/SlowQuery/pages/List/index.tsx
@@ -1,42 +1,46 @@
-import React, { useContext } from 'react'
+import React, { useContext, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
Select,
Space,
- Tooltip,
Input,
Checkbox,
message,
Menu,
Dropdown,
+ Alert,
+ Tooltip,
} from 'antd'
import {
- ReloadOutlined,
LoadingOutlined,
ExportOutlined,
MenuOutlined,
+ QuestionCircleOutlined,
} from '@ant-design/icons'
import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'
-
import {
Card,
ColumnsSelector,
TimeRangeSelector,
Toolbar,
MultiSelect,
+ TimeRange,
+ toTimeRangeValue,
} from '@lib/components'
import { CacheContext } from '@lib/utils/useCache'
import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'
-
import SlowQueriesTable from '../../components/SlowQueriesTable'
import useSlowQueryTableController, {
DEF_SLOW_QUERY_COLUMN_KEYS,
+ DEF_SLOW_QUERY_OPTIONS,
} from '../../utils/useSlowQueryTableController'
-
import styles from './List.module.less'
+import { useDebounceFn, useMemoizedFn } from 'ahooks'
+import { useDeepCompareChange } from '@lib/utils/useChange'
+import client from '@lib/client'
+import { isDistro } from '@lib/utils/distroStringsRes'
const { Option } = Select
-const { Search } = Input
const SLOW_QUERY_VISIBLE_COLUMN_KEYS = 'slow_query.visible_column_keys'
const SLOW_QUERY_SHOW_FULL_SQL = 'slow_query.show_full_sql'
@@ -45,7 +49,7 @@ const LIMITS = [100, 200, 500, 1000]
function List() {
const { t } = useTranslation()
- const slowQueryCacheMgr = useContext(CacheContext)
+ const cacheMgr = useContext(CacheContext)
const [visibleColumnKeys, setVisibleColumnKeys] =
useVersionedLocalStorageState(SLOW_QUERY_VISIBLE_COLUMN_KEYS, {
@@ -55,32 +59,25 @@ function List() {
SLOW_QUERY_SHOW_FULL_SQL,
{ defaultValue: false }
)
+ const [downloading, setDownloading] = useState(false)
- const controller = useSlowQueryTableController(
- slowQueryCacheMgr,
- visibleColumnKeys,
- showFullSQL
- )
- const {
- queryOptions,
- setQueryOptions,
- refresh,
- allSchemas,
- loadingSlowQueries,
- tableColumns,
- downloadCSV,
- downloading,
- } = controller
-
- function exportCSV() {
- const hide = message.loading(t('slow_query.toolbar.exporting') + '...', 0)
- downloadCSV().finally(hide)
- }
+ const controller = useSlowQueryTableController({
+ cacheMgr,
+ showFullSQL,
+ initialQueryOptions: {
+ ...DEF_SLOW_QUERY_OPTIONS,
+ visibleColumnKeys,
+ },
+ })
function menuItemClick({ key }) {
switch (key) {
case 'export':
- exportCSV()
+ const hide = message.loading(
+ t('slow_query.toolbar.exporting') + '...',
+ 0
+ )
+ downloadCSV().finally(hide)
break
}
}
@@ -100,46 +97,93 @@ function List() {
)
+ const [timeRange, setTimeRange] = useState(
+ controller.queryOptions.timeRange
+ )
+ const [filterSchema, setFilterSchema] = useState(
+ controller.queryOptions.schemas
+ )
+ const [filterLimit, setFilterLimit] = useState(
+ controller.queryOptions.limit
+ )
+ const [filterText, setFilterText] = useState(
+ controller.queryOptions.searchText
+ )
+
+ const sendQueryNow = useMemoizedFn(() => {
+ cacheMgr?.clear()
+ controller.setQueryOptions({
+ timeRange,
+ schemas: filterSchema,
+ limit: filterLimit,
+ searchText: filterText,
+ visibleColumnKeys,
+ digest: '',
+ plans: [],
+ })
+ })
+
+ const sendQueryDebounced = useDebounceFn(sendQueryNow, {
+ wait: 300,
+ }).run
+
+ useDeepCompareChange(() => {
+ if (
+ controller.isDataLoadedSlowly || // if data was loaded slowly
+ controller.isDataLoadedSlowly === null // or a request is not yet finished (which means slow network)..
+ ) {
+ // do not send requests on-the-fly.
+ return
+ }
+ sendQueryDebounced()
+ }, [timeRange, filterSchema, filterLimit, filterText, visibleColumnKeys])
+
+ const downloadCSV = useMemoizedFn(async () => {
+ // use last effective query options
+ const timeRangeValue = toTimeRangeValue(controller.queryOptions.timeRange)
+ try {
+ setDownloading(true)
+ const res = await client.getInstance().slowQueryDownloadTokenPost({
+ fields: '*',
+ begin_time: timeRangeValue[0],
+ end_time: timeRangeValue[1],
+ db: controller.queryOptions.schemas,
+ text: controller.queryOptions.searchText,
+ orderBy: controller.orderOptions.orderBy,
+ desc: controller.orderOptions.desc,
+ limit: 10000,
+ digest: '',
+ plans: [],
+ })
+ const token = res.data
+ if (token) {
+ window.location.href = `${client.getBasePath()}/slow_query/download?token=${token}`
+ }
+ } finally {
+ setDownloading(false)
+ }
+ })
+
return (
-
- setQueryOptions({
- ...queryOptions,
- timeRange,
- })
- }
- />
+
- setQueryOptions({
- ...queryOptions,
- schemas,
- })
- }
- items={allSchemas}
+ onChange={setFilterSchema}
+ items={controller.allSchemas}
data-e2e="execution_database_name"
/>
-
- setQueryOptions({ ...queryOptions, searchText })
- }
- data-e2e="slow_query_search"
- />
+ setFilterText(e.target.value)}
+ onSearch={sendQueryNow}
+ placeholder={t('slow_query.toolbar.keyword.placeholder')}
+ data-e2e="slow_query_search"
+ enterButton={t('slow_query.toolbar.query')}
+ />
+ {controller.isLoading && }
-
- {tableColumns.length > 0 && (
+ {controller.availableColumnsInTable.length > 0 && (
)}
-
- {loadingSlowQueries ? (
-
- ) : (
-
- )}
-
+ {!isDistro && (
+
+ {
+ window.open(t('slow_query.toolbar.help_url'), '_blank')
+ }}
+ />
+
+ )}
-
+ {controller.isDataLoadedSlowly && (
+
+
+
+ )}
diff --git a/ui/lib/apps/SlowQuery/translations/en.yaml b/ui/lib/apps/SlowQuery/translations/en.yaml
index fd858c2d06..95e2abe823 100644
--- a/ui/lib/apps/SlowQuery/translations/en.yaml
+++ b/ui/lib/apps/SlowQuery/translations/en.yaml
@@ -129,5 +129,10 @@ slow_query:
select_columns:
show_full_sql: Show Full Query Text
refresh: Refresh
+ keyword:
+ placeholder: Filter keyword
+ query: Query
export: Export
exporting: Exporting
+ help: Help
+ help_url: https://docs.pingcap.com/tidb/dev/dashboard-slow-query
diff --git a/ui/lib/apps/SlowQuery/translations/zh.yaml b/ui/lib/apps/SlowQuery/translations/zh.yaml
index 71bb62c336..9b330f60ef 100644
--- a/ui/lib/apps/SlowQuery/translations/zh.yaml
+++ b/ui/lib/apps/SlowQuery/translations/zh.yaml
@@ -132,5 +132,10 @@ slow_query:
select_columns:
show_full_sql: 显示完整 SQL 文本
refresh: 刷新
+ keyword:
+ placeholder: 关键字过滤
+ query: 查询
export: 导出
exporting: 正在导出
+ help: 帮助
+ help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-slow-query
diff --git a/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts b/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts
index 007e480bce..ad250c58a4 100644
--- a/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts
+++ b/ui/lib/apps/SlowQuery/utils/useSlowQueryTableController.ts
@@ -1,22 +1,22 @@
-import { useEffect, useMemo, useState } from 'react'
-import { useSessionStorageState } from 'ahooks'
+import { useMemo, useState } from 'react'
+import { useMemoizedFn, useSessionStorageState } from 'ahooks'
import { IColumn } from 'office-ui-fabric-react/lib/DetailsList'
-
import client, { ErrorStrategy, SlowqueryModel } from '@lib/client'
import {
- calcTimeRange,
TimeRange,
IColumnKeys,
- stringifyTimeRange,
+ DEFAULT_TIME_RANGE,
+ toTimeRangeValue,
} from '@lib/components'
import useOrderState, { IOrderOptions } from '@lib/utils/useOrderState'
-
import { getSelectedFields } from '@lib/utils/tableColumnFactory'
import { CacheMgr } from '@lib/utils/useCache'
import useCacheItemIndex from '@lib/utils/useCacheItemIndex'
-
import { derivedFields, slowQueryColumns } from './tableColumns'
import { useSchemaColumns } from './useSchemaColumns'
+import { useChange } from '@lib/utils/useChange'
+
+const SLOW_DATA_LOAD_THRESHOLD = 2000
export const DEF_SLOW_QUERY_COLUMN_KEYS: IColumnKeys = {
query: true,
@@ -32,18 +32,26 @@ const DEF_ORDER_OPTIONS: IOrderOptions = {
desc: true,
}
+interface RuntimeCacheEntity {
+ data: SlowqueryModel[]
+ isDataLoadedSlowly: boolean
+}
+
export interface ISlowQueryOptions {
- timeRange?: TimeRange
+ visibleColumnKeys: IColumnKeys
+ timeRange: TimeRange
schemas: string[]
searchText: string
limit: number
+ // below is for showing slow queries in the statement detail page
digest: string
plans: string[]
}
export const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = {
- timeRange: undefined,
+ visibleColumnKeys: DEF_SLOW_QUERY_COLUMN_KEYS,
+ timeRange: DEFAULT_TIME_RANGE,
schemas: [],
searchText: '',
limit: 100,
@@ -52,101 +60,91 @@ export const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = {
plans: [],
}
+function useQueryOptions(
+ initial?: ISlowQueryOptions,
+ persistInSession: boolean = true
+) {
+ const [memoryQueryOptions, setMemoryQueryOptions] = useState(
+ initial || DEF_SLOW_QUERY_OPTIONS
+ )
+ const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState(
+ QUERY_OPTIONS,
+ { defaultValue: initial || DEF_SLOW_QUERY_OPTIONS }
+ )
+ const queryOptions = persistInSession
+ ? sessionQueryOptions
+ : memoryQueryOptions
+ const setQueryOptions = useMemoizedFn(
+ (value: React.SetStateAction) => {
+ if (persistInSession) {
+ setSessionQueryOptions(value as any)
+ } else {
+ setMemoryQueryOptions(value)
+ }
+ }
+ )
+ return {
+ queryOptions,
+ setQueryOptions,
+ }
+}
+
+export interface ISlowQueryTableControllerOpts {
+ cacheMgr?: CacheMgr
+ showFullSQL?: boolean
+ initialQueryOptions?: ISlowQueryOptions
+ persistQueryInSession?: boolean
+}
+
export interface ISlowQueryTableController {
queryOptions: ISlowQueryOptions
- setQueryOptions: (options: ISlowQueryOptions) => void
+ setQueryOptions: (value: React.SetStateAction) => void // Updating query options will result in a refresh
+
orderOptions: IOrderOptions
changeOrder: (orderBy: string, desc: boolean) => void
- refresh: () => void
- allSchemas: string[]
- loadingSlowQueries: boolean
- slowQueries: SlowqueryModel[]
- queryTimeRange: { beginTime: number; endTime: number }
+ isLoading: boolean
+ data?: SlowqueryModel[]
+ isDataLoadedSlowly: boolean | null // SLOW_DATA_LOAD_THRESHOLD. NULL = Unknown
+ allSchemas: string[]
errors: Error[]
- tableColumns: IColumn[]
- visibleColumnKeys: IColumnKeys
-
- downloadCSV: () => Promise
- downloading: boolean
+ availableColumnsInTable: IColumn[] // returned from backend
saveClickedItemIndex: (idx: number) => void
getClickedItemIndex: () => number
}
-export default function useSlowQueryTableController(
- cacheMgr: CacheMgr | undefined,
- visibleColumnKeys: IColumnKeys,
- showFullSQL: boolean,
- options?: ISlowQueryOptions,
- needSave: boolean = true
-): ISlowQueryTableController {
+export default function useSlowQueryTableController({
+ cacheMgr,
+ showFullSQL = false,
+ initialQueryOptions,
+ persistQueryInSession = true,
+}: ISlowQueryTableControllerOpts): ISlowQueryTableController {
const { orderOptions, changeOrder } = useOrderState(
'slow_query',
- needSave,
+ persistQueryInSession,
DEF_ORDER_OPTIONS
)
- const [memoryQueryOptions, setMemoryQueryOptions] = useState(
- options || DEF_SLOW_QUERY_OPTIONS
- )
- const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState(
- QUERY_OPTIONS,
- { defaultValue: options || DEF_SLOW_QUERY_OPTIONS }
- )
- const queryOptions = useMemo(
- () => (needSave ? sessionQueryOptions : memoryQueryOptions),
- [needSave, memoryQueryOptions, sessionQueryOptions]
+ const { queryOptions, setQueryOptions } = useQueryOptions(
+ initialQueryOptions,
+ persistQueryInSession
)
const [allSchemas, setAllSchemas] = useState([])
- const [loadingSlowQueries, setLoadingSlowQueries] = useState(false)
- const [slowQueries, setSlowQueries] = useState([])
- const [refreshTimes, setRefreshTimes] = useState(0)
-
- const queryTimeRange = useMemo(() => {
- const [beginTime, endTime] = calcTimeRange(queryOptions.timeRange)
- return { beginTime, endTime }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [queryOptions, refreshTimes])
-
- function setQueryOptions(newOptions: ISlowQueryOptions) {
- if (needSave) {
- setSessionQueryOptions(newOptions)
- } else {
- setMemoryQueryOptions(newOptions)
- }
- }
-
- const [errors, setErrors] = useState([])
-
- const selectedFields = useMemo(
- () => getSelectedFields(visibleColumnKeys, derivedFields).join(','),
- [visibleColumnKeys]
+ const [isOptionsLoading, setOptionsLoading] = useState(true)
+ const [data, setData] = useState(undefined)
+ const [isDataLoading, setDataLoading] = useState(false)
+ const [isDataLoadedSlowly, setDataLoadedSlowly] = useState(
+ null
)
+ const [errors, setErrors] = useState([])
+ const { schemaColumns, isLoading: isColumnsLoading } = useSchemaColumns()
- const cacheKey = useMemo(() => {
- const { schemas, digest, limit, plans, searchText, timeRange } =
- queryOptions
- const { desc, orderBy } = orderOptions
- const cacheKey = `${schemas.join(',')}_${digest}_${limit}_${plans.join(
- ','
- )}_${searchText}_${stringifyTimeRange(
- timeRange
- )}_${desc}_${orderBy}_${selectedFields}`
- return cacheKey
- }, [queryOptions, orderOptions, selectedFields])
-
- function refresh() {
- cacheMgr?.remove(cacheKey)
-
- setErrors([])
- setRefreshTimes((prev) => prev + 1)
- }
-
- useEffect(() => {
+ // Reload these options when sending a new request.
+ useChange(() => {
async function querySchemas() {
try {
const res = await client.getInstance().infoListDatabases({
@@ -158,41 +156,69 @@ export default function useSlowQueryTableController(
}
}
- querySchemas()
- }, [])
-
- const { schemaColumns, isLoading: isSchemaLoading } = useSchemaColumns()
-
- const tableColumns = useMemo(
- () => slowQueryColumns(slowQueries, schemaColumns, showFullSQL),
- [slowQueries, schemaColumns, showFullSQL]
- )
-
- useEffect(() => {
- if (!selectedFields.length) {
- setSlowQueries([])
- setLoadingSlowQueries(false)
- return
+ async function doRequest() {
+ setOptionsLoading(true)
+ try {
+ await Promise.all([
+ querySchemas(),
+ // Multiple query options can be added later
+ ])
+ } finally {
+ setOptionsLoading(false)
+ }
}
+ doRequest()
+ }, [queryOptions])
+
+ useChange(() => {
async function getSlowQueryList() {
- const cacheItem = cacheMgr?.get(cacheKey)
- if (cacheItem) {
- setSlowQueries(cacheItem)
+ // Try cache if options are unchanged.
+ // Note: When clicking "Query" manually, cache will be cleared before reach here. So that it
+ // will always send a request without looking up in the cache.
+
+ // The cache key is built over queryOptions, instead of evaluated one.
+ // So that when passing in same relative times options (e.g. Recent 15min)
+ // the cache can be reused.
+ const cacheKey = JSON.stringify(queryOptions)
+ {
+ const cache = cacheMgr?.get(cacheKey)
+ if (cache) {
+ const cacheCloned = JSON.parse(
+ JSON.stringify(cache)
+ ) as RuntimeCacheEntity
+ setData(cacheCloned.data)
+ setDataLoadedSlowly(cacheCloned.isDataLoadedSlowly)
+ setDataLoading(false)
+ return
+ }
+ }
+
+ // May be caused by visibleColumnKeys is empty (when available columns are not yet loaded)
+ // In this case, we don't send any requests.
+ const actualVisibleColumnKeys = getSelectedFields(
+ queryOptions.visibleColumnKeys,
+ derivedFields
+ ).join(',')
+ if (actualVisibleColumnKeys.length === 0) {
return
}
- setLoadingSlowQueries(true)
+ const requestBeginAt = performance.now()
+ setDataLoading(true)
+
+ const timeRange = toTimeRangeValue(queryOptions.timeRange)
+
try {
const res = await client
.getInstance()
.slowQueryListGet(
- queryTimeRange.beginTime,
+ timeRange[0],
queryOptions.schemas,
orderOptions.desc,
queryOptions.digest,
- queryTimeRange.endTime,
- selectedFields,
+ timeRange[1],
+ actualVisibleColumnKeys,
queryOptions.limit,
orderOptions.orderBy,
queryOptions.plans,
@@ -201,54 +227,34 @@ export default function useSlowQueryTableController(
errorStrategy: ErrorStrategy.Custom,
}
)
- setSlowQueries(res.data || [])
- cacheMgr?.set(cacheKey, res.data || [])
+ const data = res?.data || []
+ setData(data)
setErrors([])
+
+ const elapsed = performance.now() - requestBeginAt
+ const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD
+ setDataLoadedSlowly(isLoadSlow)
+
+ const cacheEntity: RuntimeCacheEntity = {
+ data,
+ isDataLoadedSlowly: isLoadSlow,
+ }
+ cacheMgr?.set(cacheKey, cacheEntity)
} catch (e) {
- setErrors((prev) => prev.concat(e as Error))
+ setData(undefined)
+ setErrors((prev) => prev.concat(e))
+ } finally {
+ setDataLoading(false)
}
- setLoadingSlowQueries(false)
}
- if (isSchemaLoading) {
- return
- }
getSlowQueryList()
- }, [
- queryOptions,
- orderOptions,
- queryTimeRange,
- selectedFields,
- refreshTimes,
- cacheKey,
- cacheMgr,
- isSchemaLoading,
- ])
-
- const [downloading, setDownloading] = useState(false)
-
- async function downloadCSV() {
- try {
- setDownloading(true)
- const res = await client.getInstance().slowQueryDownloadTokenPost({
- fields: '*',
- db: queryOptions.schemas,
- digest: queryOptions.digest,
- text: queryOptions.searchText,
- plans: queryOptions.plans,
- orderBy: orderOptions.orderBy,
- desc: orderOptions.desc,
- end_time: queryTimeRange.endTime,
- begin_time: queryTimeRange.beginTime,
- })
- const token = res.data
- if (token) {
- window.location.href = `${client.getBasePath()}/slow_query/download?token=${token}`
- }
- } finally {
- setDownloading(false)
- }
- }
+ }, [queryOptions])
+
+ const availableColumnsInTable = useMemo(
+ () => slowQueryColumns(data ?? [], schemaColumns, showFullSQL),
+ [data, schemaColumns, showFullSQL]
+ )
const { saveClickedItemIndex, getClickedItemIndex } =
useCacheItemIndex(cacheMgr)
@@ -256,22 +262,18 @@ export default function useSlowQueryTableController(
return {
queryOptions,
setQueryOptions,
+
orderOptions,
changeOrder,
- refresh,
- allSchemas,
- loadingSlowQueries,
- slowQueries,
- queryTimeRange,
+ isLoading: isColumnsLoading || isDataLoading || isOptionsLoading,
+ data,
+ isDataLoadedSlowly,
+ allSchemas,
errors,
- tableColumns,
- visibleColumnKeys,
-
- downloading,
- downloadCSV,
+ availableColumnsInTable,
saveClickedItemIndex,
getClickedItemIndex,
diff --git a/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx b/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx
index 5dbbf0e0ac..4019746101 100644
--- a/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx
+++ b/ui/lib/apps/Statement/pages/Detail/SlowQueryTab.tsx
@@ -3,31 +3,24 @@ import SlowQueriesTable from '@lib/apps/SlowQuery/components/SlowQueriesTable'
import { IQuery } from './PlanDetail'
import useSlowQueryTableController, {
DEF_SLOW_QUERY_OPTIONS,
- DEF_SLOW_QUERY_COLUMN_KEYS,
} from '@lib/apps/SlowQuery/utils/useSlowQueryTableController'
+import { fromTimeRangeValue } from '@lib/components'
export interface ISlowQueryTabProps {
query: IQuery
}
export default function SlowQueryTab({ query }: ISlowQueryTabProps) {
- const controller = useSlowQueryTableController(
- undefined,
- DEF_SLOW_QUERY_COLUMN_KEYS,
- false,
- {
+ const controller = useSlowQueryTableController({
+ initialQueryOptions: {
...DEF_SLOW_QUERY_OPTIONS,
- timeRange: {
- type: 'absolute',
- value: [query.beginTime!, query.endTime!],
- },
- schemas: [query.schema!],
+ timeRange: fromTimeRangeValue([query.beginTime!, query.endTime!]),
limit: 100,
digest: query.digest!,
plans: query.plans,
},
- false
- )
+ persistQueryInSession: false,
+ })
return
}
diff --git a/ui/lib/apps/Statement/pages/List/index.tsx b/ui/lib/apps/Statement/pages/List/index.tsx
index 50b7f221ed..6f2a8f28fe 100644
--- a/ui/lib/apps/Statement/pages/List/index.tsx
+++ b/ui/lib/apps/Statement/pages/List/index.tsx
@@ -17,6 +17,7 @@ import {
SettingOutlined,
ExportOutlined,
MenuOutlined,
+ QuestionCircleOutlined,
} from '@ant-design/icons'
import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'
import { useTranslation } from 'react-i18next'
@@ -28,8 +29,8 @@ import {
MultiSelect,
TimeRangeSelector,
TimeRange,
- calcTimeRange,
DateTime,
+ toTimeRangeValue,
} from '@lib/components'
import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'
import { StatementsTable } from '../../components'
@@ -42,6 +43,7 @@ import styles from './List.module.less'
import { useDebounceFn, useMemoizedFn } from 'ahooks'
import { useDeepCompareChange } from '@lib/utils/useChange'
import client, { StatementModel } from '@lib/client'
+import { isDistro } from '@lib/utils/distroStringsRes'
const STMT_VISIBLE_COLUMN_KEYS = 'statement.visible_column_keys'
const STMT_SHOW_FULL_SQL = 'statement.show_full_sql'
@@ -161,12 +163,12 @@ export default function StatementsOverview() {
const downloadCSV = useMemoizedFn(async () => {
// use last effective query options
- const realTimeRange = calcTimeRange(controller.queryOptions.timeRange)
+ const timeRangeValue = toTimeRangeValue(controller.queryOptions.timeRange)
try {
setDownloading(true)
const res = await client.getInstance().statementsDownloadTokenPost({
- begin_time: realTimeRange[0],
- end_time: realTimeRange[1],
+ begin_time: timeRangeValue[0],
+ end_time: timeRangeValue[1],
fields: '*',
schemas: controller.queryOptions.schemas,
stmt_types: controller.queryOptions.stmtTypes,
@@ -257,7 +259,12 @@ export default function StatementsOverview() {
}
/>
)}
-
+
setShowSettings(true)}
data-e2e="statement_setting"
@@ -271,6 +278,20 @@ export default function StatementsOverview() {
+ {!isDistro && (
+
+ {
+ window.open(t('statement.settings.help_url'), '_blank')
+ }}
+ />
+
+ )}
@@ -312,9 +333,20 @@ export default function StatementsOverview() {
title={t('statement.settings.disabled_result.title')}
subTitle={t('statement.settings.disabled_result.sub_title')}
extra={
-
+
+
+ {!isDistro && (
+
+ )}
+
}
/>
)}
diff --git a/ui/lib/apps/Statement/translations/en.yaml b/ui/lib/apps/Statement/translations/en.yaml
index 9fcd8e77ea..7783440aab 100755
--- a/ui/lib/apps/Statement/translations/en.yaml
+++ b/ui/lib/apps/Statement/translations/en.yaml
@@ -69,6 +69,8 @@ statement:
save: Save
close: Disable
cancel: Cancel
+ help: Help
+ help_url: https://docs.pingcap.com/tidb/dev/dashboard-statement-list
fields:
table_names: Table Names
related_schemas: Database
diff --git a/ui/lib/apps/Statement/translations/zh.yaml b/ui/lib/apps/Statement/translations/zh.yaml
index f2ea6f7d0f..a69f033546 100755
--- a/ui/lib/apps/Statement/translations/zh.yaml
+++ b/ui/lib/apps/Statement/translations/zh.yaml
@@ -69,6 +69,8 @@ statement:
save: 保存
close: 确认
cancel: 取消
+ help: 帮助
+ help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-statement-list
fields:
related_schemas: 数据库
related_schemas_tooltip: SQL 语句涉及的数据库
diff --git a/ui/lib/apps/Statement/utils/useStatementTableController.ts b/ui/lib/apps/Statement/utils/useStatementTableController.ts
index 05c201eaf1..a88ad2c395 100644
--- a/ui/lib/apps/Statement/utils/useStatementTableController.ts
+++ b/ui/lib/apps/Statement/utils/useStatementTableController.ts
@@ -255,6 +255,7 @@ export default function useStatementTableController({
timeRange,
}
setData(data)
+ setErrors([])
const elapsed = performance.now() - requestBeginAt
const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD
@@ -266,6 +267,7 @@ export default function useStatementTableController({
}
cacheMgr?.set(cacheKey, cacheEntity)
} catch (e) {
+ setData(undefined)
setErrors((prev) => prev.concat(e))
} finally {
setDataLoading(false)
diff --git a/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx b/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx
index c54e30bbb0..de77bb4439 100644
--- a/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx
+++ b/ui/lib/apps/TopSQL/components/Filter/InstanceSelect.tsx
@@ -67,6 +67,7 @@ export function InstanceSelect({
onChange(instance)
}}
disabled={disabled}
+ data-e2e="instance-selector"
{...otherProps}
>
{instanceGroups.map((instanceGroup) => (
diff --git a/ui/lib/apps/TopSQL/pages/List/List.tsx b/ui/lib/apps/TopSQL/pages/List/List.tsx
index 991bfee087..91760bd55c 100644
--- a/ui/lib/apps/TopSQL/pages/List/List.tsx
+++ b/ui/lib/apps/TopSQL/pages/List/List.tsx
@@ -1,7 +1,11 @@
import { BrushEndListener, BrushEvent } from '@elastic/charts'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Space, Button, Spin, Alert, Tooltip, Drawer, Result } from 'antd'
-import { LoadingOutlined, SettingOutlined } from '@ant-design/icons'
+import {
+ LoadingOutlined,
+ QuestionCircleOutlined,
+ SettingOutlined,
+} from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useMount, useSessionStorage } from 'react-use'
import { useMemoizedFn } from 'ahooks'
@@ -29,6 +33,7 @@ import { ListChart } from './ListChart'
import { SettingsForm } from './SettingsForm'
import { onLegendItemOver, onLegendItemOut } from './legendAction'
import { InstanceType } from './ListDetail/ListDetailTable'
+import { isDistro } from '@lib/utils/distroStringsRes'
const TOP_N = 5
const CHART_BAR_WIDTH = 8
@@ -118,17 +123,19 @@ export function TopSQLList() {
{!isConfigLoading && !topSQLConfig?.enable && haveHistoryData && (
{t(`topsql.alert_header.body`)}
+ {` `}
{
setShowSettings(true)
telemetry.clickSettings('bannerTips')
}}
>
- {` ${t('topsql.alert_header.settings')}`}
+ {t('topsql.alert_header.settings')}
>
}
@@ -178,14 +185,34 @@ export function TopSQLList() {
-
+
{
setShowSettings(true)
telemetry.clickSettings('settingIcon')
}}
/>
+ {!isDistro && (
+
+ {
+ window.open(t('topsql.settings.help_url'), '_blank')
+ }}
+ />
+
+ )}
@@ -196,20 +223,34 @@ export function TopSQLList() {
title={t('topsql.settings.disabled_result.title')}
subTitle={t('topsql.settings.disabled_result.sub_title')}
extra={
-
+
+
+ {!isDistro && (
+
+ )}
+
}
/>
) : (
<>
-
+
(
({ onBrushEnd, data, timeWindowSize, timeRangeTimestamp }, ref) => {
const { t } = useTranslation()
- // We need to update data and xDomain.minInterval at same time on the legacy @elastic/charts
- // to avoid `Error: custom xDomain is invalid, custom minInterval is greater than computed minInterval`
- // https://github.com/elastic/elastic-charts/pull/933
- // TODO: update @elastic/charts
-
// And we need update all the data at the same time and let the chart refresh only once for a better experience.
const [bundle, setBundle] = useState({
data,
diff --git a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx
index f067b86516..271a7fdfb9 100644
--- a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx
+++ b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx
@@ -33,7 +33,7 @@ export function ListDetailContent({
const togglePlanExpanded = () => setPlanExpanded((prev) => !prev)
return (
-
+
}
@@ -66,7 +67,7 @@ export function ListDetailContent({
label={
-
+
}
>
@@ -79,7 +80,10 @@ export function ListDetailContent({
label={
-
+
}
>
@@ -97,7 +101,7 @@ export function ListDetailContent({
expanded={planExpanded}
onClick={togglePlanExpanded}
/>
-
+
}
>
diff --git a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx
index 7b5d5a9267..91845d97de 100644
--- a/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx
+++ b/ui/lib/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx
@@ -169,6 +169,11 @@ export function ListDetailTable({
return (
<>
{t('topsql.table.others')}
@@ -131,6 +132,11 @@ export function ListTable({
-
+
@@ -94,6 +97,7 @@ export function SettingsForm({ onClose, onConfigUpdated }: Props) {
htmlType="submit"
loading={submitting}
disabled={!isWriteable}
+ data-e2e="topsql_settings_save"
>
{t('topsql.settings.actions.save')}
diff --git a/ui/lib/apps/TopSQL/translations/en.yaml b/ui/lib/apps/TopSQL/translations/en.yaml
index b23b70b10e..990e8e3132 100755
--- a/ui/lib/apps/TopSQL/translations/en.yaml
+++ b/ui/lib/apps/TopSQL/translations/en.yaml
@@ -6,7 +6,7 @@ topsql:
settings: Settings
settings:
title: Settings
- open_setting: Open Settings
+ open_settings: Open Settings
disable_feature: Disable Top SQL Feature
disable_warning: Are you sure want to disable this feature?
enable: Enable Feature
@@ -21,6 +21,8 @@ topsql:
enable_info:
title: Success
content: Top SQL is enabled now and is collecting data. You need to wait for about 1 minute to view this data.
+ help: Help
+ help_url: https://docs.pingcap.com/tidb/dev/top-sql
refresh: Refresh
chart:
cpu_time: CPU Time
diff --git a/ui/lib/apps/TopSQL/translations/zh.yaml b/ui/lib/apps/TopSQL/translations/zh.yaml
index 8244a826f8..58413ef87b 100755
--- a/ui/lib/apps/TopSQL/translations/zh.yaml
+++ b/ui/lib/apps/TopSQL/translations/zh.yaml
@@ -6,7 +6,7 @@ topsql:
settings: 设置
settings:
title: 设置
- open_setting: 打开设置
+ open_settings: 打开设置
disable_feature: 关闭 Top SQL 功能
disable_warning: 确认要关闭该功能吗?
enable: 启用功能
@@ -21,6 +21,8 @@ topsql:
enable_info:
title: 成功
content: Top SQL 功能现在已启用,正在收集数据。您需要等待大约 1 分钟时间以便看到该数据。
+ help: 帮助
+ help_url: https://docs.pingcap.com/zh/tidb/dev/top-sql
refresh: 刷新
chart:
cpu_time: CPU 耗时
diff --git a/ui/lib/components/ActionsButton/index.tsx b/ui/lib/components/ActionsButton/index.tsx
deleted file mode 100644
index 1aa9d9de81..0000000000
--- a/ui/lib/components/ActionsButton/index.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react'
-import { Button, Dropdown, Menu } from 'antd'
-
-export type Action = {
- key: string
- text: string
-}
-
-export type ActionsButtonProps = {
- actions: Action[]
- disabled: boolean
- onClick: (action: string) => void
-}
-
-export default function ActionsButton({
- actions,
- disabled,
- onClick,
-}: ActionsButtonProps) {
- if (actions.length === 0) {
- throw new Error('actions should at least have one action')
- }
-
- if (disabled) {
- return null
- }
-
- // actions.length > 0
- const mainAction = actions[0]
- if (actions.length === 1) {
- return (
-
- )
- }
-
- // actions.length > 1
- const menu = (
-
- )
- return (
- onClick(mainAction.key)}>
- {mainAction.text}
-
- )
-}
diff --git a/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx b/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx
index d4caf9c262..41bc0fa347 100644
--- a/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx
+++ b/ui/lib/components/AutoRefreshButton/AutoRefreshButton.tsx
@@ -81,7 +81,7 @@ export function AutoRefreshButton({
{options.map((sec) => {
return (
-
+
{getValueFormat('s')(sec, 0)}
)
@@ -142,6 +142,7 @@ export function AutoRefreshButton({
return (
,
+ HTMLSpanElement
+ > {
data?: string
displayVariant?: DisplayVariant
}
@@ -42,7 +46,11 @@ for (const key in translations) {
})
}
-function CopyLink({ data, displayVariant = 'default' }: ICopyLinkProps) {
+function CopyLink({
+ data,
+ displayVariant = 'default',
+ ...otherProps
+}: ICopyLinkProps) {
const { t } = useTranslation()
const [showCopied, setShowCopied] = useState(false)
@@ -56,7 +64,7 @@ function CopyLink({ data, displayVariant = 'default' }: ICopyLinkProps) {
}
return (
-
+
{!showCopied && (
diff --git a/ui/lib/components/TimeRangeSelector/index.tsx b/ui/lib/components/TimeRangeSelector/index.tsx
index cd972f2a0c..96948f95b1 100644
--- a/ui/lib/components/TimeRangeSelector/index.tsx
+++ b/ui/lib/components/TimeRangeSelector/index.tsx
@@ -55,7 +55,7 @@ export function toTimeRangeValue(timeRange?: TimeRange): TimeRangeValue {
return [...t2.value]
} else {
const now = dayjs().unix()
- return [now - t2.value, now]
+ return [now - t2.value, now + 1]
}
}
@@ -135,7 +135,10 @@ function TimeRangeSelector({
})
const dropdownContent = (
-
+
{t(
diff --git a/ui/lib/components/index.ts b/ui/lib/components/index.ts
index de37b22b04..3b373eeb64 100644
--- a/ui/lib/components/index.ts
+++ b/ui/lib/components/index.ts
@@ -53,8 +53,6 @@ export * from './AppearAnimate'
export { default as AppearAnimate } from './AppearAnimate'
export * from './Blink'
export { default as Blink } from './Blink'
-export * from './ActionsButton'
-export { default as ActionsButton } from './ActionsButton'
export * from './DrawerFooter'
export { default as DrawerFooter } from './DrawerFooter'
diff --git a/ui/lib/utils/distroAssets.ts b/ui/lib/utils/distroAssets.ts
index b354004e21..d06e3d43c1 100644
--- a/ui/lib/utils/distroAssets.ts
+++ b/ui/lib/utils/distroAssets.ts
@@ -10,6 +10,6 @@ if (timestamp === '__DISTRO_ASSETS_RES_TIMESTAMP__') {
const logoSvg = `${publicPathPrefix}/distro-res/logo.svg?t=${timestamp}`
const lightLogoSvg = `${publicPathPrefix}/distro-res/logo-icon-light.svg?t=${timestamp}`
-const landingSvg = `${publicPathPrefix}/distro-res/landing.svg?t=${timestamp}`
+const landingSvg = `${publicPathPrefix}/distro-res/landing.png?t=${timestamp}`
export { logoSvg, lightLogoSvg, landingSvg }
diff --git a/ui/lib/utils/store.ts b/ui/lib/utils/store.ts
index 3951d8a14f..0807e077b4 100644
--- a/ui/lib/utils/store.ts
+++ b/ui/lib/utils/store.ts
@@ -30,12 +30,12 @@ export const useIsFeatureSupport = (feature: string) =>
export const useNgmState = () => store.useState((s) => s.appInfo?.ngm_state)
-export async function reloadWhoAmI() {
+export async function reloadWhoAmI(): Promise {
if (!getAuthToken()) {
store.update((s) => {
s.whoAmI = undefined
})
- return
+ return false
}
try {
@@ -45,10 +45,12 @@ export async function reloadWhoAmI() {
store.update((s) => {
s.whoAmI = resp.data
})
+ return true
} catch (ex) {
store.update((s) => {
s.whoAmI = undefined
})
+ return false
}
}
diff --git a/ui/package.json b/ui/package.json
index b48d1432fe..aa192ac24f 100755
--- a/ui/package.json
+++ b/ui/package.json
@@ -68,9 +68,11 @@
"build": "gulp build",
"fmt": "prettier --write .",
"gen:browserlist": "gulp gen:browserlist",
- "run:e2e-test:compat-features": "cypress run --spec cypress/integration/**/*.compat_spec.js",
- "run:e2e-test:common-features": "cypress run --spec cypress/integration/**/*.spec.js",
- "open:cypress": "cypress open"
+ "run:e2e-test:compat-features": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.compat_spec.[jt]s",
+ "run:e2e-test:common-features": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.spec.[jt]s",
+ "run:e2e-test:without-ngm": "TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.without_ngm_spec.[jt]s",
+ "run:e2e-test:specify": "TZ=Asia/Shanghai cypress run",
+ "open:cypress": "TZ=Asia/Shanghai cypress open"
},
"husky": {
"hooks": {
@@ -91,8 +93,9 @@
},
"devDependencies": {
"@baurine/esbuild-plugin-babel": "^0.3.0",
- "@baurine/esbuild-plugin-postcss3": "^0.2.3",
+ "@baurine/esbuild-plugin-postcss3": "^0.3.3",
"@cypress/code-coverage": "^3.9.12",
+ "@cypress/skip-test": "^2.6.1",
"@openapitools/openapi-generator-cli": "^2.5.1",
"@types/d3": "^5.7.2",
"@types/d3-graphviz": "^2.6.7",
@@ -108,6 +111,7 @@
"clipboardy": "2.3.0",
"customize-cra": "^1.0.0",
"cypress": "8.5.0",
+ "cypress-image-snapshot": "^4.0.1",
"dotenv": "^16.0.0",
"esbuild": "^0.14.38",
"esbuild-plugin-yaml": "^0.0.1",
diff --git a/ui/public/distro-res/landing.png b/ui/public/distro-res/landing.png
new file mode 100644
index 0000000000..b8ed133f02
Binary files /dev/null and b/ui/public/distro-res/landing.png differ
diff --git a/ui/public/distro-res/landing.svg b/ui/public/distro-res/landing.svg
deleted file mode 100644
index 5d66f97609..0000000000
--- a/ui/public/distro-res/landing.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/ui/src/style.less b/ui/src/style.less
index e7fed5344d..7cc7bb3135 100644
--- a/ui/src/style.less
+++ b/ui/src/style.less
@@ -1,2 +1,2 @@
-@import 'antd/dist/antd.less';
-@import '../lib/antd.less';
+@import 'antd/lib/style/components.less';
+// it is expected to import 'antd/es/style/components.less' but it doesn't exist this file
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 5066987ab3..64a8f72a75 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1788,10 +1788,10 @@
resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-babel/-/esbuild-plugin-babel-0.3.0.tgz#b7409eb2a388fb03a5dacbf333154ae3604abcc7"
integrity sha512-AKa/svOQUhHE7HNa8vU3qEXL+U1ln211Sl5mCBX+AQIldEEcvA80udzVENGh6VEztUtbpsXRsPdP9VI8tyX96w==
-"@baurine/esbuild-plugin-postcss3@^0.2.3":
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-postcss3/-/esbuild-plugin-postcss3-0.2.3.tgz#c5bf3bbec4c5696cb23b854b6c82cbd7e5837efb"
- integrity sha512-L5D/QA/CMpo7FGq6g/6KKRDTuKGe/276091h6UrEX9HD9jBiQW/1NK1Xs8UB+Gx4r0eVeyK73forEc3rYtKOEA==
+"@baurine/esbuild-plugin-postcss3@^0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@baurine/esbuild-plugin-postcss3/-/esbuild-plugin-postcss3-0.3.3.tgz#496e95566cb496f17d7f98a5f8431a1160f92fdf"
+ integrity sha512-0GiZRUW0LMtBaOaX+ICcpdhzkixPR6RzCnwOrL2GtWYQh1aPUegwEnHwAs+GHb4g1LezC0h2I3NCgs0TTjbQEw==
dependencies:
autoprefixer "^10.2.5"
bulma "^0.9.3"
@@ -2923,7 +2923,7 @@ ansi-colors@^4.1.1:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
+ansi-escapes@^4.1.0, ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
@@ -2957,6 +2957,11 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+ integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
+
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -3052,6 +3057,13 @@ apache-md5@^1.0.6:
resolved "https://registry.yarnpkg.com/apache-md5/-/apache-md5-1.1.7.tgz#dcef1802700cc231d60c5e08fd088f2f9b36375a"
integrity sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==
+app-path@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.npmmirror.com/app-path/-/app-path-3.3.0.tgz#0342a909db37079c593979c720f99e872475eba3"
+ integrity sha512-EAgEXkdcxH1cgEePOSsmUtw9ItPl0KTxnh/pj9ZbhvbKbij9x0oX6PWpGnorDr0DS5AosLgoa5n3T/hZmKQpYA==
+ dependencies:
+ execa "^1.0.0"
+
append-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1"
@@ -3478,12 +3490,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
-base64-js@^1.0.2:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
- integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
-
-base64-js@^1.3.1:
+base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -3974,7 +3981,18 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^2.0.0, chalk@^2.4.2:
+chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -4664,6 +4682,18 @@ customize-cra@^1.0.0:
dependencies:
lodash.flow "^3.5.0"
+cypress-image-snapshot@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/cypress-image-snapshot/-/cypress-image-snapshot-4.0.1.tgz#59084e713a8d03500c8e053ad7a76f3f18609648"
+ integrity sha512-PBpnhX/XItlx3/DAk5ozsXQHUi72exybBNH5Mpqj1DVmjq+S5Jd9WE5CRa4q5q0zuMZb2V2VpXHth6MjFpgj9Q==
+ dependencies:
+ chalk "^2.4.1"
+ fs-extra "^7.0.1"
+ glob "^7.1.3"
+ jest-image-snapshot "4.2.0"
+ pkg-dir "^3.0.0"
+ term-img "^4.0.0"
+
cypress-real-events@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.0.tgz#ad6a78de33af3af0e6437f5c713e30691c44472c"
@@ -5716,7 +5746,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -6389,6 +6419,13 @@ find-up@^2.1.0:
dependencies:
locate-path "^2.0.0"
+find-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+ integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+ dependencies:
+ locate-path "^3.0.0"
+
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -6560,6 +6597,15 @@ fs-extra@^10.0.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
+fs-extra@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+ integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
fs-extra@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
@@ -6669,6 +6715,11 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+get-stdin@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
+ integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=
+
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -6866,6 +6917,11 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
+glur@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmmirror.com/glur/-/glur-1.1.2.tgz#f20ea36db103bfc292343921f1f91e83c3467689"
+ integrity sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==
+
graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@@ -6953,6 +7009,13 @@ gulplog@^1.0.0:
dependencies:
glogg "^1.0.0"
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmmirror.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
+ dependencies:
+ ansi-regex "^2.0.0"
+
has-bigints@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@@ -8010,6 +8073,29 @@ iterare@1.2.1:
resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042"
integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==
+iterm2-version@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.npmmirror.com/iterm2-version/-/iterm2-version-4.2.0.tgz#b78069f747f34a772bc7dc17bda5bd9ed5e09633"
+ integrity sha512-IoiNVk4SMPu6uTcK+1nA5QaHNok2BMDLjSl5UomrOixe5g4GkylhPwuiGdw00ysSCrXAKNMfFTu+u/Lk5f6OLQ==
+ dependencies:
+ app-path "^3.2.0"
+ plist "^3.0.1"
+
+jest-image-snapshot@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmmirror.com/jest-image-snapshot/-/jest-image-snapshot-4.2.0.tgz#559d7ade69e9918517269cef184261c80029a69e"
+ integrity sha512-6aAqv2wtfOgxiJeBayBCqHo1zX+A12SUNNzo7rIxiXh6W6xYVu8QyHWkada8HeRi+QUTHddp0O0Xa6kmQr+xbQ==
+ dependencies:
+ chalk "^1.1.3"
+ get-stdin "^5.0.1"
+ glur "^1.1.2"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ pixelmatch "^5.1.0"
+ pngjs "^3.4.0"
+ rimraf "^2.6.2"
+ ssim.js "^3.1.1"
+
js-cookie@^2.2.1, js-cookie@^2.x.x:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
@@ -8111,6 +8197,13 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonfile@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
@@ -8346,6 +8439,14 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
+locate-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+ integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
+ dependencies:
+ p-locate "^3.0.0"
+ path-exists "^3.0.0"
+
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
@@ -8438,7 +8539,7 @@ lodash.unionwith@^4.6.0:
resolved "https://registry.yarnpkg.com/lodash.unionwith/-/lodash.unionwith-4.6.0.tgz#74d140b5ca8146e6c643c3724f5152538d9ac1f0"
integrity sha1-dNFAtcqBRubGQ8NyT1FSU42awfA=
-lodash@4.17.21, lodash@^4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21:
+lodash@4.17.21, lodash@^4, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -8948,6 +9049,13 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
mkdirp@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@@ -9552,7 +9660,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
-p-limit@^2.2.0:
+p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
@@ -9566,6 +9674,13 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"
+p-locate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+ integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
+ dependencies:
+ p-limit "^2.0.0"
+
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -9849,6 +9964,20 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+pixelmatch@^5.1.0:
+ version "5.2.1"
+ resolved "https://registry.npmmirror.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65"
+ integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==
+ dependencies:
+ pngjs "^4.0.1"
+
+pkg-dir@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+ integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
+ dependencies:
+ find-up "^3.0.0"
+
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
@@ -9863,6 +9992,14 @@ please-upgrade-node@^3.2.0:
dependencies:
semver-compare "^1.0.0"
+plist@^3.0.1:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987"
+ integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==
+ dependencies:
+ base64-js "^1.5.1"
+ xmlbuilder "^9.0.7"
+
plugin-error@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
@@ -9873,6 +10010,16 @@ plugin-error@^1.0.1:
arr-union "^3.1.0"
extend-shallow "^3.0.2"
+pngjs@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
+ integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
+
+pngjs@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmmirror.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe"
+ integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==
+
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -11101,6 +11248,13 @@ rfdc@^1.3.0:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
+rimraf@^2.6.2:
+ version "2.7.1"
+ resolved "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+ integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+ dependencies:
+ glob "^7.1.3"
+
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -11638,6 +11792,11 @@ sshpk@^1.14.1:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
+ssim.js@^3.1.1:
+ version "3.5.0"
+ resolved "https://registry.npmmirror.com/ssim.js/-/ssim.js-3.5.0.tgz#d7276b9ee99b57a5ff0db34035f02f35197e62df"
+ integrity sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==
+
stack-generator@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36"
@@ -11940,6 +12099,11 @@ subarg@^1.0.0:
dependencies:
minimist "^1.1.0"
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmmirror.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+ integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
+
supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
@@ -11988,6 +12152,14 @@ syntax-error@^1.1.1:
dependencies:
acorn-node "^1.2.0"
+term-img@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmmirror.com/term-img/-/term-img-4.1.0.tgz#5b170961f7aa20b2f3b22deb8ad504beb963a8a5"
+ integrity sha512-DFpBhaF5j+2f7kheKFc1ajsAUUDGOaNPpKPtiIMxlbfud6mvfFZuWGnTRpaujUa5J7yl6cIw/h6nyr4mSsENPg==
+ dependencies:
+ ansi-escapes "^4.1.0"
+ iterm2-version "^4.1.0"
+
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@@ -12492,6 +12664,11 @@ unist-util-visit@^4.0.0:
unist-util-is "^5.0.0"
unist-util-visit-parents "^5.0.0"
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
universalify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
@@ -12904,6 +13081,11 @@ write-file-atomic@^3.0.0:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
+xmlbuilder@^9.0.7:
+ version "9.0.7"
+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
+ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
+
xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"