From 1aa73a57c3088db57fa8bb985ba22aff50511238 Mon Sep 17 00:00:00 2001 From: Leonardo Petrora <29212725+lpetrora@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:59:25 +0100 Subject: [PATCH] Metric labels do not include hostname when two queries are used This change adds a custom label input box and allows the user to customize the labels. It corresponding filter is created, these variables are available: * $filter_site: Site name * $filter_host_name: Host name * $filter_host_in_group: Group containing the host * $filter_service: Service name * $filter_service_in_group: Group containing the service $label is always available and contains the original label sent by Checmk. CMK-15138 --- package.json | 3 +- src/RequestSpec.ts | 2 + src/backend/rest.ts | 23 ++++--- src/backend/web.ts | 22 +++++-- src/types.ts | 9 +++ src/ui/QueryEditor.tsx | 61 +++++++++++++++++- src/ui/components.tsx | 61 ++++++++++++++++++ src/ui/utils.ts | 1 + src/utils.ts | 27 +++++++- src/webapi.ts | 15 +++-- tests/cypress/e2e/spec.cy.ts | 36 ++++++++++- tests/cypress/support/commands.ts | 10 +++ tests/cypress/types.ts | 1 + tests/docker-compose.yml | 2 + tests/package.json | 2 +- tests/yarn.lock | 8 +-- yarn.lock | 104 +++++++++++++++++++++++++++++- 17 files changed, 353 insertions(+), 34 deletions(-) create mode 100644 tests/cypress/types.ts diff --git a/package.json b/package.json index 48140899..c394612a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@vue/compiler-sfc": "3.2.47", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.3", + "cypress": "13.6.0", "eslint": "^8.39.0", "eslint-plugin-jsdoc": "^43.0.7", "eslint-plugin-react": "^7.32.2", @@ -78,5 +79,5 @@ "react-dom": "17.0.2", "tslib": "2.5.3" }, - "packageManager": "yarn@" + "packageManager": "yarn@1.22.21" } diff --git a/src/RequestSpec.ts b/src/RequestSpec.ts index fea63d3a..82a8b2ae 100644 --- a/src/RequestSpec.ts +++ b/src/RequestSpec.ts @@ -31,6 +31,8 @@ export interface RequestSpec { service_in_group: NegatableOption | undefined; graph: string | undefined; + + label: string | undefined; } // subset of RequestSpec used with the Filters Component diff --git a/src/backend/rest.ts b/src/backend/rest.ts index 282a3c43..af030316 100644 --- a/src/backend/rest.ts +++ b/src/backend/rest.ts @@ -8,6 +8,7 @@ import { FieldType, MetricFindValue, MutableDataFrame, + ScopedVars, TimeRange, dateTime, } from '@grafana/data'; @@ -16,7 +17,7 @@ import { Aggregation, GraphType, MetricFindQuery } from 'RequestSpec'; import * as process from 'process'; import { CmkQuery } from '../types'; -import { createCmkContext, replaceVariables, toLiveStatusQuery, updateQuery } from '../utils'; +import { createCmkContext, replaceVariables, toLiveStatusQuery, updateMetricTitles, updateQuery } from '../utils'; import { Backend, DatasourceOptions } from './types'; import { validateRequestSpec } from './validate'; @@ -26,18 +27,20 @@ type RestApiError = { title: string; }; +export type MetricResponse = { + color: string; + data_points: number[]; + line_type: string; + title: string; +}; + type RestApiGraphResponse = { time_range: { start: string; end: string; }; step: number; - metrics: Array<{ - color: string; - data_points: number[]; - line_type: string; - title: string; - }>; + metrics: MetricResponse[]; }; type CommonRequest = { @@ -155,7 +158,7 @@ export default class RestApiBackend implements Backend { const promises = request.targets .filter((target) => !target.hide) .map((target) => { - return this.getSingleGraph(request.range, target); + return this.getSingleGraph(request.range, target, request.scopedVars); }); return await Promise.all(promises).then((data) => ({ data })); } @@ -250,7 +253,7 @@ export default class RestApiBackend implements Backend { return result; } - async getSingleGraph(range: TimeRange, query: CmkQuery): Promise { + async getSingleGraph(range: TimeRange, query: CmkQuery, scopedVars: ScopedVars = {}): Promise { // it's not about a single graph line, but a single chart. grafana supports // to query multiple graphs in one request, but we have to unwind this, as // our api only supports a single chart/query per api call. @@ -322,6 +325,8 @@ export default class RestApiBackend implements Backend { const { time_range, step, metrics } = response.data; + updateMetricTitles(metrics, query, scopedVars); + const timeValues = []; let currentTime: DateTime = dateTime(time_range.start); const endTime: DateTime = dateTime(time_range.end); diff --git a/src/backend/web.ts b/src/backend/web.ts index bf6541a9..abecd68d 100644 --- a/src/backend/web.ts +++ b/src/backend/web.ts @@ -1,10 +1,17 @@ -import { DataQueryRequest, DataQueryResponse, FieldType, MetricFindValue, MutableDataFrame } from '@grafana/data'; +import { + DataQueryRequest, + DataQueryResponse, + FieldType, + MetricFindValue, + MutableDataFrame, + ScopedVars, +} from '@grafana/data'; import { BackendSrvRequest, FetchError, FetchResponse, getBackendSrv } from '@grafana/runtime'; import { MetricFindQuery } from 'RequestSpec'; import { defaults, get, isUndefined, zip } from 'lodash'; import { CmkQuery, defaultQuery } from '../types'; -import { updateQuery } from '../utils'; +import { updateMetricTitles, updateQuery } from '../utils'; import { WebAPiGetGraphResult, WebApiResponse, @@ -89,7 +96,7 @@ export default class WebApiBackend implements Backend { .map((target) => { // TODO: check if the defaults call is still necessary. const query = defaults(target, defaultQuery); - return this.getGraphQuery([from, to], query); + return this.getGraphQuery([from, to], query, options.scopedVars); }); return Promise.all(promises).then((data) => ({ data })); } @@ -145,7 +152,11 @@ export default class WebApiBackend implements Backend { } } - async getGraphQuery(range: number[], query: CmkQuery): Promise> { + async getGraphQuery( + range: number[], + query: CmkQuery, + scopedVars: ScopedVars = {} + ): Promise> { updateQuery(query); const graph = get(query, 'requestSpec.graph'); if (isUndefined(graph) || graph === '') { @@ -173,8 +184,11 @@ export default class WebApiBackend implements Backend { if (response.result_code !== 0) { throw new Error(`${response.result}`); } + const { start_time, step, curves } = response.result; + updateMetricTitles(curves, query, scopedVars); + const frame = new MutableDataFrame({ refId: query.refId, fields: [ diff --git a/src/types.ts b/src/types.ts index 68fb4fcb..077b3208 100644 --- a/src/types.ts +++ b/src/types.ts @@ -78,3 +78,12 @@ export interface SecureJsonData { export interface ResponseDataAutocomplete { choices: Array<[string, string]>; } + +export enum label { + ORIGINAL = '$label', + SITE = '$filter_site', + HOSTNAME = '$filter_host_name', + HOST_IN_GROUP = '$filter_host_in_group', + SERVICE = '$filter_service', + SERVICE_IN_GROUP = '$filter_service_in_group', +} diff --git a/src/ui/QueryEditor.tsx b/src/ui/QueryEditor.tsx index 80b7693f..8d1479d7 100644 --- a/src/ui/QueryEditor.tsx +++ b/src/ui/QueryEditor.tsx @@ -1,12 +1,12 @@ import { QueryEditorProps, SelectableValue } from '@grafana/data'; -import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; +import { Button, Card, Icon, InlineFieldRow, Toggletip, VerticalGroup } from '@grafana/ui'; import React from 'react'; import { DataSource } from '../DataSource'; import { Aggregation, GraphType, RequestSpec } from '../RequestSpec'; -import { CmkQuery, DataSourceOptions } from '../types'; +import { CmkQuery, DataSourceOptions, label } from '../types'; import { aggregationToPresentation, updateQuery } from '../utils'; -import { CheckMkSelect } from './components'; +import { CheckMkSelect, GenericField } from './components'; import { Filters } from './filters'; import { labelForRequestSpecKey } from './utils'; @@ -19,6 +19,8 @@ export const QueryEditor = (props: Props): JSX.Element => { const [qAggregation, setQAggregation] = React.useState(rs.aggregation || 'off'); const [qGraphType, setQGraphType] = React.useState(rs.graph_type || 'predefined_graph'); const [qGraph, setQGraph] = React.useState(rs.graph); + const [qLabel, setQLabel] = React.useState(rs.label); + const filters: Partial = { // make sure to only include keys filters should change, otherwise they could // overwrite other fields! @@ -43,6 +45,7 @@ export const QueryEditor = (props: Props): JSX.Element => { graph_type: qGraphType, graph: qGraph, aggregation: qAggregation, + label: qLabel, }; // TODO: not sure if this is a dirty hack or a great solution: @@ -109,6 +112,56 @@ export const QueryEditor = (props: Props): JSX.Element => { /> ); + const LabelsTooltip = ( + + + These variables are supported if provided as filters: +
    + {[ + [label.SITE, 'Site name'], + [label.HOSTNAME, 'Host name'], + [label.HOST_IN_GROUP, 'Group containing the host'], + [label.SERVICE, 'Service name'], + [label.SERVICE_IN_GROUP, 'Group containing the service'], + ].map((lbl) => ( +
  • + {lbl[0]}: {lbl[1]} +
  • + ))} +
