diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 0850da201..5e8cf088f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -4,7 +4,7 @@ */ const { API, INDEX, ADMIN_AUTH } = require("./constants"); -const { NODE_API } = require("../../server/utils/constants") +const { NODE_API } = require("../../server/utils/constants"); // *********************************************** // This example commands.js shows you how to @@ -123,8 +123,8 @@ Cypress.Commands.add("createIndex", (index, policyID = null, settings = {}) => { }); Cypress.Commands.add("deleteSnapshot", (repository, snapshot) => { - cy.request("DELETE", `${Cypress.env("opensearch")}${NODE_API._SNAPSHOTS}/${repository}/${snapshot}`) -}) + cy.request("DELETE", `${Cypress.env("opensearch")}${NODE_API._SNAPSHOTS}/${repository}/${snapshot}`); +}); Cypress.Commands.add("createRollup", (rollupId, rollupJSON) => { cy.request("PUT", `${Cypress.env("opensearch")}${API.ROLLUP_JOBS_BASE}/${rollupId}`, rollupJSON); @@ -134,6 +134,14 @@ Cypress.Commands.add("createIndexTemplate", (name, template) => { cy.request("PUT", `${Cypress.env("opensearch")}${API.INDEX_TEMPLATE_BASE}/${name}`, template); }); +Cypress.Commands.add("deleteTemplate", (name) => { + cy.request({ + url: `${Cypress.env("opensearch")}${API.INDEX_TEMPLATE_BASE}/${name}`, + failOnStatusCode: false, + method: "DELETE", + }); +}); + Cypress.Commands.add("createDataStream", (name) => { cy.request("PUT", `${Cypress.env("opensearch")}${API.DATA_STREAM_BASE}/${name}`); }); @@ -150,6 +158,10 @@ Cypress.Commands.add("createTransform", (transformId, transformJSON) => { cy.request("PUT", `${Cypress.env("opensearch")}${API.TRANSFORM_JOBS_BASE}/${transformId}`, transformJSON); }); +Cypress.Commands.add("createPipeline", (pipelineId, pipelineJSON) => { + cy.request("PUT", `${Cypress.env("opensearch")}/_ingest/pipeline/${pipelineId}`, pipelineJSON); +}); + Cypress.Commands.add("disableJitter", () => { // Sets the jitter to 0 in the ISM plugin cluster settings const jitterJson = { @@ -163,3 +175,39 @@ Cypress.Commands.add("disableJitter", () => { }; cy.request("PUT", `${Cypress.env("opensearch")}/_cluster/settings`, jitterJson); }); + +Cypress.Commands.add("addAlias", (alias, index) => { + cy.request({ + url: `${Cypress.env("opensearch")}/_aliases`, + method: "POST", + body: { + actions: [ + { + add: { + index, + alias, + }, + }, + ], + }, + failOnStatusCode: false, + }); +}); + +Cypress.Commands.add("removeAlias", (alias) => { + cy.request({ + url: `${Cypress.env("opensearch")}/_aliases`, + method: "POST", + body: { + actions: [ + { + remove: { + index: "*", + alias, + }, + }, + ], + }, + failOnStatusCode: false, + }); +}); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index b662c58ae..0054853f6 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -104,5 +104,21 @@ declare namespace Cypress { * cy.disableJitter() */ disableJitter(): Chainable; + + /** + * Delete template + * @example + * cy.deleteTemplate("some_template") + */ + deleteTemplate(name: string); + + /** + * Create a ingest pipeline + * @example + * cy.createPipeline("pipelineId", {"description": "sample description", "processors": []}) + */ + createPipeline(pipelineId: string, pipeline: object); + addAlias(alias: string, index: string); + removeAlias(alias: string); } } diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 315ef2b15..02e22c8ce 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -1,9 +1,9 @@ { "id": "indexManagementDashboards", - "version": "2.4.1", - "opensearchDashboardsVersion": "2.4.1", + "version": "2.5.0", + "opensearchDashboardsVersion": "2.4.2", "configPath": ["opensearch_index_management"], - "requiredPlugins": ["navigation"], + "requiredPlugins": ["navigation", "opensearchDashboardsReact"], "server": true, "ui": true } diff --git a/package.json b/package.json index 2f3e07e5d..69c0dfc37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opensearch_index_management_dashboards", - "version": "2.4.1", + "version": "2.5.0", "description": "Opensearch Dashboards plugin for Index Management", "main": "index.js", "license": "Apache-2.0", @@ -52,14 +52,18 @@ "@elastic/elastic-eslint-config-kibana": "link:../../packages/opensearch-eslint-config-opensearch-dashboards", "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", "@testing-library/dom": "^8.11.3", + "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.1.9", + "@types/diff": "^5.0.2", + "@types/flat": "^5.0.2", "@types/react-dom": "^16.9.8", "@types/react-router-dom": "^5.3.2", "cypress": "^6.0.0", + "diff": "^4.0.1", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-prefer-object-spread": "^1.2.1", "husky": "^3.0.0", - "lint-staged": "^9.2.0", + "lint-staged": "^10.2.0", "ts-loader": "^6.2.1" }, "engines": { diff --git a/public/JobHandler/index.tsx b/public/JobHandler/index.tsx new file mode 100644 index 000000000..2035a8342 --- /dev/null +++ b/public/JobHandler/index.tsx @@ -0,0 +1,274 @@ +import React, { ReactChild } from "react"; +import { EuiLink } from "@elastic/eui"; +import { CoreSetup } from "../../../../src/core/public"; +import { jobSchedulerInstance } from "../context/JobSchedulerContext"; +import { CommonService, IndexService } from "../services"; +import { ReindexJobMetaData, RecoveryJobMetaData } from "../models/interfaces"; +import { ROUTES } from "../utils/constants"; + +const DetailLink = (props: { index: string }) => { + return {props.index}; +}; + +type TaskResult = { + found: boolean; + _source: { + completed: boolean; + response: { + failures: { + cause?: { + reason: string; + }; + }[]; + }; + error?: { + type: string; + reason: string; + }; + }; +}; + +export const EVENT_MAP = { + REINDEX_COMPLETE: "REINDEX_COMPLETE", + SPLIT_COMPLETE: "SPLIT_COMPLETE", + SHRINK_COMPLETE: "SHRINK_COMPLETE", +}; + +const triggerEvent = (eventName: string, data?: unknown) => { + const event = new CustomEvent(eventName, { + detail: data, + }); + window.dispatchEvent(event); +}; + +export const listenEvent = (eventName: string, callback: () => void) => { + window.addEventListener(eventName, callback); +}; + +export const destroyListener = (eventName: string, callback: () => void) => { + window.removeEventListener(eventName, callback); +}; + +export function JobHandlerRegister(core: CoreSetup) { + const indexService = new IndexService(core.http); + const commonService = new CommonService(core.http); + jobSchedulerInstance.addCallback({ + callbackName: "callbackForReindex", + callback: async (job: ReindexJobMetaData) => { + const extras = job.extras; + const tasksResult = await commonService.apiCaller({ + endpoint: "transport.request", + data: { + path: `.tasks/_doc/${extras.taskId}`, + method: "GET", + }, + }); + if (tasksResult.ok) { + const { _source, found } = tasksResult.response; + const { completed, response, error } = (_source || {}) as TaskResult["_source"]; + const { failures } = response; + if (completed && found) { + if (!failures.length && !error?.reason) { + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + triggerEvent(EVENT_MAP.REINDEX_COMPLETE, job); + core.notifications.toasts.addSuccess( + { + title: (( + <> + Source index has been successfully reindexed as{" "} + + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + } else { + let errors: ReactChild[] = []; + if (failures.length) { + errors.push( +
    + {Array.from(new Set(failures.map((item) => item.cause?.reason).filter((item) => item))).map((item) => ( +
  • {item}
  • + ))} +
