diff --git a/src/plugins/data/common/data_frames/types.ts b/src/plugins/data/common/data_frames/types.ts index 978f2817bcf4..10c92e4aded3 100644 --- a/src/plugins/data/common/data_frames/types.ts +++ b/src/plugins/data/common/data_frames/types.ts @@ -105,6 +105,11 @@ export interface IDataFrameResponse extends SearchResponse { took: number; } +export interface IDataFrameResponseError { + status: number; + message: string; +} + export interface IDataFrameError extends IDataFrameResponse { error: Error; } diff --git a/src/plugins/data/public/query/query_string/language_service/query_result.tsx b/src/plugins/data/public/query/query_string/language_service/query_result.tsx new file mode 100644 index 000000000000..32a52590b5af --- /dev/null +++ b/src/plugins/data/public/query/query_string/language_service/query_result.tsx @@ -0,0 +1,54 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './_recent_query.scss'; + +import { EuiButtonEmpty, EuiPopover, EuiText, EuiContextMenu, EuiPopoverTitle } from '@elastic/eui'; + +import React, { useState } from 'react'; +import { SearchData } from '../../../../../discover/public'; + +export function QueryResult(props: { queryResult: SearchData }) { + console.log('QueryResult', props.queryResult); + const [isPopoverOpen, setPopover] = useState(false); + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const status = props.queryResult.status; + + if (status === 'ready') { + return ( + {}}> + + {'Complete in ' + props.queryResult.queryTime + ' ms'} + + + ); + } + + return ( + + + {'Error'} + + + } + isOpen={isPopoverOpen} + closePopover={() => setPopover(false)} + panelPaddingSize="none" + anchorPosition={'downRight'} + > + Error message +
+ + {props.queryResult.errorMsg && props.queryResult.errorMsg.message} + +
+
+ ); +} diff --git a/src/plugins/data/public/ui/query_editor/query_editor.tsx b/src/plugins/data/public/ui/query_editor/query_editor.tsx index 3c9e81b4824d..998e1623d98b 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -29,6 +29,8 @@ import { DatasetSelector } from '../dataset_selector'; import { QueryControls } from '../../query/query_string/language_service/get_query_control_links'; import { RecentQuery } from '../../query/query_string/language_service/recent_query'; import { DefaultInputProps } from './editors'; +import { SearchData } from '../../../../discover/public'; +import { QueryResult } from '../../query/query_string/language_service/query_result'; const LANGUAGE_ID_SQL = 'SQL'; monaco.languages.register({ id: LANGUAGE_ID_SQL }); @@ -59,6 +61,7 @@ export interface QueryEditorProps { filterBar?: any; prepend?: React.ComponentProps['prepend']; savedQueryManagement?: any; + queryResult?: SearchData; } interface Props extends QueryEditorProps { @@ -356,6 +359,7 @@ export default class QueryEditorUI extends Component { {this.props.query.dataset?.timeFieldName || ''} , + , ], end: [ ; savedQueryManagement?: any; + queryResult?: SearchData; } // Needed for React.lazy @@ -186,6 +189,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { dataTestSubj={props.dataTestSubj} filterBar={props.filterBar} savedQueryManagement={props.savedQueryManagement} + queryResult={props.queryResult} /> ); diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index b3b240dfa2f1..e786050cbc05 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -211,6 +211,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) datePickerRef={props.datePickerRef} isFilterBarPortable={props.isFilterBarPortable} {...overrideDefaultBehaviors(props)} + queryResult={props.queryResult} /> ); diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index bb9a2c7eb28c..bc91368f7d26 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -30,6 +30,7 @@ import { InjectedIntl, injectI18n } from '@osd/i18n/react'; import classNames from 'classnames'; +import { BehaviorSubject } from 'rxjs'; import { compact, get, isEqual } from 'lodash'; import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; @@ -45,6 +46,7 @@ import { QueryEditorTopRow } from '../query_editor'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { FilterOptions } from '../filter_bar/filter_options'; +import { SearchData } from '../../../../discover/public'; interface SearchBarInjectedDeps { opensearchDashboards: OpenSearchDashboardsReactContextValue; @@ -92,6 +94,7 @@ export interface SearchBarOwnProps { onRefresh?: (payload: { dateRange: TimeRange }) => void; indicateNoData?: boolean; + queryResult?: SearchData; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -550,6 +553,7 @@ class SearchBarUI extends Component { indicateNoData={this.props.indicateNoData} datePickerRef={this.props.datePickerRef} savedQueryManagement={searchBarMenu(false, true)} + queryResult={this.props.queryResult} /> ); } diff --git a/src/plugins/discover/public/application/view_components/canvas/index.tsx b/src/plugins/discover/public/application/view_components/canvas/index.tsx index a44ac89c5d62..2e956d6908e9 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -144,6 +144,9 @@ export default function DiscoverCanvas({ setHeaderActionMenu, history, optionalR {fetchState.status === ResultStatus.NO_RESULTS && ( )} + {fetchState.status === ResultStatus.ERROR && ( + + )} {fetchState.status === ResultStatus.UNINITIALIZED && ( refetch$.next()} /> )} diff --git a/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx index 6cccc06cc0ee..939ca3448f08 100644 --- a/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx @@ -21,6 +21,8 @@ import { useDispatch, setSavedQuery, useSelector } from '../../utils/state_manag import './discover_canvas.scss'; import { TopNavMenuItemRenderType } from '../../../../../navigation/public'; +import { SearchData } from '../utils'; +import { query } from '../../../../../console/server/lib/spec_definitions/js/query/dsl'; export interface TopNavProps { opts: { @@ -34,9 +36,10 @@ export interface TopNavProps { export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavProps) => { const { services } = useOpenSearchDashboards(); - const { inspectorAdapters, savedSearch, indexPattern } = useDiscoverContext(); + const { data$, inspectorAdapters, savedSearch, indexPattern } = useDiscoverContext(); const [indexPatterns, setIndexPatterns] = useState(undefined); const [screenTitle, setScreenTitle] = useState(''); + const [queryResult, setQueryResult] = useState(undefined); const state = useSelector((s) => s.discover); const dispatch = useDispatch(); @@ -113,6 +116,15 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro ); }, [savedSearch?.title]); + useEffect(() => { + if (!data$) { + return; + } + const subscription = data$.subscribe((d) => { + setQueryResult(d); + }); + }, [data$]); + const showDatePicker = useMemo(() => (indexPattern ? indexPattern.isTimeBased() : false), [ indexPattern, ]); @@ -160,6 +172,7 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro datePickerRef={opts?.optionalRef?.datePickerRef} groupActions={showActionsInGroup} screenTitle={screenTitle} + queryResult={queryResult} /> ); diff --git a/src/plugins/discover/public/application/view_components/index.ts b/src/plugins/discover/public/application/view_components/index.ts index 45fd68cf1285..7aff16330f66 100644 --- a/src/plugins/discover/public/application/view_components/index.ts +++ b/src/plugins/discover/public/application/view_components/index.ts @@ -5,3 +5,4 @@ export * from './canvas'; export * from './panel'; +export * from './utils'; diff --git a/src/plugins/discover/public/application/view_components/utils/index.tsx b/src/plugins/discover/public/application/view_components/utils/index.tsx new file mode 100644 index 000000000000..c9dbbb829a3f --- /dev/null +++ b/src/plugins/discover/public/application/view_components/utils/index.tsx @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { SearchData, ResultStatus } from './use_search'; diff --git a/src/plugins/discover/public/application/view_components/utils/use_search.ts b/src/plugins/discover/public/application/view_components/utils/use_search.ts index b6c13d4982f2..2c8fafdf6639 100644 --- a/src/plugins/discover/public/application/view_components/utils/use_search.ts +++ b/src/plugins/discover/public/application/view_components/utils/use_search.ts @@ -39,6 +39,7 @@ export enum ResultStatus { LOADING = 'loading', // initial data load READY = 'ready', // results came back NO_RESULTS = 'none', // no results came back + ERROR = 'error', // error occurred } export interface SearchData { @@ -50,6 +51,8 @@ export interface SearchData { bucketInterval?: TimechartHeaderBucketInterval | {}; chartData?: Chart; title?: string; + errorMsg?: any; + queryTime?: number; } export type SearchRefetch = 'refetch' | undefined; @@ -149,6 +152,8 @@ export const useSearch = (services: DiscoverViewServices) => { dataset = searchSource.getField('index'); + let queryTime; + try { // Only show loading indicator if we are fetching when the rows are empty if (fetchStateRef.current.rows?.length === 0) { @@ -180,6 +185,9 @@ export const useSearch = (services: DiscoverViewServices) => { .ok({ json: fetchResp }); const hits = fetchResp.hits.total as number; const rows = fetchResp.hits.hits; + //setQueryTime(inspectorRequest.getTime()); + //console.log('queryTime', queryTime); + queryTime = inspectorRequest.getTime(); let bucketInterval = {}; let chartData; for (const row of rows) { @@ -216,17 +224,25 @@ export const useSearch = (services: DiscoverViewServices) => { indexPattern?.title !== searchSource.getDataFrame()?.name ? searchSource.getDataFrame()?.name : indexPattern?.title, + queryTime, }); } catch (error) { // If the request was aborted then no need to surface this error in the UI if (error instanceof Error && error.name === 'AbortError') return; data$.next({ - status: ResultStatus.NO_RESULTS, - rows: [], + status: ResultStatus.ERROR, + errorMsg: error.body || error, + title: + indexPattern?.title !== searchSource.getDataFrame()?.name + ? searchSource.getDataFrame()?.name + : indexPattern?.title, + queryTime, }); - data.search.showError(error as Error); + console.log('error', error.body); + + //data.search.showError((error.body || error) as Error); } finally { initalSearchComplete.current = true; } diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 164aea1fb5bc..54575735a518 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -40,3 +40,4 @@ export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './sav export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './embeddable'; export { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from './url_generator'; +export { SearchData, ResultStatus } from './application/view_components'; diff --git a/src/plugins/inspector/common/adapters/request/request_responder.ts b/src/plugins/inspector/common/adapters/request/request_responder.ts index a65d1b9bc9cd..539257ffaaeb 100644 --- a/src/plugins/inspector/common/adapters/request/request_responder.ts +++ b/src/plugins/inspector/common/adapters/request/request_responder.ts @@ -86,4 +86,8 @@ export class RequestResponder { public error(response: Response): void { this.finish(RequestStatus.ERROR, response); } + + public getTime() { + return this.request.time; + } } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index ffc6656144f8..70247d27e114 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -31,6 +31,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHeaderLinks, EuiText } from '@elastic/eui'; import classNames from 'classnames'; import React, { ReactElement, useRef } from 'react'; +import { BehaviorSubject } from 'rxjs'; import { MountPoint } from '../../../../core/public'; import { @@ -42,6 +43,7 @@ import { DataSourceMenuProps, createDataSourceMenu } from '../../../data_source_ import { MountPointPortal } from '../../../opensearch_dashboards_react/public'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; +import { SearchData } from '../../../discover/public'; export enum TopNavMenuItemRenderType { IN_PORTAL = 'in_portal', @@ -82,6 +84,7 @@ export type TopNavMenuProps = Omit & * ``` */ setMenuMountPoint?: (menuMount: MountPoint | undefined) => void; + queryResult?: SearchData; }; /* @@ -102,6 +105,7 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { dataSourceMenuConfig, groupActions, screenTitle, + queryResult, ...searchBarProps } = props; @@ -156,6 +160,7 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { ); diff --git a/src/plugins/query_enhancements/common/utils.ts b/src/plugins/query_enhancements/common/utils.ts index bdb8740e4e48..9c73ab351635 100644 --- a/src/plugins/query_enhancements/common/utils.ts +++ b/src/plugins/query_enhancements/common/utils.ts @@ -125,14 +125,6 @@ export class DataFramePolling { } } -export const handleDataFrameError = (response: any) => { - const df = response.body; - if (df.error) { - const jsError = new Error(df.error.response); - return throwError(jsError); - } -}; - export const fetch = (context: EnhancedFetchContext, query: Query, aggConfig?: QueryAggConfig) => { const { http, path, signal } = context; const body = JSON.stringify({ query: { ...query, format: 'jdbc' }, aggConfig }); @@ -143,7 +135,7 @@ export const fetch = (context: EnhancedFetchContext, query: Query, aggConfig?: Q body, signal, }) - ).pipe(tap(handleDataFrameError)); + ); }; export const fetchDataFrame = (context: EnhancedFetchContext, query: Query, df: IDataFrame) => { @@ -156,7 +148,7 @@ export const fetchDataFrame = (context: EnhancedFetchContext, query: Query, df: body, signal, }) - ).pipe(tap(handleDataFrameError)); + ); }; export const fetchDataFramePolling = (context: EnhancedFetchContext, df: IDataFrame) => { diff --git a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts index 4618945a35f7..c8a5165cb565 100644 --- a/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts +++ b/src/plugins/query_enhancements/public/search/ppl_search_interceptor.ts @@ -4,8 +4,7 @@ */ import { trimEnd } from 'lodash'; -import { Observable, throwError } from 'rxjs'; -import { catchError } from 'rxjs/operators'; +import { Observable } from 'rxjs'; import { formatTimePickerDate, Query } from '../../../data/common'; import { DataPublicPluginStart, @@ -52,11 +51,7 @@ export class PPLSearchInterceptor extends SearchInterceptor { const query = this.buildQuery(); - return fetch(context, query, this.getAggConfig(searchRequest, query)).pipe( - catchError((error) => { - return throwError(error); - }) - ); + return fetch(context, query, this.getAggConfig(searchRequest, query)); } public search(request: IOpenSearchDashboardsSearchRequest, options: ISearchOptions) { diff --git a/src/plugins/query_enhancements/server/routes/index.ts b/src/plugins/query_enhancements/server/routes/index.ts index a3673946114d..60a5bed20496 100644 --- a/src/plugins/query_enhancements/server/routes/index.ts +++ b/src/plugins/query_enhancements/server/routes/index.ts @@ -15,6 +15,7 @@ import { ISearchStrategy } from '../../../data/server'; import { API, SEARCH_STRATEGY } from '../../common'; import { registerQueryAssistRoutes } from './query_assist'; import { registerDataSourceConnectionsRoutes } from './data_source_connection'; +import { instance } from '../../../console/public/application/contexts/editor_context/editor_registry'; /** * Defines a route for a specific search strategy. @@ -85,10 +86,9 @@ function defineRoute( ); return res.ok({ body: { ...queryRes } }); } catch (err) { - logger.error(err); return res.custom({ - statusCode: 500, - body: err, + statusCode: err.name, + body: err.message, }); } } diff --git a/src/plugins/query_enhancements/server/search/ppl_search_strategy.ts b/src/plugins/query_enhancements/server/search/ppl_search_strategy.ts index ae2192d4155f..e8fcbba04040 100644 --- a/src/plugins/query_enhancements/server/search/ppl_search_strategy.ts +++ b/src/plugins/query_enhancements/server/search/ppl_search_strategy.ts @@ -10,6 +10,7 @@ import { DATA_FRAME_TYPES, IDataFrameError, IDataFrameResponse, + IDataFrameResponseError, IDataFrameWithAggs, IOpenSearchDashboardsSearchRequest, Query, @@ -41,11 +42,9 @@ export const pplSearchStrategyProvider = ( const rawResponse: any = await pplFacet.describeQuery(context, request); if (!rawResponse.success) { - return { - type: DATA_FRAME_TYPES.ERROR, - body: { error: rawResponse.data }, - took: rawResponse.took, - } as IDataFrameError; + const error = new Error(rawResponse.data.body); + error.name = rawResponse.data.status; + throw error; } const dataFrame = createDataFrame({ @@ -79,7 +78,7 @@ export const pplSearchStrategyProvider = ( took: rawResponse.took, } as IDataFrameResponse; } catch (e) { - logger.error(`pplSearchStrategy: ${e.message}`); + logger.error(`pplSearchStrategy: ${e}`); if (usage) usage.trackError(); throw e; } diff --git a/yarn.lock b/yarn.lock index 9e9e097b9339..d19f58a17a00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15986,7 +15986,7 @@ string-similarity@^4.0.1: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16021,15 +16021,6 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16108,7 +16099,7 @@ stringify-entities@^3.0.1: character-entities-legacy "^1.0.0" xtend "^4.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16150,13 +16141,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -18300,7 +18284,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18326,15 +18310,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"