+
+ Also, the original label is available as +
    +
  • + {label.ORIGINAL} +
  • +
+
+ + } + closeButton={false} + placement="right-end" + > + +
+ ); + + const labelField = ( + + ); + if (editionMode === 'RAW') { return ( @@ -122,6 +175,7 @@ export const QueryEditor = (props: Props): JSX.Element => { /> {graphTypeSelect} {graphSelect} + {labelField} ); } else { @@ -139,6 +193,7 @@ export const QueryEditor = (props: Props): JSX.Element => { /> {graphTypeSelect} {graphSelect} + {labelField} ); } diff --git a/src/ui/components.tsx b/src/ui/components.tsx index d987482b..6637e404 100644 --- a/src/ui/components.tsx +++ b/src/ui/components.tsx @@ -288,6 +288,67 @@ export const Filter = (props: FilterPr ); }; +interface GenericFieldProps extends CommonProps { + requestSpecKey: Key; + children?: React.ReactNode; + width?: number; + tooltip?: string; + dataTestId?: string; + placeholder?: string; + prefix?: React.ReactNode; + suffix?: React.ReactNode; +} +export const GenericField = (props: GenericFieldProps) => { + const { + label, + width = 32, + onChange, + value, + requestSpecKey, + children = null, + tooltip, + placeholder = 'none', + prefix, + suffix, + } = props; + const { dataTestId = `${requestSpecKey}-filter-input` } = props; + + const [textValue, setTextValue] = React.useState(value !== undefined ? value : ''); + + const debouncedOnChange = React.useMemo( + () => + debounce((newValue) => { + onChange(newValue); + }, 1000), + [onChange] + ); + + const onValueChange = (event: React.ChangeEvent) => { + setTextValue(event.target.value); // update text input + debouncedOnChange(event.target.value); // only the last debounce call comes through + }; + + return ( + + + <> + + {children} + + + + ); +}; + const SingleTag = (props: { index: number; onChange: (newValue: TagValue) => void; diff --git a/src/ui/utils.ts b/src/ui/utils.ts index f8e31fee..e3f54f74 100644 --- a/src/ui/utils.ts +++ b/src/ui/utils.ts @@ -14,6 +14,7 @@ export const labelForRequestSpecKey = (key: keyof RequestSpec, rq: Partial str[0].toUpperCase() + str.slice(1).toLowerCase(); @@ -276,3 +277,25 @@ export function toLiveStatusQuery(filter: Partial, table: 'h query: query, }; } + +type GrapResponse = WebApiCurve | MetricResponse; + +export function updateMetricTitles(metrics: GrapResponse[], query: CmkQuery, scopedVars: ScopedVars = {}) { + const titleTemplate = query.requestSpec?.label || label.ORIGINAL; + if (titleTemplate !== label.ORIGINAL) { + scopedVars = { + ...scopedVars, + [label.SITE.substring(1)]: { value: query.requestSpec?.site || '' }, + [label.HOSTNAME.substring(1)]: { value: query.requestSpec?.host_name || '' }, + [label.HOST_IN_GROUP.substring(1)]: { value: query.requestSpec?.host_in_group?.value || '' }, + [label.SERVICE.substring(1)]: { value: query.requestSpec?.service || '' }, + [label.SERVICE_IN_GROUP.substring(1)]: { value: query.requestSpec?.service_in_group?.value || '' }, + [label.ORIGINAL.substring(1)]: { value: '' }, + }; + + metrics.forEach((metric) => { + scopedVars[label.ORIGINAL.substring(1)] = { value: metric.title }; + metric.title = getTemplateSrv().replace(titleTemplate, scopedVars); + }); + } +} diff --git a/src/webapi.ts b/src/webapi.ts index 32946fba..735d8703 100644 --- a/src/webapi.ts +++ b/src/webapi.ts @@ -2,17 +2,18 @@ import { GraphType, NegatableOption, RequestSpec, TagValue } from './RequestSpec import { Context, Edition, Params } from './types'; import { aggregationToPresentation, createCmkContext, presentationToAggregation } from './utils'; +export interface WebApiCurve { + title: string; + rrddata: Array<{ + i: number; + d: Record; + }>; +} export interface WebAPiGetGraphResult { start_time: number; end_time: number; step: number; - curves: Array<{ - title: string; - rrddata: Array<{ - i: number; - d: Record; - }>; - }>; + curves: WebApiCurve[]; } export interface WebApiResponse { diff --git a/tests/cypress/e2e/spec.cy.ts b/tests/cypress/e2e/spec.cy.ts index ffe88574..1a1d3f84 100644 --- a/tests/cypress/e2e/spec.cy.ts +++ b/tests/cypress/e2e/spec.cy.ts @@ -1,6 +1,7 @@ import '../support/api_commands'; import CheckmkSelectors from '../support/checkmk_selectors'; import '../support/commands'; +import { label } from '../types'; describe('e2e tests', () => { const cmkUser = 'cmkuser'; @@ -21,6 +22,8 @@ describe('e2e tests', () => { const inputServiceId = 'input_Service'; const inputSiteId = 'input_Site'; const inputHostLabelId = CheckmkSelectors.AddDashboard.hostLabelFieldId; + const inputCustomLabelSelector = 'input[data-test-id="custom-label-field"]'; + const refreshQueryButtonSelector = 'button[data-test-id="data-testid RefreshPicker run button"]'; const inputHostRegexDataTestId = 'host_name_regex-filter-input'; const inputServiceRegexDataTestId = 'service_regex-filter-input'; @@ -114,9 +117,8 @@ describe('e2e tests', () => { // Assert all filters are set cy.get(queryEditorSelector).contains('Type to trigger search').should('not.exist'); - cy.get(queryEditorSelector).find('button').eq(0).click(); // Remove filter by hostname - cy.get(queryEditorSelector).find('button').click(); // Remove filter by service + cy.get(queryEditorSelector).find('button').eq(0).click(); // Remove filter by service cy.inputLocatorById(inputFilterId).type('Service{enter}'); // Filter -> 'Service' // Assert the filter is not set @@ -280,6 +282,36 @@ describe('e2e tests', () => { cy.assertHoverSelectorsOff(1); cy.assertHoverSelectorsOn(1); }); + it('Custom labels', {}, () => { + cy.selectDataSource(CmkCEE); + + cy.contains('Checkmk ' + CmkCEE).should('be.visible'); // Assert Cmk CEE datasource is used + + cy.inputLocatorById(inputFilterId).type('Hostname').type('{enter}'); // Filter -> 'Host name' + cy.inputLocatorById(inputHostId).type(hostName0).type('{enter}'); // Hostname -> hostName0 + cy.contains(hostName0).should('exist'); + cy.contains('Predefined graph').should('exist'); + + cy.get(`#${inputGraphId}`).type('time usage by phase').type('{enter}'); // Predefined graph -> 'Time usage by phase' (one entry) + cy.contains('Time usage by phase').should('exist'); + + cy.assertLegendElement(`CPU time in user space`); + + //Label $label + cy.get(inputCustomLabelSelector).clear().type(label.ORIGINAL).type('{enter}'); + cy.refreshGraph(); + cy.assertLegendElement(`CPU time in user space`); + + //Label $label + constant + cy.get(inputCustomLabelSelector).clear().type(`${label.ORIGINAL} - LMP`).type('{enter}'); + cy.refreshGraph(); + cy.assertLegendElement(`CPU time in user space - LMP`); + + //Label $host_name + $label + cy.get(inputCustomLabelSelector).clear().type(`${label.ORIGINAL} - ${label.HOSTNAME}`).type('{enter}'); + cy.refreshGraph(); + cy.assertLegendElement(`CPU time in user space - ${hostName0}`); + }); }); describe('CRE tests', () => { it('time-usage panel by service (single host)', {}, () => { diff --git a/tests/cypress/support/commands.ts b/tests/cypress/support/commands.ts index e60867ba..a212e7b3 100644 --- a/tests/cypress/support/commands.ts +++ b/tests/cypress/support/commands.ts @@ -136,6 +136,14 @@ Cypress.Commands.add('inputLocatorByDataTestId', (dataTestId: string) => { return cy.get(`input[data-test-id="${dataTestId}"]`); }); +Cypress.Commands.add('getById', (id: string) => { + return cy.get(`#${id}`); +}); + +Cypress.Commands.add('refreshGraph', () => { + cy.get('button[data-testid="data-testid RefreshPicker run button"]').click(); +}); + declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { @@ -154,6 +162,8 @@ declare global { inputLocatorById(id: string): Chainable; inputLocatorByDataTestId(dataTestId: string): Chainable; selectDataSource(edition: string): Chainable; + getById(id: string): Chainable; + refreshGraph(): Chainable; } } } diff --git a/tests/cypress/types.ts b/tests/cypress/types.ts new file mode 100644 index 00000000..df511476 --- /dev/null +++ b/tests/cypress/types.ts @@ -0,0 +1 @@ +export * from '../../src/types'; diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index d4c5278a..ce522c8a 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -38,3 +38,5 @@ services: - ./cypress:/cypress - ./cypress.config.js:/cypress.config.js - ./cypress.env.json:/cypress.env.json + - ../src/types.ts:/cypress/types.ts:ro + - ../src/RequestSpec.ts:/cypress/RequestSpec.ts:ro diff --git a/tests/package.json b/tests/package.json index 13777abc..f9208ce2 100644 --- a/tests/package.json +++ b/tests/package.json @@ -9,6 +9,6 @@ "author": "", "license": "GPL-2.0-only", "devDependencies": { - "cypress": "13.3.3" + "cypress": "13.5.1" } } diff --git a/tests/yarn.lock b/tests/yarn.lock index a2424434..7f3f0164 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -316,10 +316,10 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -cypress@13.3.3: - version "13.3.3" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.3.3.tgz#353e69b6543aee8aee4e91fa39bcecbab26ab564" - integrity sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw== +cypress@13.5.1: + version "13.5.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.1.tgz#8b19bf0b9f31ea43f78980b2479bd3f25197d5cc" + integrity sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug== dependencies: "@cypress/request" "^3.0.0" "@cypress/xvfb" "^1.2.4" diff --git a/yarn.lock b/yarn.lock index e21e0aaf..18b61f9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1044,6 +1044,30 @@ tunnel-agent "^0.6.0" uuid "^8.3.2" +"@cypress/request@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" + integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + http-signature "~1.3.6" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "6.10.4" + safe-buffer "^5.1.2" + tough-cookie "^4.1.3" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + "@cypress/webpack-preprocessor@5.17.0": version "5.17.0" resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.17.0.tgz#3f58cf333c5931094780e3ca14c9302a1965025e" @@ -2711,6 +2735,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.6.tgz#0296e9a30b22d2a8fcaa48d3c45afe51474ca55b" integrity sha512-fGmT/P7z7ecA6bv/ia5DlaWCH4YeZvAQMNpUhrJjtAhOhZfoxS1VLUgU2pdk63efSjQaOJWdXMuAJsws+8I6dg== +"@types/node@^18.17.5": + version "18.18.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.13.tgz#ae0f76c0bfe79d8fad0f910b78ae3e59b333c6e8" + integrity sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g== + dependencies: + undici-types "~5.26.4" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -4055,6 +4086,11 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + comment-parser@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" @@ -4264,6 +4300,55 @@ cypress-file-upload@5.0.8: resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== +cypress@13.6.0: + version "13.6.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.6.0.tgz#b98b7b837679012ed09c7ecee5565bf7b31d4982" + integrity sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw== + dependencies: + "@cypress/request" "^3.0.0" + "@cypress/xvfb" "^1.2.4" + "@types/node" "^18.17.5" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.6.0" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^6.2.1" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.4" + enquirer "^2.3.6" + eventemitter2 "6.4.7" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.8" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + process "^0.11.10" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.5.3" + supports-color "^8.1.1" + tmp "~0.2.1" + untildify "^4.0.0" + yauzl "^2.10.0" + cypress@9.5.1: version "9.5.1" resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.1.tgz#51162f3688cedf5ffce311b914ef49a7c1ece076" @@ -5286,6 +5371,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== + eventemitter2@^6.4.3: version "6.4.9" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" @@ -7437,7 +7527,7 @@ minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -8085,6 +8175,13 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== +qs@6.10.4: + version "6.10.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" + integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== + dependencies: + side-channel "^1.0.4" + qs@^6.4.0: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" @@ -9891,6 +9988,11 @@ underscore@1.7.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" integrity sha512-cp0oQQyZhUM1kpJDLdGO1jPZHgS/MpzoWYfe9+CM2h/QGDZlqwT2T3YGukuBdaNJ/CAPoeyAZRRHz8JFo176vA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"