+ ); + } + + if (error?.reason) { + errors.push( +
    +
  • {error.reason}
  • +
+ ); + } + + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + core.notifications.toasts.addDanger( + { + title: (( + <> + Reindex from to {extras.destIndex} has some errors, please check the errors + below: + + ) as unknown) as string, + text: ((
{errors}
) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + } + return true; + } + } + + return false; + }, + timeoutCallback(job: ReindexJobMetaData) { + const extras = job.extras; + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + core.notifications.toasts.addDanger( + { + title: (( + <> + Reindex from to {extras.destIndex} does not finish in reasonable time, please check + the task {extras.taskId} manually + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + }, + listenType: "reindex", + }); + jobSchedulerInstance.addCallback({ + callbackName: "callbackForSplit", + callback: async (job: RecoveryJobMetaData) => { + const extras = job.extras; + const indexResult = await indexService.getIndices({ + from: 0, + size: 10, + search: extras.destIndex, + terms: extras.destIndex, + sortField: "index", + sortDirection: "desc", + showDataStreams: false, + }); + if (indexResult.ok) { + const [firstItem] = indexResult.response.indices || []; + if (firstItem && firstItem.health !== "red") { + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + triggerEvent(EVENT_MAP.SPLIT_COMPLETE, job); + core.notifications.toasts.addSuccess( + { + title: (( + <> + Source index has been successfully split as{" "} + . + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + return true; + } + } + + return false; + }, + timeoutCallback(job: RecoveryJobMetaData) { + const extras = job.extras; + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + core.notifications.toasts.addDanger( + { + title: (( + <> + Split to {extras.destIndex} does not finish in reasonable time, please check the + index manually + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + }, + listenType: "split", + }); + jobSchedulerInstance.addCallback({ + callbackName: "callbackForShrink", + callback: async (job: RecoveryJobMetaData) => { + const extras = job.extras; + const indexResult = await indexService.getIndices({ + from: 0, + size: 10, + search: extras.destIndex, + terms: extras.destIndex, + sortField: "index", + sortDirection: "desc", + showDataStreams: false, + }); + if (indexResult.ok) { + const [firstItem] = indexResult.response.indices || []; + if (firstItem && firstItem.health !== "red") { + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + triggerEvent(EVENT_MAP.SHRINK_COMPLETE, job); + core.notifications.toasts.addSuccess( + { + title: (( + <> + Source index has been successfully shrunken as{" "} + . + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + return true; + } + } + + return false; + }, + timeoutCallback(job: RecoveryJobMetaData) { + const extras = job.extras; + if (extras.toastId) { + core.notifications.toasts.remove(extras.toastId); + } + core.notifications.toasts.addDanger( + { + title: (( + <> + Shrink to {extras.destIndex} does not finish in reasonable time, please check the + index manually. + + ) as unknown) as string, + }, + { + toastLifeTimeMs: 1000 * 60 * 60 * 24 * 5, + } + ); + }, + listenType: "shrink", + }); +} diff --git a/public/components/AdvancedSettings/AdvancedSettings.test.tsx b/public/components/AdvancedSettings/AdvancedSettings.test.tsx new file mode 100644 index 000000000..037111627 --- /dev/null +++ b/public/components/AdvancedSettings/AdvancedSettings.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from "react"; +import { fireEvent, render } from "@testing-library/react"; +import AdvancedSettings from "./index"; +import userEvent from "@testing-library/user-event"; + +describe(" spec", () => { + it("render the component", () => { + render(); + expect(document.body.children).toMatchSnapshot(); + }); + + it("do some actions with render props", async () => { + const onChangeMock = jest.fn(); + render( + ( + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
+ +
+
+
+
+ , +] +`; diff --git a/public/components/SwitchableEditor/index.tsx b/public/components/SwitchableEditor/index.tsx new file mode 100644 index 000000000..8b0e14658 --- /dev/null +++ b/public/components/SwitchableEditor/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import SwitchableEditor from "./SwitchableEditor"; + +export default SwitchableEditor; +export * from "./SwitchableEditor"; diff --git a/public/components/Toast/Toast.test.tsx b/public/components/Toast/Toast.test.tsx new file mode 100644 index 000000000..543fe391f --- /dev/null +++ b/public/components/Toast/Toast.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { waitFor } from "@testing-library/react"; +import { act } from "react-dom/test-utils"; +import { SimpleEuiToast } from "./index"; + +describe("SimpleEuiToast show", () => { + it("render the component", async () => { + await act(async () => { + SimpleEuiToast.addSuccess("Success information"); + }); + expect(document.body).toMatchSnapshot(); + expect(document.querySelector('[data-test-subj="toast_Success information"]')).not.toBeNull(); + await act(async () => { + SimpleEuiToast.addDanger("Error information"); + }); + expect(document.querySelector('[data-test-subj="toast_Error information"]')).not.toBeNull(); + await act(async () => { + SimpleEuiToast.show({ + toastLifeTimeMs: 10, + title: "Test quick destroy", + }); + }); + await waitFor(() => { + expect(document.querySelector('[data-test-subj="toast_Test quick destroy"]')).toBeNull(); + }); + }); +}); diff --git a/public/components/Toast/__snapshots__/Toast.test.tsx.snap b/public/components/Toast/__snapshots__/Toast.test.tsx.snap new file mode 100644 index 000000000..9678911ce --- /dev/null +++ b/public/components/Toast/__snapshots__/Toast.test.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SimpleEuiToast show render the component 1`] = ` + +
+
+
+

+ A new notification appears +

+
+ + Success information + +
+ +
+
+
+ +`; diff --git a/public/components/Toast/index.tsx b/public/components/Toast/index.tsx new file mode 100644 index 000000000..fc0679f6e --- /dev/null +++ b/public/components/Toast/index.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from "react"; +import { render } from "react-dom"; +import { EuiGlobalToastList, EuiGlobalToastListProps } from "@elastic/eui"; + +export type SimpleEuiToastProps = EuiGlobalToastListProps["toasts"][number]; + +const TOAST_MOUNT_ID = "EUI_SIMPLE_TOAST_MOUNT_ID"; + +let addToastHandler: (params: SimpleEuiToastProps) => void; +let removeAllToastsHandler: () => void; +let id = 0; + +const SimpleToast = () => { + const [toasts, setToasts] = useState([]); + + addToastHandler = (toast) => { + setToasts(toasts.concat(toast)); + }; + + const removeToast: (params: SimpleEuiToastProps & { id: string }) => void = (removedToast) => { + setToasts(toasts.filter((toast) => toast.id !== removedToast.id)); + }; + + removeAllToastsHandler = () => { + setToasts([]); + }; + + useEffect(() => { + return () => { + removeAllToastsHandler(); + }; + }, []); + + return ; +}; + +export const SimpleEuiToast = { + show: (props: Partial & { title: SimpleEuiToastProps["title"] }) => { + let dom; + if (!document.getElementById(TOAST_MOUNT_ID)) { + dom = document.createElement("div"); + dom.id = TOAST_MOUNT_ID; + dom.setAttribute("data-role", "SimpleEuiToast"); + document.body.appendChild(dom); + render(, dom); + } else { + dom = document.getElementById(TOAST_MOUNT_ID); + } + addToastHandler({ + ...props, + "data-test-subj": `toast_${props.title}`, + id: `toast_${id++}`, + }); + }, + addSuccess: (message: SimpleEuiToastProps["text"]) => + SimpleEuiToast.show({ + title: message, + color: "success", + }), + addDanger: (message: SimpleEuiToastProps["text"]) => + SimpleEuiToast.show({ + title: message, + color: "danger", + }), +}; diff --git a/public/containers/ErrorNotification/ErrorNotification.test.tsx b/public/containers/ErrorNotification/ErrorNotification.test.tsx new file mode 100644 index 000000000..8d8ae4b0a --- /dev/null +++ b/public/containers/ErrorNotification/ErrorNotification.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from "react"; +import "@testing-library/jest-dom/extend-expect"; +import { render } from "@testing-library/react"; +import ErrorNotificationContainer, { ErrorNotificationProps } from "./ErrorNotification"; +import { ServicesContext } from "../../services"; +import { browserServicesMock, coreServicesMock } from "../../../test/mocks"; +import { ErrorNotification as IErrorNotification } from "../../../models/interfaces"; +import { CoreServicesContext } from "../../components/core_services"; + +const ErrorNotification = (props: Pick) => { + const [value, onChange] = useState(props.value); + return ( + + + + ); +}; + +function renderErrorNotification(errorNotification: IErrorNotification) { + return { + ...render( + + + + ), + }; +} + +describe(" spec", () => { + it("renders the component", () => { + const { container } = render( + + ); + expect(container.firstChild).toMatchSnapshot(); + }); + + it("renders the channel ui editor for channels", () => { + const errorNotification = { channel: { id: "some_id" }, message_template: { source: "some source message" } }; + const { queryByTestId, queryByText } = renderErrorNotification(errorNotification); + + expect(queryByTestId("channel-notification-refresh")).not.toBeNull(); + expect(queryByText("Switch to using Channel ID")).toBeNull(); + }); + + it("renders the json legacy editor for destinations", () => { + const errorNotification = { destination: { slack: { url: "https://slack.com" } }, message_template: { source: "some source message" } }; + const { queryByTestId, queryByText } = renderErrorNotification(errorNotification); + + expect(queryByTestId("channel-notification-refresh")).toBeNull(); + expect(queryByText("Switch to using Channel ID")).not.toBeNull(); + }); +}); diff --git a/public/containers/ErrorNotification/ErrorNotification.tsx b/public/containers/ErrorNotification/ErrorNotification.tsx new file mode 100644 index 000000000..a02b66475 --- /dev/null +++ b/public/containers/ErrorNotification/ErrorNotification.tsx @@ -0,0 +1,161 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ChangeEvent, Component, useContext } from "react"; +import { EuiLink, EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from "@elastic/eui"; +import { ContentPanel } from "../../components/ContentPanel"; +import "brace/theme/github"; +import "brace/mode/json"; +import { FeatureChannelList } from "../../../server/models/interfaces"; +import { BrowserServices } from "../../models/interfaces"; +import { ErrorNotification as IErrorNotification } from "../../../models/interfaces"; +import { ServicesContext } from "../../services"; +import { getErrorMessage } from "../../utils/helpers"; +import { CoreServicesContext } from "../../components/core_services"; +import ChannelNotification from "../../components/ChannelNotification"; +import LegacyNotification from "../../components/LegacyNotification"; +import { ERROR_NOTIFICATION_DOCUMENTATION_URL } from "../../utils/constants"; + +export interface ErrorNotificationProps { + value?: IErrorNotification; + onChange: (val: Required["value"]) => void; + onChangeChannelId?: (value: string) => void; + onChangeMessage?: (value: string) => void; + browserServices: BrowserServices; +} + +interface ErrorNotificationState { + channels: FeatureChannelList[]; + loadingChannels: boolean; +} + +class ErrorNotification extends Component { + static contextType = CoreServicesContext; + constructor(props: ErrorNotificationProps) { + super(props); + + this.state = { + channels: [], + loadingChannels: true, + }; + } + + componentDidMount = async (): Promise => { + await this.getChannels(); + }; + + getChannels = async (): Promise => { + this.setState({ loadingChannels: true }); + try { + const { notificationService } = this.props.browserServices; + const response = await notificationService.getChannels(); + if (response.ok) { + this.setState({ channels: response.response.channel_list }); + } else { + this.context.notifications.toasts.addDanger(`Could not load notification channels: ${response.error}`); + } + } catch (err) { + this.context.notifications.toasts.addDanger(getErrorMessage(err, "Could not load the notification channels")); + } + this.setState({ loadingChannels: false }); + }; + + onChangeChannelId = (e: ChangeEvent) => { + const { onChange, value, onChangeChannelId } = this.props; + const id = e.target.value; + onChangeChannelId && onChangeChannelId(id); + onChange({ + ...value, + channel: { + id, + }, + }); + }; + + onChangeMessage = (e: ChangeEvent) => { + const { onChange, value, onChangeMessage } = this.props; + const message = e.target.value; + onChangeMessage && onChangeMessage(message); + onChange({ + ...value, + message_template: { + source: message, + }, + }); + }; + + onSwitchToChannels = () => { + const { onChange } = this.props; + onChange({ + channel: { + id: "", + }, + message_template: { + source: "", + }, + }); + }; + + render() { + const { value: errorNotification, onChange } = this.props; + const { channels, loadingChannels } = this.state; + const hasDestination = !!errorNotification?.destination; + + let content = ( + + ); + + // If we have a destination in the error notification then it's either an older policy or they created through the API + if (hasDestination) { + content = ; + } + + return ( + + + +

Error notification

+
+
+ + + - optional + + + + } + titleSize="s" + subTitleText={ + +

+ You can set up an error notification for when a policy execution fails.{" "} + + Learn more + +

+
+ } + > +
{content}
+
+ ); + } +} + +export default function ErrorNotificationContainer(props: Omit) { + const browserServices = useContext(ServicesContext) as BrowserServices; + return ; +} diff --git a/public/containers/ErrorNotification/__snapshots__/ErrorNotification.test.tsx.snap b/public/containers/ErrorNotification/__snapshots__/ErrorNotification.test.tsx.snap new file mode 100644 index 000000000..f24ff5faa --- /dev/null +++ b/public/containers/ErrorNotification/__snapshots__/ErrorNotification.test.tsx.snap @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` spec renders the component 1`] = ` +
+
+
+
+
+
+

+ Error notification +

+
+
+
+
+
+ + - optional + +
+
+
+
+
+
+

+ You can set up an error notification for when a policy execution fails. + + + Learn more + EuiIconMock + EuiIconMock + + (opens in a new tab or window) + + +

+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+ + EuiIconMock + +
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ Embed variables in your message using Mustache template. +
+
+
+
+
+
+`; diff --git a/public/containers/ErrorNotification/index.ts b/public/containers/ErrorNotification/index.ts new file mode 100644 index 000000000..5e1409536 --- /dev/null +++ b/public/containers/ErrorNotification/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import ErrorNotification from "./ErrorNotification"; + +export default ErrorNotification; diff --git a/public/containers/IndexDetail/IndexDetail.test.tsx b/public/containers/IndexDetail/IndexDetail.test.tsx new file mode 100644 index 000000000..bc87da9a7 --- /dev/null +++ b/public/containers/IndexDetail/IndexDetail.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from "react"; +import { render, waitFor } from "@testing-library/react"; +import IndexDetail, { IIndexDetailProps } from "./index"; +import { browserServicesMock, coreServicesMock } from "../../../test/mocks"; +import { ServicesContext } from "../../services"; +import { CoreServicesContext } from "../../components/core_services"; +import { CatIndex } from "../../../server/models/interfaces"; + +browserServicesMock.commonService.apiCaller = jest.fn( + async (payload): Promise => { + if (payload.data?.index?.includes("error_index")) { + return { + ok: false, + error: "error index", + }; + } + + return { + ok: true, + response: (payload.data.index || []).map( + (index: string): CatIndex => { + return { + index, + "docs.count": "0", + "docs.deleted": "1", + "pri.store.size": "1", + data_stream: "no", + "store.size": "1mb", + rep: "2", + uuid: "1", + health: "green", + pri: "4", + status: "open", + }; + } + ), + }; + } +); + +function renderWithServiceAndCore(props: IIndexDetailProps) { + return { + ...render( + + + + + + ), + }; +} + +describe(" spec", () => { + it("render the component", async () => { + const { container, queryByText } = renderWithServiceAndCore({ + indices: ["test"], + children: <>content underneath the table, + }); + + expect(queryByText("children content here")).toBeNull(); + await waitFor(() => { + expect(container).toMatchSnapshot(); + expect(browserServicesMock.commonService.apiCaller).toBeCalledTimes(1); + expect(browserServicesMock.commonService.apiCaller).toBeCalledWith({ + endpoint: "cat.indices", + data: { + index: ["test"], + format: "json", + }, + }); + }); + expect(queryByText("content underneath the table")).not.toBeNull(); + }); + + it("render with error", async () => { + const onGetIndicesDetailMock = jest.fn(); + renderWithServiceAndCore({ + indices: ["error_index"], + children: <>content underneath the table, + onGetIndicesDetail: onGetIndicesDetailMock, + }); + + await waitFor(() => { + expect(coreServicesMock.notifications.toasts.addDanger).toBeCalledTimes(1); + expect(coreServicesMock.notifications.toasts.addDanger).toBeCalledWith("error index"); + expect(onGetIndicesDetailMock).toBeCalledTimes(1); + expect(onGetIndicesDetailMock).toBeCalledWith([]); + }); + }); +}); diff --git a/public/containers/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap b/public/containers/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap new file mode 100644 index 000000000..a38a36806 --- /dev/null +++ b/public/containers/IndexDetail/__snapshots__/IndexDetail.test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` spec render the component 1`] = ` +
+
+
+
+

+ Source index details +

+
+
+
+
+
+
+
+
+
+`; diff --git a/public/containers/IndexDetail/index.tsx b/public/containers/IndexDetail/index.tsx new file mode 100644 index 000000000..b353d8573 --- /dev/null +++ b/public/containers/IndexDetail/index.tsx @@ -0,0 +1,70 @@ +import { EuiSpacer } from "@elastic/eui"; +import React, { useContext, useEffect, useState } from "react"; +import { CatIndex } from "../../../server/models/interfaces"; +import { ContentPanel } from "../../components/ContentPanel"; +import { CoreServicesContext } from "../../components/core_services"; +import { ServicesContext } from "../../services"; +import DescriptionListHoz from "../../components/DescriptionListHoz"; + +export interface IIndexDetailProps { + indices: string[]; + onGetIndicesDetail?: (args: CatIndex[]) => void; + children?: React.ReactChild; +} + +export default function IndexDetail(props: IIndexDetailProps) { + const [loading, setLoading] = useState(false); + const [items, setItems] = useState([] as CatIndex[]); + const services = useContext(ServicesContext); + const coreServices = useContext(CoreServicesContext); + useEffect(() => { + (async () => { + setLoading(true); + const result = await services?.commonService.apiCaller({ + endpoint: "cat.indices", + data: { + index: props.indices, + format: "json", + }, + }); + let finalResponse: CatIndex[] = []; + if (result?.ok) { + finalResponse = result.response; + } else { + coreServices?.notifications.toasts.addDanger(result?.error || ""); + } + setItems(finalResponse); + props.onGetIndicesDetail && props.onGetIndicesDetail(finalResponse); + setLoading(false); + })(); + }, [props.indices.join(","), setLoading, setItems, coreServices]); + return ( + + + {items && items.length ? ( + + ) : null} + + {loading ? null : props.children} + + ); +} diff --git a/public/context/JobSchedulerContext.tsx b/public/context/JobSchedulerContext.tsx new file mode 100644 index 000000000..73353dc78 --- /dev/null +++ b/public/context/JobSchedulerContext.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { JobScheduler } from "../lib/JobScheduler"; + +const jobSchedulerInstance = new JobScheduler({ + callbacks: [], +}); + +jobSchedulerInstance.init(); + +export { jobSchedulerInstance }; + +export const JobSchedulerContext = React.createContext(jobSchedulerInstance); diff --git a/public/index_management_app.tsx b/public/index_management_app.tsx index a7f684c43..f668540d2 100644 --- a/public/index_management_app.tsx +++ b/public/index_management_app.tsx @@ -16,6 +16,7 @@ import { NotificationService, ServicesContext, SnapshotManagementService, + CommonService, } from "./services"; import { DarkModeContext } from "./components/DarkMode"; import Main from "./pages/Main"; @@ -32,6 +33,7 @@ export function renderApp(coreStart: CoreStart, params: AppMountParameters, land const transformService = new TransformService(http); const notificationService = new NotificationService(http); const snapshotManagementService = new SnapshotManagementService(http); + const commonService = new CommonService(http); const services = { indexService, managedIndexService, @@ -40,6 +42,7 @@ export function renderApp(coreStart: CoreStart, params: AppMountParameters, land transformService, notificationService, snapshotManagementService, + commonService, }; const isDarkMode = coreStart.uiSettings.get("theme:darkMode") || false; diff --git a/public/lib/JobScheduler/JobScheduler.test.ts b/public/lib/JobScheduler/JobScheduler.test.ts new file mode 100644 index 000000000..7f76efcdd --- /dev/null +++ b/public/lib/JobScheduler/JobScheduler.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { waitFor } from "@testing-library/dom"; +import { JobScheduler } from "./JobScheduler"; + +describe("JobScheduler spec", () => { + it("basic usage", async () => { + const callback = jest.fn(async () => { + return false; + }); + const timeoutCallback = jest.fn(); + // setup job scheduler + const jobScheduler = new JobScheduler({ + callbacks: [ + { + callback, + callbackName: "test", + timeoutCallback, + }, + ], + }); + jobScheduler.init(); + + // add a job + const addedJob = await jobScheduler.addJob({ + interval: 1000, + timeout: 2500, + extras: {}, + type: "reindex", + }); + await jobScheduler.addJob(addedJob); + // if the same job was added, ignore that. + expect(jobScheduler.getAllJobs()).resolves.toHaveLength(1); + + // excute every second + await waitFor( + () => + new Promise(async (resolve, reject) => { + const result = await jobScheduler.getAllJobs(); + try { + expect(result).toHaveLength(0); + resolve(true); + } catch (e) { + reject(e); + } + }), + { + timeout: 10000, + } + ); + expect(callback).toBeCalledTimes(3); + + // setup a long timeout job + const testJob = await jobScheduler.addJob({ + interval: 1000, + type: "reindex", + extras: {}, + id: "test", + }); + expect(testJob.id).toEqual("test"); + await jobScheduler.changeJob(testJob.id, { + timeout: 2000, + }); + await new Promise((resolve) => setTimeout(resolve, 3000)); + // wait for 3s, and the job should be gone + expect(jobScheduler.getAllJobs()).resolves.toHaveLength(0); + + // add a callback + jobScheduler.addCallback({ + callbackName: "test1", + callback: async () => false, + timeoutCallback, + }); + + // delete the callback + jobScheduler.deleteCallback("test1"); + expect(jobScheduler.getAllCallbacks()).toHaveLength(1); + + // add a job + const testDeleteJob = await jobScheduler.addJob({ + interval: 1000, + type: "reindex", + extras: {}, + id: "testDeleteJob", + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + jobScheduler.deleteJob(testDeleteJob.id); + }, 30000); + + it("jobs when resume", async () => { + const callback = jest.fn(() => Promise.reject(false)); + const timeoutCallback = jest.fn(); + // setup job scheduler + const jobScheduler = new JobScheduler({ + callbacks: [ + { + callback, + callbackName: "test", + timeoutCallback, + }, + ], + }); + jobScheduler.addJob({ + createTime: Date.now() - 20 * 1000, + timeout: 2000, + interval: 1000, + type: "reindex", + extras: {}, + }); + jobScheduler.init(); + await new Promise((resolve) => setTimeout(resolve, 3000)); + expect(jobScheduler.getAllJobs()).resolves.toHaveLength(0); + expect(callback).toBeCalledTimes(1); + expect(timeoutCallback).toBeCalledTimes(1); + const result = await jobScheduler.changeJob("1", {}); + expect(result).toBe(false); + }); +}); diff --git a/public/lib/JobScheduler/JobScheduler.ts b/public/lib/JobScheduler/JobScheduler.ts new file mode 100644 index 000000000..945a7a705 --- /dev/null +++ b/public/lib/JobScheduler/JobScheduler.ts @@ -0,0 +1,157 @@ +import { IJobSchedulerOptions, IJobItemMetadata, IStorage, JobItemMetadata, TimeoutId } from "./interface"; +import { StoreLocalStorage } from "./store-localstorage"; + +export class JobScheduler { + private options: IJobSchedulerOptions; + // key: jobId value: timerid + private runningJobMap: Record = {}; + private storage: IStorage; + constructor(options: IJobSchedulerOptions) { + this.options = options; + this.storage = options.storage || new StoreLocalStorage(); + } + async init(): Promise { + this.loopJob(); + return true; + } + private getId() { + return `${Date.now()}_${Math.floor(Math.random() * 10)}`; + } + private formatJob(job: IJobItemMetadata): JobItemMetadata { + const formattedJob = { ...job }; + if (!formattedJob.id) { + formattedJob.id = this.getId(); + } + + if (!formattedJob.createTime) { + formattedJob.createTime = Date.now(); + } + + if (!formattedJob.timeout) { + formattedJob.timeout = 1000 * 60 * 60; + } + + return formattedJob as JobItemMetadata; + } + private isStaledJob(job: JobItemMetadata) { + if (!job.latestRunTime) { + // haven't run once, return false + return false; + } + + return job.timeout + job.createTime < job.latestRunTime; + } + private async loopJob() { + const jobs = await this.storage.getAll(); + // loop all the jobs to see if any job do not exist in runningJobMap + jobs.forEach(async (job) => { + // if a job is staled, remove that + if (this.isStaledJob(job)) { + await this.runStaledJob(job.id); + await this.deleteJob(job.id); + return; + } + + if (!this.runningJobMap[job.id]) { + const timeoutCallback = setTimeout(async () => { + if (!this.isStaledJob(job)) { + this.runJob(job.id); + } else { + await this.runStaledJob(job.id); + await this.deleteJob(job.id); + } + }, job.interval); + this.runningJobMap[job.id] = timeoutCallback; + } + }); + } + private async runStaledJob(jobId: JobItemMetadata["id"]): Promise { + const job = await this.getJob(jobId); + if (!job) { + return undefined; + } + const filteredCallbacks = this.options.callbacks.filter( + (callbackItem) => callbackItem.listenType === job.type || !callbackItem.listenType + ); + await Promise.all( + filteredCallbacks.map(async (callbackItem) => { + try { + return callbackItem.timeoutCallback(job); + } catch (e) { + return false; + } + }) + ); + } + private async runJob(jobId: JobItemMetadata["id"]): Promise { + const job = await this.getJob(jobId); + if (!job) { + return undefined; + } + const filteredCallbacks = this.options.callbacks.filter( + (callbackItem) => callbackItem.listenType === job.type || !callbackItem.listenType + ); + job.latestRunTime = Date.now(); + const result = await Promise.all( + filteredCallbacks.map(async (callbackItem) => { + try { + return await callbackItem.callback(job); + } catch (e) { + return false; + } + }) + ); + const hasFinish = result.some((res) => res === true); + await this.deleteJob(job.id); + if (!hasFinish) { + await this.addJob(job); + } + } + addCallback(callback: IJobSchedulerOptions["callbacks"][number]) { + this.options.callbacks.push(callback); + } + deleteCallback(callbackName: string) { + const findIndex = this.options.callbacks.findIndex((item) => item.callbackName === callbackName); + if (findIndex > -1) { + this.options.callbacks.splice(findIndex, 1); + } + } + getAllCallbacks() { + return this.options.callbacks; + } + async addJob(job: IJobItemMetadata): Promise { + const formattedJob = this.formatJob(job); + if (this.runningJobMap[formattedJob.id]) { + return formattedJob; + } + + await this.storage.set(formattedJob.id, formattedJob); + this.loopJob(); + return formattedJob; + } + async deleteJob(jobId: JobItemMetadata["id"]): Promise { + clearTimeout(this.runningJobMap[jobId]); + delete this.runningJobMap[jobId]; + const storageResult = await this.storage.delete(jobId); + return storageResult || true; + } + getJob(jobId: JobItemMetadata["id"]): Promise { + return this.storage.get(jobId); + } + getAllJobs(): Promise { + return this.storage.getAll(); + } + async changeJob(jobId: JobItemMetadata["id"], jobMeta: Partial>): Promise { + const nowJob = await this.getJob(jobId); + + if (!nowJob) { + return false; + } + + return this.storage.set(jobId, { + ...nowJob, + ...jobMeta, + id: jobId, + }); + } +} diff --git a/public/lib/JobScheduler/index.ts b/public/lib/JobScheduler/index.ts new file mode 100644 index 000000000..86f934b50 --- /dev/null +++ b/public/lib/JobScheduler/index.ts @@ -0,0 +1,2 @@ +export * from "./interface"; +export { JobScheduler } from "./JobScheduler"; diff --git a/public/lib/JobScheduler/interface.ts b/public/lib/JobScheduler/interface.ts new file mode 100644 index 000000000..9e907fee2 --- /dev/null +++ b/public/lib/JobScheduler/interface.ts @@ -0,0 +1,33 @@ +export interface IJobItemMetadata { + interval: number; + extras: any; // extra fields to store job-related info + type: "reindex" | "split" | "shrink"; // enum for job type + id?: string; // a number to indicate the job + createTime?: number; // the time when this job is created + latestRunTime?: number; // the time when the job latest run, will be used to check if the job is staled + // the timeout for job to do, once the time goes beyond the timeout + // a timeout error toast will show. + timeout?: number; +} + +export type JobItemMetadata = IJobItemMetadata & Required>; + +export interface IJobSchedulerOptions { + callbacks: { + listenType?: IJobItemMetadata["type"]; + callback: (params: IJobItemMetadata) => Promise; + timeoutCallback: (params: IJobItemMetadata) => void; + callbackName: string; + }[]; + storage?: IStorage; +} + +export interface IStorage { + setup(): Promise; + getAll(): Promise; + set(key: string, value: JobItemMetadata): Promise; + get(key: string): Promise; + delete(key: string): Promise; +} + +export type TimeoutId = ReturnType; diff --git a/public/lib/JobScheduler/store-localstorage.ts b/public/lib/JobScheduler/store-localstorage.ts new file mode 100644 index 000000000..cdb69a4b7 --- /dev/null +++ b/public/lib/JobScheduler/store-localstorage.ts @@ -0,0 +1,44 @@ +import { IStorage, JobItemMetadata } from "./interface"; + +const JOB_STORAGE_KEY = "ISM_JOBS"; + +export class StoreLocalStorage implements IStorage { + async setup(): Promise { + // do nothing + return true; + } + async getAll(): Promise { + return JSON.parse(localStorage.getItem(JOB_STORAGE_KEY) || "[]"); + } + async set(key: string, value: JobItemMetadata): Promise { + try { + const result = await this.getAll(); + const findIndex = result.findIndex((item) => item.id === key); + if (findIndex > -1) { + result[findIndex] = value; + } else { + result.push(value); + } + this.saveToDisk(result); + return true; + } catch (e) { + return false; + } + } + async get(key: string): Promise { + const all = await this.getAll(); + return all.find((item) => item.id === key); + } + async delete(key: string): Promise { + const result = await this.getAll(); + return this.saveToDisk(result.filter((item) => item.id !== key)); + } + private saveToDisk(payload: JobItemMetadata[]) { + try { + localStorage.setItem(JOB_STORAGE_KEY, JSON.stringify(payload)); + return true; + } catch (e) { + return false; + } + } +} diff --git a/public/lib/field/index.tsx b/public/lib/field/index.tsx new file mode 100644 index 000000000..750fe4eeb --- /dev/null +++ b/public/lib/field/index.tsx @@ -0,0 +1,174 @@ +import React, { useEffect, useRef, useState } from "react"; +import { set, get, unset } from "lodash"; +import { Rule, FieldOption, FieldInstance, InitOption, InitResult, ValidateFunction, FieldName } from "./interfaces"; +import buildInRules from "./rules"; + +export function transformNameToString(name: FieldName) { + if (Array.isArray(name)) { + return name.join("."); + } else { + return name; + } +} + +export default function useField(options?: FieldOption): FieldInstance { + const [, setValuesState] = useState((options?.values || {}) as Record); + const [, setErrorsState] = useState({} as Record); + const destroyRef = useRef(false); + const values = useRef>(options?.values || {}); + const errors = useRef>({}); + const fieldsMapRef = useRef>({}); + const setValues = (obj: Record) => { + if (destroyRef.current) { + return; + } + values.current = { + ...values.current, + ...obj, + }; + setValuesState(values.current); + }; + const resetValues = (obj: Record) => { + if (destroyRef.current) { + return; + } + values.current = obj; + setValuesState(values.current); + }; + const setValue: FieldInstance["setValue"] = (name: FieldName, value) => { + const payload = { ...values.current }; + if (!Array.isArray(name)) { + name = [name]; + } + set(payload, name, value); + setValues(payload); + }; + const setErrors: FieldInstance["setErrors"] = (errs) => { + if (destroyRef.current) { + return; + } + errors.current = errs; + setErrorsState(errors.current); + }; + const setError: FieldInstance["setError"] = (name, error) => { + setErrors({ + ...errors.current, + [transformNameToString(name)]: error, + }); + }; + const validateField = async (name: FieldName) => { + const fieldOptions = fieldsMapRef.current[transformNameToString(name)]; + const rules: Rule[] = fieldOptions.rules || []; + const result = await Promise.all( + rules.map(async (item) => { + let validateFunction: ValidateFunction = () => undefined; + if (item.validator) { + validateFunction = item.validator; + } else if (item.required) { + validateFunction = buildInRules.required; + } else if (item.format) { + validateFunction = buildInRules.format; + } else if (typeof item.min === "number" || typeof item.max === "number") { + validateFunction = buildInRules.size; + } else if (item.pattern) { + validateFunction = buildInRules.pattern; + } + + let errorInfo = null; + try { + const result = validateFunction( + { + ...item, + field: transformNameToString(name), + }, + get(values.current, name) + ); + if (result && (result as Promise).then) { + await result; + } else { + errorInfo = result; + } + } catch (e) { + errorInfo = e || item.message; + } + + return errorInfo; + }) + ); + const fieldErrors = result.filter((item) => item) as string[]; + + return fieldErrors; + }; + useEffect(() => { + return () => { + destroyRef.current = true; + }; + }, []); + const refCallbacks = useRef>>({}); + return { + registerField: (initOptions: InitOption): InitResult => { + const fieldName = transformNameToString(initOptions.name); + fieldsMapRef.current[fieldName] = initOptions; + const payload: InitResult = { + ...initOptions.props, + value: get(values.current, initOptions.name), + onChange: async (val) => { + setValue(initOptions.name, val); + options?.onChange && options?.onChange(initOptions.name, val); + const validateErros = await validateField(initOptions.name); + setError(initOptions.name, validateErros.length ? validateErros : null); + }, + }; + if (options?.unmountComponent) { + if (!refCallbacks.current[fieldName]) { + refCallbacks.current[fieldName] = (ref: any) => { + if (!ref) { + delete fieldsMapRef.current[fieldName]; + delete refCallbacks.current[fieldName]; + } + }; + } + payload.ref = refCallbacks.current[fieldName] as React.RefCallback; + } + return payload; + }, + setValue, + setValues, + getValue: (name) => get(values.current, name), + getValues: () => values.current, + getError: (name) => errors.current[transformNameToString(name)], + getErrors: () => errors.current, + validatePromise: async () => { + const result = await Promise.all( + Object.values(fieldsMapRef.current).map(({ name }) => { + return validateField(name).then((res) => { + if (res.length) { + return { + [transformNameToString(name)]: res, + }; + } + + return null; + }); + }) + ); + const resultArray = result.filter((item) => item) as Record[]; + const resultPayload = resultArray.reduce((total, current) => ({ ...total, ...current }), {} as Record); + setErrors(resultPayload); + return { + errors: resultArray.length ? resultPayload : null, + values: values.current, + }; + }, + setError, + setErrors, + resetValues, + deleteValue: (key) => { + const newValues = { ...values.current }; + unset(newValues, key); + resetValues(newValues); + }, + }; +} + +export * from "./interfaces"; diff --git a/public/lib/field/interfaces.ts b/public/lib/field/interfaces.ts new file mode 100644 index 000000000..d51b964e4 --- /dev/null +++ b/public/lib/field/interfaces.ts @@ -0,0 +1,181 @@ +import React from "react"; + +// if it's a string[], the value will become nested. +// registerField({ name: ['a', 'b', 'c.d'] }) => { a: { b: { c,d: '' } } } +export type FieldName = string | string[]; + +export type FieldOption = { + /** + * All component changes will arrive here [set value will not trigger this function] + */ + onChange?: (name: FieldName, value?: any) => void; + + /** + * Initialization data + */ + values?: {}; + + unmountComponent?: boolean; +}; + +export type ValidateResults = { + errors: Record | null; + values: any; +}; + +export type InitResult = { + value?: T; + onChange(value: T): void; + ref?: React.RefCallback; +}; + +export type Rule = { + /** + * cannot be empty (cannot be used with pattern) + * @default true + */ + required?: boolean; + + /** + * error message + */ + message?: string; + + /** + * Check Regular Expression + */ + pattern?: RegExp; + /** + * Minimum string length /minimum number of arrays + */ + minLength?: number; + /** + * Maximum string length /maximum number of arrays + */ + maxLength?: number; + + /** + * String exact length /array exact number + */ + length?: number; + + /** + * minimum + */ + min?: number; + + /** + * maximum value + */ + max?: number; + /** + * Summary of common patterns + */ + format?: "url" | "email" | "tel" | "number"; + + /** + * Custom verification, (don't forget to execute callback() when the verification is successful, otherwise the verification will not return) + */ + validator?: (rule: Rule, value: string | number | object | boolean | Date | null | any) => string | Promise; + + /** + * The name of the event that triggered the validation + */ + trigger?: "onChange" | "onBlur" | string; +}; + +export type InitOption = { + /** + * The name of the field + */ + name: FieldName; + + /** + * The name of the event that triggered the data change + * @default 'onChange' + */ + trigger?: string | "onChange" | "onBlur"; + + /** + * Check rules + */ + rules?: Rule[]; + + /** + * Component custom events can be written here, others will be transparently transmitted (small package version ^0.3.0 support, large package ^0.7.0 support) + */ + props?: any; +}; + +export type FieldInstance = { + /** + * Initialize each component + */ + registerField(option?: InitOption): InitResult; + + /** + * check + * @param name + */ + validatePromise(name?: FieldName): Promise; + + /** + * Get the value of a single input control + * @param field name + */ + getValue(name: FieldName): any; + + /** + * Get the values ​​of a set of input controls, if no parameters are passed in, get the values ​​of all components + * @param names + */ + getValues(): any; + + /** + * Set the value of a single input control (will trigger render, please follow the timing of react) + */ + setValue(name: FieldName, value: any): void; + + /** + * Set the value of a set of input controls (will trigger render, please follow the timing of react) + */ + setValues(obj: any): void; + + /** + * Reset values + */ + resetValues(obj: any): void; + + /** + * Delete value + */ + deleteValue(key: FieldName): void; + + /** + * Get the Error of a single input control + */ + getError(name: FieldName): null | string[]; + + /** + * Get the Error for a set of input controls + * @param names field name + */ + getErrors(): any; + + /** + * Sets the Error for a single input control + * @param name + * @param errors + */ + setError(name: FieldName, errors: null | string[]): void; + + /** + * Sets the Error for a set of input controls + */ + setErrors(obj: any): void; +}; + +export type ValidateFunction = ( + rule: Rule & { field: string; aliasName?: string }, + value: string | number | object | boolean | Date +) => string | Promise | undefined; diff --git a/public/lib/field/messages.ts b/public/lib/field/messages.ts new file mode 100644 index 000000000..df8e34dc4 --- /dev/null +++ b/public/lib/field/messages.ts @@ -0,0 +1,30 @@ +export default { + default: "%s verification failed", + required: "%s is a required field", + format: { + number: "%s is not a legal number", + email: "%s is not a valid email address", + url: "%s is not a valid URL address", + tel: "%s is not a valid phone number", + }, + number: { + length: "%s length must be %s", + min: "%s field value must not be less than %s", + max: "%s field value must not be greater than %s", + minLength: "%s field character length must be at least %s", + maxLength: "%s field character length cannot exceed %s", + }, + string: { + length: "%s length must be %s", + min: "%s field value must not be less than %s", + max: "%s field value must not be greater than %s", + minLength: "%s field character length must be at least %s", + maxLength: "%s field character length cannot exceed %s", + }, + array: { + length: "%s length must be %s", + minLength: "%s must not be less than %s", + maxLength: "%s must not exceed %s", + }, + pattern: "%s field value %s does not match the regular %s", +}; diff --git a/public/lib/field/rules/index.ts b/public/lib/field/rules/index.ts new file mode 100644 index 000000000..949fc4564 --- /dev/null +++ b/public/lib/field/rules/index.ts @@ -0,0 +1,90 @@ +import { ValidateFunction } from "../interfaces"; +import messages from "../messages"; +import { format as messageFormat } from "../util"; + +const pattern = { + email: /[\w\u4E00-\u9FA5]+([-+.][\w\u4E00-\u9FA5]+)*@[\w\u4E00-\u9FA5]+([-.][\w\u4E00-\u9FA5]+)*\.[\w\u4E00-\u9FA5]+([-.][\w\u4E00-\u9FA5]+)*/, + url: /^(?:(?:http|https|ftp):\/\/|\/\/)(?:(?:(?:[-\w\u00a1-\uffff]+)(?:\.[-\w\u00a1-\uffff]+)+|localhost)(?::\d{2,5})?(?:(?:\/|#)[^\s]*)?)$/, + number: /\d*/, + tel: /^(1\d{10})$|(((400)-(\d{3})-(\d{4}))|^((\d{7,8})|(\d{3,4})-(\d{7,8})|(\d{7,8})-(\d{1,4}))$)$|^([ ]?)$/, +}; + +const types = { + number(value: any) { + if (isNaN(value)) { + return false; + } + return typeof value === "number" || (typeof value === "string" && !!value.match(pattern.number)); + }, + email(value: any) { + return typeof value === "string" && !!value.match(pattern.email) && value.length < 255; + }, + url(value: any) { + return typeof value === "string" && !!value.match(pattern.url); + }, + tel(value: any) { + return typeof value === "string" && !!value.match(pattern.tel); + }, +}; + +const rules = { + required: (rule, value) => { + if (value === undefined || value === null || value === "" || value.length === 0) { + return messageFormat(rule.message || messages.required, rule.aliasName || rule.field); + } + }, + format: (rule, value) => { + const custom = ["email", "number", "url", "tel"]; + const ruleType = rule.format; + if (ruleType && custom.indexOf(ruleType) > -1 && !types[ruleType](value)) { + return messageFormat(rule.message || messages.format[ruleType], rule.aliasName || rule.field, ruleType); + } + }, + size: (rule, value) => { + let key: "number" | "string" | null = null; + const isNum = typeof value === "number"; + const isStr = typeof value === "string"; + + if (isNum) { + key = "number"; + } else if (isStr) { + key = "string"; + } + + if (!key) { + return false; + } + + if (typeof rule.min === "number" || typeof rule.max === "number") { + let val = value; + const max = Number(rule.max); + const min = Number(rule.min); + + if (isStr) { + val = Number(val); + } + + if (val < min) { + return messageFormat(rule.message || messages[key].min, rule.aliasName || rule.field, "" + rule.min); + } else if (val > max) { + return messageFormat(rule.message || messages[key].max, rule.aliasName || rule.field, "" + rule.max); + } + } + }, + pattern: (rule, value: string) => { + if (rule.pattern) { + if (rule.pattern instanceof RegExp) { + if (!rule.pattern.test(value)) { + return messageFormat(rule.message || messages.pattern, rule.aliasName || rule.field, value, rule.pattern.toString()); + } + } else if (typeof rule.pattern === "string") { + const _pattern = new RegExp(rule.pattern); + if (!_pattern.test(value)) { + return messageFormat(rule.message || messages.pattern, rule.aliasName || rule.field, value, rule.pattern); + } + } + } + }, +} as Record; + +export default rules as Record; diff --git a/public/lib/field/util.ts b/public/lib/field/util.ts new file mode 100644 index 000000000..6511e2160 --- /dev/null +++ b/public/lib/field/util.ts @@ -0,0 +1,34 @@ +const formatRegExp = /%[sdj%]/g; + +export function format(...args: string[]) { + let i = 1; + const f = args[0]; + const len = args.length; + if (typeof f === "string") { + const str = String(f).replace(formatRegExp, (x) => { + if (x === "%%") { + return "%"; + } + if (i >= len) { + return x; + } + switch (x) { + case "%s": + return String(args[i++]); + case "%d": + return `${Number(args[i++])}`; + case "%j": + try { + return JSON.stringify(args[i++]); + } catch (_) { + return "[Circular]"; + } + default: + return x; + } + }); + + return str; + } + return f; +} diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index 7a8fb9784..41a60d9ab 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -5,6 +5,7 @@ import { Direction, Query } from "@elastic/eui"; import { SMPolicy } from "../../models/interfaces"; +import { IJobItemMetadata } from "../lib/JobScheduler"; import { IndexService, ManagedIndexService, @@ -13,6 +14,7 @@ import { TransformService, NotificationService, SnapshotManagementService, + CommonService, } from "../services"; export interface BrowserServices { @@ -23,6 +25,7 @@ export interface BrowserServices { transformService: TransformService; notificationService: NotificationService; snapshotManagementService: SnapshotManagementService; + commonService: CommonService; } export interface SMPoliciesQueryParams { @@ -60,7 +63,6 @@ export interface Column { sortable: boolean; } - export interface RestoreError { reason?: string; type?: string; @@ -108,3 +110,19 @@ export interface IndexItem { index: string; restore_status?: string; } +export interface ReindexJobMetaData extends IJobItemMetadata { + extras: { + toastId: string; + sourceIndex: string; + destIndex: string; + taskId: string; + }; +} + +export interface RecoveryJobMetaData extends IJobItemMetadata { + extras: { + toastId: string; + sourceIndex: string; + destIndex: string; + }; +} diff --git a/public/plugin.ts b/public/plugin.ts index ce07e27e6..059190ae1 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -8,6 +8,7 @@ import { IndexManagementPluginSetup } from "."; import { IndexManagementPluginStart } from "."; import { actionRepoSingleton } from "./pages/VisualCreatePolicy/utils/helpers"; import { ROUTES } from "./utils/constants"; +import { JobHandlerRegister } from "./JobHandler"; export class IndexManagementPlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) { @@ -15,6 +16,7 @@ export class IndexManagementPlugin implements Plugin { + it("calls api caller nodejs route when calling apiCaller", async () => { + httpClientMock.fetch = jest.fn().mockResolvedValue({ ok: true }); + const queryObject = { + endpoint: "indices.get", + }; + await commonService.apiCaller(queryObject); + + expect(httpClientMock.fetch).toHaveBeenCalledTimes(1); + expect(httpClientMock.fetch).toHaveBeenCalledWith(`${NODE_API.API_CALLER}`, { + method: "POST", + body: JSON.stringify(queryObject), + }); + }); +}); diff --git a/public/services/CommonService.ts b/public/services/CommonService.ts new file mode 100644 index 000000000..2df20b5b6 --- /dev/null +++ b/public/services/CommonService.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpFetchOptions, HttpSetup } from "opensearch-dashboards/public"; +import { ServerResponse } from "../../server/models/types"; +import { NODE_API } from "../../utils/constants"; +import { IAPICaller } from "../../models/interfaces"; + +export default class CommonService { + httpClient: HttpSetup; + + constructor(httpClient: HttpSetup) { + this.httpClient = httpClient; + } + + apiCaller = async (params: IAPICaller): Promise> => { + let url = `${NODE_API.API_CALLER}`; + const payload: HttpFetchOptions = {}; + payload.method = "POST"; + payload.body = JSON.stringify({ + data: params.data, + endpoint: params.endpoint, + }); + return (await this.httpClient.fetch(url, payload)) as ServerResponse; + }; +} diff --git a/public/services/IndexService.test.ts b/public/services/IndexService.test.ts index dd877cd7b..749a12c48 100644 --- a/public/services/IndexService.test.ts +++ b/public/services/IndexService.test.ts @@ -45,4 +45,12 @@ describe("IndexService spec", () => { expect(httpClientMock.get).toHaveBeenCalledTimes(1); }); + + it("calls search nodejs route when calling getAlias", async () => { + httpClientMock.post = jest.fn().mockResolvedValue({ data: {} }); + const queryObject = {}; + await indexService.getAliases(queryObject); + + expect(httpClientMock.get).toHaveBeenCalledTimes(1); + }); }); diff --git a/public/services/IndexService.ts b/public/services/IndexService.ts index da2d058a4..777a32b52 100644 --- a/public/services/IndexService.ts +++ b/public/services/IndexService.ts @@ -8,6 +8,7 @@ import { AcknowledgedResponse, ApplyPolicyResponse, DataStream, + GetAliasesResponse, GetDataStreamsAndIndicesNamesResponse, GetDataStreamsResponse, GetIndicesResponse, @@ -36,9 +37,22 @@ export default class IndexService { return await this.httpClient.get(url, { query: queryObject }); }; + getAliases = async (queryObject: HttpFetchQuery): Promise> => { + const url = `..${NODE_API._ALIASES}`; + return await this.httpClient.get(url, { query: queryObject }); + }; + getDataStreamsAndIndicesNames = async (searchValue: string): Promise> => { const [getIndicesResponse, getDataStreamsResponse] = await Promise.all([ - this.getIndices({ from: 0, size: 100, search: searchValue, sortDirection: "desc", sortField: "index", showDataStreams: true }), + this.getIndices({ + from: 0, + size: 10, + search: searchValue, + terms: [searchValue], + sortDirection: "desc", + sortField: "index", + showDataStreams: true, + }), this.getDataStreams({ search: searchValue }), ]); diff --git a/public/services/SnapshotManagementService.ts b/public/services/SnapshotManagementService.ts index 75fe1fa22..74d35b10d 100644 --- a/public/services/SnapshotManagementService.ts +++ b/public/services/SnapshotManagementService.ts @@ -47,9 +47,10 @@ export default class SnapshotManagementService { createSnapshot = async (snapshotId: string, repository: string, snapshot: Snapshot): Promise> => { let url = `..${NODE_API._SNAPSHOTS}/${snapshotId}`; - const response = (await this.httpClient.put(url, { query: { repository }, body: JSON.stringify(snapshot) })) as ServerResponse< - CreateSnapshotResponse - >; + const response = (await this.httpClient.put(url, { + query: { repository }, + body: JSON.stringify(snapshot), + })) as ServerResponse; return response; }; @@ -81,9 +82,10 @@ export default class SnapshotManagementService { primaryTerm: number ): Promise> => { let url = `..${NODE_API.SMPolicies}/${policyId}`; - const response = (await this.httpClient.put(url, { query: { seqNo, primaryTerm }, body: JSON.stringify(policy) })) as ServerResponse< - DocumentSMPolicy - >; + const response = (await this.httpClient.put(url, { + query: { seqNo, primaryTerm }, + body: JSON.stringify(policy), + })) as ServerResponse; return response; }; diff --git a/public/services/index.ts b/public/services/index.ts index 28eee9f41..2a6397bec 100644 --- a/public/services/index.ts +++ b/public/services/index.ts @@ -11,6 +11,7 @@ import RollupService from "./RollupService"; import TransformService from "./TransformService"; import NotificationService from "./NotificationService"; import SnapshotManagementService from "./SnapshotManagementService"; +import CommonService from "./CommonService"; export { ServicesConsumer, @@ -22,4 +23,5 @@ export { TransformService, NotificationService, SnapshotManagementService, + CommonService, }; diff --git a/public/utils/constants.ts b/public/utils/constants.ts index a45f5a14d..07250614c 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -3,6 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { InitOption } from "../lib/field"; +import { ComponentMapEnum } from "../components/FormGenerator"; + export const PLUGIN_NAME = "opensearch_index_management_dashboards"; export const DEFAULT_EMPTY_DATA = "-"; @@ -13,7 +16,7 @@ export const ACTIONS_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ export const STATES_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#states"; export const ERROR_NOTIFICATION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#error-notifications"; export const TRANSITION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#transitions"; -export const INDEX_SETTINGS_URL = "https://opensearch.org/docs/latest/api-reference/index-apis/create-index/#index-settings"; +export const INDEX_SETTINGS_URL = "https://opensearch.org/docs/latest/api-reference/index-apis/create-index#index-settings"; export const SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-management/"; export const CRON_EXPRESSION_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/monitoring-plugins/alerting/cron/"; export const RESTORE_SNAPSHOT_DOCUMENTATION_URL = @@ -22,6 +25,7 @@ export const REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/ export const FS_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#shared-file-system"; export const S3_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#amazon-s3"; +export const SHRINK_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/api-reference/index-apis/shrink-index"; export const ROUTES = Object.freeze({ CHANGE_POLICY: "/change-policy", @@ -49,6 +53,14 @@ export const ROUTES = Object.freeze({ REPOSITORIES: "/repositories", CREATE_REPOSITORY: "/create-repository", EDIT_REPOSITORY: "/edit-repository", + CREATE_INDEX: "/create-index", + INDEX_DETAIL: "/index-detail", + REINDEX: "/reindex", + ALIASES: "/aliases", + TEMPLATES: "/templates", + CREATE_TEMPLATE: "/create-template", + SPLIT_INDEX: "/split-index", + SHRINK_INDEX: "/shrink-index", }); export const BREADCRUMBS = Object.freeze({ @@ -84,6 +96,16 @@ export const BREADCRUMBS = Object.freeze({ REPOSITORIES: { text: "Repositories", href: `#${ROUTES.REPOSITORIES}` }, CREATE_REPOSITORY: { text: "Create repository", href: `#${ROUTES.CREATE_REPOSITORY}` }, EDIT_REPOSITORY: { text: "Edit repository", href: `#${ROUTES.EDIT_REPOSITORY}` }, + CREATE_INDEX: { text: "Create Index", href: `#${ROUTES.CREATE_INDEX}` }, + EDIT_INDEX: { text: "Edit Index", href: `#${ROUTES.CREATE_INDEX}` }, + INDEX_DETAIL: { text: "Index Detail", href: "#" }, + REINDEX: { text: "Reindex", href: `#${ROUTES.REINDEX}` }, + ALIASES: { text: "Aliases", href: `#${ROUTES.ALIASES}` }, + TEMPLATES: { text: "Templates", href: `#${ROUTES.TEMPLATES}` }, + CREATE_TEMPLATE: { text: "Create template", href: `#${ROUTES.CREATE_TEMPLATE}` }, + EDIT_TEMPLATE: { text: "Edit template", href: `#${ROUTES.CREATE_TEMPLATE}` }, + SPLIT_INDEX: { text: "Split Index", href: `#${ROUTES.SPLIT_INDEX}` }, + SHRINK_INDEX: { text: "Shrink index", href: `#${ROUTES.SHRINK_INDEX}` }, }); // TODO: EUI has a SortDirection already @@ -122,7 +144,7 @@ export const browseIndicesCols = [ width: "100%", truncateText: true, sortable: true, - } + }, ]; export const restoreIndicesCols = [ @@ -137,6 +159,181 @@ export const restoreIndicesCols = [ field: "restore_status", name: "Restore status", width: "25%", - sortable: true - } -]; \ No newline at end of file + sortable: true, + }, +]; +export const INDEX_IMPORT_SETTINGS = ["index.number_of_replicas", "index.number_of_shards", "index.refresh_interval"]; + +export const INDEX_DYNAMIC_SETTINGS = [ + "index.number_of_replicas", + "index.auto_expand_replicas", + "index.search.idle.after", + "index.refresh_interval", + "index.max_result_window", + "index.max_inner_result_window", + "index.max_rescore_window", + "index.max_docvalue_fields_search", + "index.max_script_fields", + "index.max_ngram_diff", + "index.max_shingle_diff", + "index.max_refresh_listeners", + "index.analyze.max_token_count", + "index.highlight.max_analyzed_offset", + "index.max_terms_count", + "index.max_regex_length", + "index.query.default_field", + "index.routing.allocation.enable", + "index.gc_deletes", + "index.default_pipeline", + "index.final_pipeline", + "index.hidden", +]; + +export const INDEX_MAPPING_TYPES: { + label?: string; + hasChildren?: boolean; + options?: { + fields?: (InitOption & { label: string; type: ComponentMapEnum; initValue?: any })[]; + }; +}[] = [ + { + label: "alias", + options: { + fields: [ + { + label: "Path", + name: "path", + type: "Input", + rules: [ + { + required: true, + message: "Path is required.", + }, + ], + }, + ], + }, + }, + { + label: "boolean", + }, + { + label: "binary", + }, + { + label: "completion", + }, + { + label: "date", + }, + { + label: "date_range", + }, + { + label: "double", + }, + { + label: "double_range", + }, + { + label: "float", + }, + { + label: "geo_point", + }, + { + label: "geo_shape", + }, + { + label: "half_float", + }, + { + label: "integer", + }, + { + label: "ip", + }, + { + label: "ip_range", + }, + { + label: "keyword", + }, + { + label: "long", + }, + { + label: "long_range", + }, + { + label: "object", + hasChildren: true, + }, + { + label: "percolator", + }, + { + label: "rank_feature", + }, + { + label: "rank_features", + }, + { + label: "search_as_you_type", + }, + { + label: "text", + }, + { + label: "token_count", + options: { + fields: [ + { + label: "Analyzer", + name: "analyzer", + initValue: "standard", + type: "Input", + rules: [ + { + required: true, + message: "Analyzer is required.", + }, + ], + }, + ], + }, + }, +]; + +export enum IndicesUpdateMode { + mappings = "mappings", + settings = "settings", + alias = "alias", +} + +export const INDEX_MAPPING_TYPES_WITH_CHILDREN = INDEX_MAPPING_TYPES.filter((item) => item.hasChildren).map((item) => item.label); + +export const DEFAULT_LEGACY_ERROR_NOTIFICATION = { + destination: { + slack: { + url: "", + }, + }, + message_template: { + source: "The index {{ctx.index}} failed during policy execution.", + }, +}; + +export const ALIAS_STATUS_OPTIONS = ["open", "closed", "hidden", "none", "all"].map((item) => ({ + label: item, + value: item, +})); + +export const INDEX_NAMING_MESSAGE = `Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, \", *, +, /, \, |, ?, #, > are not allowed.`; + +export const REPLICA_NUMBER_MESSAGE = "Specify the number of replicas each primary shard should have. Default is 1."; + +export const TEMPLATE_TYPE = { + INDEX_TEMPLATE: "Indexes", + DATA_STREAM: "Data streams", +}; diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts index e460d8019..e1417c6d5 100644 --- a/server/models/interfaces.ts +++ b/server/models/interfaces.ts @@ -12,6 +12,7 @@ import { TransformService, NotificationService, SnapshotManagementService, + CommonService, } from "../services"; import { DocumentPolicy, @@ -22,6 +23,7 @@ import { Rollup, Transform, } from "../../models/interfaces"; +import AliasServices from "../services/AliasServices"; export interface NodeServices { indexService: IndexService; @@ -32,6 +34,8 @@ export interface NodeServices { transformService: TransformService; notificationService: NotificationService; snapshotManagementService: SnapshotManagementService; + commonService: CommonService; + aliasService: AliasServices; } export interface SearchResponse { @@ -170,13 +174,13 @@ export interface IndexUpdateResponse { failedIndices: FailedIndex[]; } -export interface ApplyPolicyResponse extends IndexUpdateResponse { } +export interface ApplyPolicyResponse extends IndexUpdateResponse {} -export interface RemovePolicyResponse extends IndexUpdateResponse { } +export interface RemovePolicyResponse extends IndexUpdateResponse {} -export interface ChangePolicyResponse extends IndexUpdateResponse { } +export interface ChangePolicyResponse extends IndexUpdateResponse {} -export interface RetryManagedIndexResponse extends IndexUpdateResponse { } +export interface RetryManagedIndexResponse extends IndexUpdateResponse {} export interface RetryParams { index: string; @@ -322,7 +326,7 @@ export interface QueryStringQuery { export interface CatIndex { "docs.count": string; "docs.deleted": string; - health: string; + health: "red" | "yellow" | "green"; index: string; pri: string; "pri.store.size": string; @@ -331,15 +335,17 @@ export interface CatIndex { "store.size": string; uuid: string; data_stream: string | null; + extraStatus?: "recovery" | "reindex" | "open" | "close"; } export interface CatSnapshotIndex { index?: string; - "restore_status"?: string; + restore_status?: string; } export interface ManagedCatIndex extends CatIndex { managed: string; + managedPolicy: string; } export interface DataStream { @@ -465,3 +471,17 @@ export interface GetSMPoliciesResponse { policies: DocumentSMPolicy[]; totalPolicies: number; } + +export interface Alias { + alias: string; + index: string; + filter: string; + is_write_index: string; + "routing.index": string; + "routing.search": string; +} + +export interface GetAliasesResponse { + aliases: Alias[]; + totalAliases: number; +} diff --git a/server/models/types.ts b/server/models/types.ts index cbb7566b6..45ae6b8d9 100644 --- a/server/models/types.ts +++ b/server/models/types.ts @@ -25,5 +25,5 @@ export type RollupsSort = { "rollup.rollup.last_updated_time": "rollup.last_updated_time"; }; -export type ServerResponse = FailedServerResponse | { ok: true; response: T }; +export type ServerResponse = FailedServerResponse | { ok: true; response: T; error?: string }; export type FailedServerResponse = { ok: false; error: string }; diff --git a/server/plugin.ts b/server/plugin.ts index 27943917d..43f0c5909 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -15,9 +15,22 @@ import { DataStreamService, NotificationService, SnapshotManagementService, + CommonService, + AliasServices, } from "./services"; -import { indices, policies, managedIndices, rollups, transforms, notifications, snapshotManagement } from "../server/routes"; +import { + indices, + policies, + managedIndices, + rollups, + transforms, + notifications, + snapshotManagement, + common, + aliases, +} from "../server/routes"; import dataStreams from "./routes/dataStreams"; +import { NodeServices } from "./models/interfaces"; export class IndexPatternManagementPlugin implements Plugin { public async setup(core: CoreSetup) { @@ -35,7 +48,9 @@ export class IndexPatternManagementPlugin implements Plugin>> => { + try { + const { search } = request.query as { + search?: string; + }; + + const client = this.osDriver.asScoped(request); + const [aliases, apiAccessible, errMsg] = await getAliases(client, search); + + if (!apiAccessible) + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: errMsg, + }, + }); + + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: { + aliases: aliases, + totalAliases: aliases.length, + }, + }, + }); + } catch (err) { + console.error("Index Management - AliasesService - getAliases:", err); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: err.message, + }, + }); + } + }; +} + +export async function getAliases( + { callAsCurrentUser: callWithRequest }: ILegacyScopedClusterClient, + search?: string +): Promise<[Alias[], boolean, string]> { + const searchPattern = search ? `*${search}*` : "*"; + + let accessible = true; + let errMsg = ""; + const aliasesResponse = await callWithRequest("cat.aliases", { + format: "json", + name: searchPattern, + }).catch((e) => { + if (e.statusCode === 403 && e.message.startsWith(SECURITY_EXCEPTION_PREFIX)) { + accessible = false; + errMsg = e.message; + return { alias: [] }; + } + throw e; + }); + + return [aliasesResponse, accessible, errMsg]; +} diff --git a/server/services/CommonService.ts b/server/services/CommonService.ts new file mode 100644 index 000000000..9ba04cffc --- /dev/null +++ b/server/services/CommonService.ts @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AcknowledgedResponse } from "../models/interfaces"; +import { ServerResponse } from "../models/types"; +import { + OpenSearchDashboardsRequest, + OpenSearchDashboardsResponseFactory, + ILegacyCustomClusterClient, + IOpenSearchDashboardsResponse, + RequestHandlerContext, +} from "../../../../src/core/server"; +import { IAPICaller } from "../../models/interfaces"; + +export interface ICommonCaller { + (arg: any): T; +} + +export default class IndexService { + osDriver: ILegacyCustomClusterClient; + + constructor(osDriver: ILegacyCustomClusterClient) { + this.osDriver = osDriver; + } + + apiCaller = async ( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory + ): Promise>> => { + try { + const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); + const useQuery = !request.body; + const usedParam = (useQuery ? request.query : request.body) as IAPICaller; + const { endpoint, data } = usedParam || {}; + const payload = useQuery ? JSON.parse(data || "{}") : data; + const commonCallerResponse = await callWithRequest(endpoint, payload || {}); + return response.custom({ + statusCode: 200, + body: { + ok: true, + response: commonCallerResponse, + }, + }); + } catch (err) { + console.error("Index Management - CommonService - apiCaller", err); + return response.custom({ + statusCode: 200, + body: { + ok: false, + error: err.message, + }, + }); + } + }; +} diff --git a/server/services/IndexService.ts b/server/services/IndexService.ts index 007cf63c5..952aca459 100644 --- a/server/services/IndexService.ts +++ b/server/services/IndexService.ts @@ -24,6 +24,7 @@ import { } from "../../../../src/core/server"; import { getSearchString } from "../utils/helpers"; import { getIndexToDataStreamMapping } from "./DataStreamService"; +import { IRecoveryItem, IReindexItem, ITaskItem } from "../../models/interfaces"; export default class IndexService { osDriver: ILegacyCustomClusterClient; @@ -58,16 +59,80 @@ export default class IndexService { const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); - const [indicesResponse, indexToDataStreamMapping]: [CatIndex[], IndexToDataStream] = await Promise.all([ + const [recoverys, tasks, indicesResponse, indexToDataStreamMapping]: [ + IRecoveryItem[], + ITaskItem[], + CatIndex[], + IndexToDataStream + ] = await Promise.all([ + callWithRequest("cat.recovery", { + format: "json", + detailed: true, + }), + callWithRequest("cat.tasks", { + format: "json", + detailed: true, + actions: "indices:data/write/reindex", + }), callWithRequest("cat.indices", params), getIndexToDataStreamMapping({ callAsCurrentUser: callWithRequest }), ]); + const formattedTasks: IReindexItem[] = tasks.map( + (item): IReindexItem => { + const { description } = item; + const regexp = /reindex from \[([^\]]+)\] to \[([^\]]+)\]/i; + const matchResult = description.match(regexp); + if (matchResult) { + const [, fromIndex, toIndex] = matchResult; + return { ...item, fromIndex, toIndex }; + } else { + return { + ...item, + fromIndex: "", + toIndex: "", + }; + } + } + ); + + const onGoingRecovery = recoverys.filter((item) => item.stage !== "done"); + // Augment the indices with their parent data stream name. indicesResponse.forEach((index) => { index.data_stream = indexToDataStreamMapping[index.index] || null; + let extraStatus: CatIndex["extraStatus"] = index.status as "open" | "close"; + if (index.health === "green") { + if (formattedTasks.find((item) => item.toIndex === index.index)) { + extraStatus = "reindex"; + } + } else { + if (onGoingRecovery.find((item) => item.index === index.index)) { + extraStatus = "recovery"; + } + } + + if (extraStatus) { + index.extraStatus = extraStatus; + } }); + if (sortField === "status") { + // add new more status to status field so we need to sort + indicesResponse.sort((a, b) => { + let flag; + const aStatus = a.extraStatus as string; + const bStatus = b.extraStatus as string; + if (sortDirection === "asc") { + flag = aStatus < bStatus; + } else { + flag = aStatus > bStatus; + } + + return flag ? -1 : 1; + }); + } + // Filtering out indices that belong to a data stream. This must be done before pagination. const filteredIndices = showDataStreams ? indicesResponse : indicesResponse.filter((index) => index.data_stream === null); @@ -85,7 +150,11 @@ export default class IndexService { body: { ok: true, response: { - indices: paginatedIndices.map((catIndex: CatIndex) => ({ ...catIndex, managed: managedStatus[catIndex.index] || "N/A" })), + indices: paginatedIndices.map((catIndex: CatIndex) => ({ + ...catIndex, + managed: managedStatus[catIndex.index] ? "Yes" : "No", + managedPolicy: managedStatus[catIndex.index], + })), totalIndices: filteredIndices.length, }, }, @@ -125,7 +194,10 @@ export default class IndexService { for (const indexName in explainResponse) { if (indexName === "total_managed_indices") continue; const explain = explainResponse[indexName] as ExplainAPIManagedIndexMetaData; - managed[indexName] = explain["index.plugins.index_state_management.policy_id"] === null ? "No" : "Yes"; + managed[indexName] = + explain["index.plugins.index_state_management.policy_id"] === null + ? "" + : explain["index.plugins.index_state_management.policy_id"]; } return managed; diff --git a/server/services/index.ts b/server/services/index.ts index 7f74da407..9e5d3cf3e 100644 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -11,6 +11,8 @@ import RollupService from "./RollupService"; import TransformService from "./TransformService"; import NotificationService from "./NotificationService"; import SnapshotManagementService from "./SnapshotManagementService"; +import CommonService from "./CommonService"; +import AliasServices from "./AliasServices"; export { IndexService, @@ -21,4 +23,6 @@ export { TransformService, NotificationService, SnapshotManagementService, + CommonService, + AliasServices, }; diff --git a/test/jest.config.js b/test/jest.config.js index d88355ea1..a5214b1aa 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -34,6 +34,10 @@ module.exports = { "!/build/**", "!/cypress/**", "!**/vendor/**", + "!**/index.d.ts", + "!**/lib/field/**", + // There is a compile error in monaco-editor, ignore related components + "!**/components/JSONDiffEditor/**", ], clearMocks: true, testPathIgnorePatterns: ["/build/", "/node_modules/"], diff --git a/test/mocks/browserServicesMock.ts b/test/mocks/browserServicesMock.ts index fd6aa320f..ec532f136 100644 --- a/test/mocks/browserServicesMock.ts +++ b/test/mocks/browserServicesMock.ts @@ -10,6 +10,8 @@ import { RollupService, TransformService, NotificationService, + CommonService, + SnapshotManagementService, } from "../../public/services"; import httpClientMock from "./httpClientMock"; @@ -19,6 +21,8 @@ const policyService = new PolicyService(httpClientMock); const rollupService = new RollupService(httpClientMock); const transformService = new TransformService(httpClientMock); const notificationService = new NotificationService(httpClientMock); +const snapshotManagementService = new SnapshotManagementService(httpClientMock); +const commonService = new CommonService(httpClientMock); export default { indexService, @@ -27,4 +31,6 @@ export default { rollupService, transformService, notificationService, + snapshotManagementService, + commonService, }; diff --git a/test/mocks/coreServicesMock.ts b/test/mocks/coreServicesMock.ts index 30146ce00..3f2f435d8 100644 --- a/test/mocks/coreServicesMock.ts +++ b/test/mocks/coreServicesMock.ts @@ -14,8 +14,26 @@ const coreServicesMock = { }, notifications: { toasts: { - addDanger: jest.fn().mockName("addDanger"), - addSuccess: jest.fn().mockName("addSuccess"), + addDanger: jest.fn(() => ({})).mockName("addDanger"), + addSuccess: jest.fn(() => ({})).mockName("addSuccess"), + }, + }, + docLinks: { + links: { + opensearch: { + reindexData: { + base: "https://opensearch.org/docs/latest/opensearch/reindex-data/", + }, + queryDSL: { + base: "https://opensearch.org/docs/opensearch/query-dsl/index/", + }, + indexTemplates: { + base: "https://opensearch.org/docs/latest/opensearch/index-templates", + }, + indexAlias: { + base: "https://opensearch.org/docs/latest/opensearch/index-alias/", + }, + }, }, }, }; diff --git a/test/mocks/httpClientMock.ts b/test/mocks/httpClientMock.ts index 25964605e..ed0b31795 100644 --- a/test/mocks/httpClientMock.ts +++ b/test/mocks/httpClientMock.ts @@ -12,5 +12,6 @@ httpClientMock.get = jest.fn(); httpClientMock.head = jest.fn(); httpClientMock.post = jest.fn(); httpClientMock.put = jest.fn(); +httpClientMock.fetch = jest.fn(); export default httpClientMock as HttpSetup; diff --git a/test/mocks/index.ts b/test/mocks/index.ts index df4405a77..a4edfa2a0 100644 --- a/test/mocks/index.ts +++ b/test/mocks/index.ts @@ -9,4 +9,118 @@ import httpClientMock from "./httpClientMock"; import styleMock from "./styleMock"; import coreServicesMock from "./coreServicesMock"; -export { browserServicesMock, historyMock, httpClientMock, styleMock, coreServicesMock }; +const apiCallerMock = (browserServicesMockObject: typeof browserServicesMock) => { + browserServicesMockObject.commonService.apiCaller = jest.fn( + async (payload): Promise => { + switch (payload.endpoint) { + case "transport.request": { + if (payload.data?.path?.startsWith("/_index_template/_simulate_index/bad_index")) { + return { + ok: true, + response: {}, + }; + } else if (payload.data?.path?.startsWith("_index_template/bad_template")) { + return { + ok: false, + error: "bad template", + }; + } else if (payload.data?.path?.startsWith("_index_template/good_template")) { + return { + ok: true, + response: { + index_templates: [ + { + name: "good_template", + index_template: {}, + }, + ], + }, + }; + } else { + return { + ok: true, + response: { + template: { + settings: { + index: { + number_of_replicas: "10", + number_of_shards: "1", + }, + }, + }, + }, + }; + } + } + case "indices.create": + if (payload.data?.index === "bad_index") { + return { + ok: false, + error: "bad_index", + }; + } + + return { + ok: true, + response: {}, + }; + break; + case "cat.aliases": + return { + ok: true, + response: [ + { + alias: ".kibana", + index: ".kibana_1", + filter: "-", + is_write_index: "-", + }, + { + alias: "2", + index: "1234", + filter: "-", + is_write_index: "-", + }, + ], + }; + case "indices.get": + const payloadIndex = payload.data?.index; + if (payloadIndex === "bad_index") { + return { + ok: false, + error: "bad_error", + response: {}, + }; + } + + return { + ok: true, + response: { + [payload.data?.index]: { + aliases: { + update_test_1: {}, + }, + mappings: { + properties: { + test_mapping_1: { + type: "text", + }, + }, + }, + settings: { + "index.number_of_shards": "1", + "index.number_of_replicas": "1", + }, + }, + }, + }; + } + return { + ok: true, + response: {}, + }; + } + ); +}; + +export { browserServicesMock, historyMock, httpClientMock, styleMock, coreServicesMock, apiCallerMock }; diff --git a/utils/constants.ts b/utils/constants.ts index 685b5f99d..0dda4d67a 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -9,6 +9,7 @@ export const NODE_API = Object.freeze({ _SEARCH_SAMPLE_DATA: `${BASE_API_PATH}/_searchSampleData`, _INDICES: `${BASE_API_PATH}/_indices`, _DATA_STREAMS: `${BASE_API_PATH}/_data_streams`, + _ALIASES: `${BASE_API_PATH}/_aliases`, _MAPPINGS: `${BASE_API_PATH}/_mappings`, APPLY_POLICY: `${BASE_API_PATH}/applyPolicy`, EDIT_ROLLOVER_ALIAS: `${BASE_API_PATH}/editRolloverAlias`, @@ -24,6 +25,8 @@ export const NODE_API = Object.freeze({ SMPolicies: `${BASE_API_PATH}/smPolicies`, _SNAPSHOTS: `${BASE_API_PATH}/_snapshots`, _REPOSITORIES: `${BASE_API_PATH}/_repositores`, + PUT_INDEX: `${BASE_API_PATH}/putIndex`, + API_CALLER: `${BASE_API_PATH}/apiCaller`, }); export const REQUEST = Object.freeze({ @@ -33,3 +36,28 @@ export const REQUEST = Object.freeze({ POST: "POST", HEAD: "HEAD", }); + +export const SYSTEM_INDEX = [ + ".plugins-ml-model", + ".plugins-ml-task", + ".opendistro-alerting-config", + ".opendistro-alerting-alert*", + ".opendistro-anomaly-results*", + ".opendistro-anomaly-detector*", + ".opendistro-anomaly-checkpoints", + ".opendistro-anomaly-detection-state", + ".opendistro-reports-*", + ".opensearch-notifications-*", + ".opensearch-notebooks", + ".opensearch-observability", + ".opendistro-asynchronous-search-response*", + ".opendistro_security", + ".opendistro-job-scheduler-lock", + ".opendistro-ism-config", + ".replication-metadata-store", + "kibana*", + ".kibana*", + ".tasks", +]; + +export const SYSTEM_ALIAS = [".plugins*", ".opendistro*", ".opensearch*", ".replication-metadata-store", "kibana*", ".kibana*", ".tasks"]; diff --git a/utils/helper.ts b/utils/helper.ts new file mode 100644 index 000000000..1372474d9 --- /dev/null +++ b/utils/helper.ts @@ -0,0 +1,5 @@ +// minimatch is a peer dependency of glob +import minimatch from "minimatch"; +export const filterByMinimatch = (input: string, rules: string[]): boolean => { + return rules.some((item) => minimatch(input, item)); +}; diff --git a/yarn.lock b/yarn.lock index 31123dffd..890ddb5a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -112,27 +112,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== - dependencies: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== - dependencies: - "@nodelib/fs.scandir" "2.1.4" - fastq "^1.6.0" - "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" @@ -154,6 +133,17 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/react-hooks@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" + integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + react-error-boundary "^3.1.0" + "@testing-library/user-event@^13.1.9": version "13.1.9" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.1.9.tgz#29e49a42659ac3c1023565ff56819e0153a82e99" @@ -171,13 +161,15 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b" integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== -"@types/glob@^7.1.1": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" +"@types/diff@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.2.tgz#dd565e0086ccf8bc6522c6ebafd8a3125c91c12b" + integrity sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg== + +"@types/flat@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/flat/-/flat-5.0.2.tgz#642a51a037d1f52fda082312b0e4566dc09a9f8f" + integrity sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ== "@types/history@*": version "4.7.8" @@ -216,32 +208,27 @@ dependencies: jest-diff "^24.3.0" -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/node@*": - version "10.12.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.27.tgz#eb3843f15d0ba0986cc7e4d734d2ee8b50709ef8" - integrity sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg== - "@types/node@12.12.50": version "12.12.50" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== "@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + version "2.4.1" + resolved "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@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" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@^16.9.8": +"@types/react-dom@>=16.9.0", "@types/react-dom@^16.9.8": version "16.9.10" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f" integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw== @@ -265,7 +252,14 @@ "@types/history" "*" "@types/react" "*" -"@types/react@*", "@types/react@^16", "@types/react@^16.9.8": +"@types/react-test-renderer@>=16.9.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz#7b7f69ca98821ea5501b21ba24ea7b6139da2243" + integrity sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@>=16.9.0", "@types/react@^16", "@types/react@^16.9.8": version "16.14.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.3.tgz#f5210f5deecf35d8794845549c93c2c3ad63aa9c" integrity sha512-zPrXn03hmPYqh9DznqSFQsoRtrQ4aHgnZDO+hMGvsE/PORvDTdJCHQ6XvJV31ic+0LzF73huPFXUb++W6Kri0Q== @@ -483,11 +477,23 @@ ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + ansi-escapes@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== +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== + dependencies: + type-fest "^0.21.3" + ansi-regex@^2.0.0, ansi-regex@^3.0.0, ansi-regex@^4.0.0, ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -580,11 +586,6 @@ array-find@^1.0.0: resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" integrity sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg= -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -625,6 +626,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -901,22 +907,27 @@ cachedir@^2.3.0: caller-callsite@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + resolved "https://registry.npmmirror.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + resolved "https://registry.npmmirror.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + resolved "https://registry.npmmirror.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.0.0: version "5.3.1" @@ -1049,6 +1060,13 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-table3@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" @@ -1067,6 +1085,14 @@ cli-truncate@^0.2.1: slice-ansi "0.0.4" string-width "^1.0.1" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -1113,6 +1139,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.16: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -1135,6 +1166,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.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -1199,7 +1235,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: cosmiconfig@^5.2.1: version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== dependencies: import-fresh "^2.0.0" @@ -1207,6 +1243,17 @@ cosmiconfig@^5.2.1: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -1240,7 +1287,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: cross-spawn@^6.0.0: version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" @@ -1369,10 +1416,10 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@^4.2.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -1413,20 +1460,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" - integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== - dependencies: - globby "^10.0.1" - graceful-fs "^4.2.2" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.1" - p-map "^3.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1445,6 +1478,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -1454,13 +1492,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - dom-accessibility-api@^0.5.9: version "0.5.13" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz#102ee5f25eacce09bdf1cfa5a298f86da473be4b" @@ -1542,6 +1573,13 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" +enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + errno@^0.1.3, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -1657,7 +1695,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: execa@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + resolved "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== dependencies: cross-spawn "^6.0.0" @@ -1668,22 +1706,7 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-2.1.0.tgz#e5d3ecd837d2a60ec50f3da78fd39767747bbe99" - integrity sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^3.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -execa@^4.0.2: +execa@^4.0.2, execa@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -1782,30 +1805,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.0.3: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fastq@^1.6.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" - integrity sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA== - dependencies: - reusify "^1.0.4" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -1981,12 +1985,12 @@ get-own-enumerable-property-symbols@^3.0.0: get-stdin@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" + resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== get-stream@^4.0.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" @@ -2025,7 +2029,7 @@ glob-all@^3.2.1: glob "^7.1.2" yargs "^15.3.1" -glob-parent@^3.1.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^3.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2063,26 +2067,12 @@ global-dirs@^2.0.1: dependencies: ini "1.3.7" -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - graceful-fs@^4.1.11, graceful-fs@^4.1.15: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: +graceful-fs@^4.1.2, 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" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -2207,7 +2197,7 @@ human-signals@^1.1.1: husky@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.1.0.tgz#5faad520ab860582ed94f0c1a77f0f04c90b57c0" + resolved "https://registry.npmmirror.com/husky/-/husky-3.1.0.tgz#5faad520ab860582ed94f0c1a77f0f04c90b57c0" integrity sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ== dependencies: chalk "^2.4.2" @@ -2232,19 +2222,22 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== -ignore@^5.1.1: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - import-fresh@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== dependencies: caller-path "^2.0.0" resolve-from "^3.0.0" +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2379,8 +2372,8 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-directory@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + resolved "https://registry.npmmirror.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" @@ -2462,11 +2455,6 @@ is-observable@^1.1.0: dependencies: symbol-observable "^1.1.0" -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - is-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" @@ -2645,24 +2633,25 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@^9.2.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.5.0.tgz#290ec605252af646d9b74d73a0fa118362b05a33" - integrity sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA== +lint-staged@^10.2.0: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: - chalk "^2.4.2" - commander "^2.20.0" - cosmiconfig "^5.2.1" - debug "^4.1.1" + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" dedent "^0.7.0" - del "^5.0.0" - execa "^2.0.3" - listr "^0.14.3" - log-symbols "^3.0.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" + log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" - please-upgrade-node "^3.1.1" - string-argv "^0.3.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" stringify-object "^3.3.0" listr-silent-renderer@^1.1.1: @@ -2694,6 +2683,20 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" +listr2@^3.2.2: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" @@ -2755,13 +2758,6 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - log-symbols@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -2779,6 +2775,16 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -2854,11 +2860,6 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3, merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -3044,7 +3045,7 @@ neo-async@^2.5.0, neo-async@^2.6.1: nice-try@^1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + resolved "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== "node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.2.1: @@ -3078,7 +3079,7 @@ nice-try@^1.0.4: normalize-package-data@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" @@ -3100,18 +3101,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: npm-run-path@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== dependencies: path-key "^2.0.0" -npm-run-path@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5" - integrity sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg== - dependencies: - path-key "^3.0.0" - npm-run-path@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -3200,13 +3194,8 @@ ospath@^1.2.2: p-finally@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" @@ -3234,10 +3223,10 @@ p-map@^2.0.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" @@ -3260,6 +3249,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-asn1@^5.0.0, parse-asn1@^5.1.5: version "5.1.6" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" @@ -3273,8 +3269,8 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: parse-json@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + resolved "https://registry.npmmirror.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -3316,8 +3312,8 @@ path-is-absolute@^1.0.0: path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + resolved "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" @@ -3389,7 +3385,7 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -please-upgrade-node@^3.1.1, please-upgrade-node@^3.2.0: +please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== @@ -3542,6 +3538,13 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -3554,7 +3557,7 @@ react-is@^17.0.1: read-pkg@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" @@ -3647,8 +3650,13 @@ require-main-filename@^2.0.0: resolve-from@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-url@^0.2.1: version "0.2.1" @@ -3679,15 +3687,23 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" @@ -3713,14 +3729,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: run-node@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" + resolved "https://registry.npmmirror.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== -run-parallel@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" - integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== - run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -3735,6 +3746,13 @@ rxjs@^6.3.3: dependencies: tslib "^1.9.0" +rxjs@^7.5.1: + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -3818,8 +3836,8 @@ sha.js@^2.4.0, sha.js@^2.4.8: shebang-command@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== dependencies: shebang-regex "^1.0.0" @@ -3832,15 +3850,20 @@ shebang-command@^2.0.0: shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0: + version "3.0.7" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -3855,6 +3878,24 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -3926,7 +3967,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: spdx-correct@^3.0.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + resolved "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" @@ -3934,21 +3975,21 @@ spdx-correct@^3.0.0: spdx-exceptions@^2.1.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + resolved "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + resolved "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" - integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + version "3.0.12" + resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -3960,7 +4001,7 @@ split-string@^3.0.1, split-string@^3.0.2: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: version "1.16.1" @@ -4024,7 +4065,7 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -string-argv@^0.3.0: +string-argv@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== @@ -4101,8 +4142,8 @@ strip-ansi@^6.0.0: strip-eof@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + resolved "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== strip-final-newline@^2.0.0: version "2.0.0" @@ -4180,6 +4221,11 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -4255,6 +4301,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -4272,9 +4323,14 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== typedarray@^0.0.6: @@ -4380,7 +4436,7 @@ uuid@^3.3.2: validate-npm-package-license@^3.0.1: version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + resolved "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" @@ -4462,7 +4518,7 @@ which-module@^2.0.0: which@^1.2.9: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmmirror.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" @@ -4498,6 +4554,15 @@ 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" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4523,6 +4588,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"