diff --git a/.eslintrc.js b/.eslintrc.js index 562ff10ff..b275fe320 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,17 @@ module.exports = { root: true, extends: ["@elastic/eslint-config-kibana", "plugin:@elastic/eui/recommended"], rules: { - // "@osd/eslint/require-license-header": "off" + // "@osd/eslint/require-license-header": "off", + "import/no-default-export": "off", + "@typescript-eslint/naming-convention": [ + "error", + { + selector: "default", + format: ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"], + leadingUnderscore: "allow", + trailingUnderscore: "allow", + }, + ], }, overrides: [ { diff --git a/.lintstagedrc b/.lintstagedrc index e7e7db117..e155bc449 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,3 +1,9 @@ { - "*.{ts,tsx,js,jsx,json,css,md}": ["prettier --write", "git add"] + "*.{ts,tsx}": [ + "npx eslint --fix", + "prettier --write" + ], + "*.{js,jsx,json,css,md}": [ + "prettier --write" + ] } diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 091fe9f91..31de0f4c9 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -/// +// / declare namespace Cypress { interface Chainable { diff --git a/models/interfaces.ts b/models/interfaces.ts index 062dd49e8..1ab88e216 100644 --- a/models/interfaces.ts +++ b/models/interfaces.ts @@ -41,13 +41,13 @@ export type DiffableMappingsPropertiesObject = Record< } >; -export type MappingsProperties = { +export type MappingsProperties = Array<{ fieldName: string; type: string; path?: string; analyzer?: string; properties?: MappingsProperties; -}[]; +}>; export interface IndexItem { index: string; @@ -525,14 +525,14 @@ export interface Transform { enabled_at: number | null; updated_at: number; metadata_id: string | null; - aggregations: Map; + aggregations: Map; page_size: number; schedule: IntervalSchedule | CronSchedule; schema_version: number; source_index: string; target_index: string; - roles: String[]; - data_selection_query: Map; + roles: string[]; + data_selection_query: Map; } export interface TransformMetadata { @@ -571,7 +571,7 @@ export interface CronSchedule { }; } -//Frontend dimension data model +// Frontend dimension data model export interface DimensionItem { sequence: number; field: FieldItem; @@ -579,7 +579,7 @@ export interface DimensionItem { interval?: number; } -//Frontend metric data model +// Frontend metric data model export interface MetricItem { source_field: FieldItem; all: boolean; @@ -620,18 +620,23 @@ interface HistogramItem { }; } -//Backend dimension data model +// Backend dimension data model export type RollupDimensionItem = DateHistogramItem | TermsItem | HistogramItem; -//Backend metric data model +// Backend metric data model export interface RollupMetricItem { source_field: string; metrics: [ { + // eslint-disable-next-line @typescript-eslint/ban-types min?: Object; + // eslint-disable-next-line @typescript-eslint/ban-types max?: Object; + // eslint-disable-next-line @typescript-eslint/ban-types sum?: Object; + // eslint-disable-next-line @typescript-eslint/ban-types avg?: Object; + // eslint-disable-next-line @typescript-eslint/ban-types value_count?: Object; } ]; diff --git a/package.json b/package.json index 7c079c666..3f26c4b4b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "osd": "node ../../scripts/osd", "opensearch": "node ../../scripts/opensearch", "lint": "node ../../scripts/eslint . && node ../../scripts/stylelint", + "eslint:fix": "yarn eslint:run --fix", + "eslint:run": "npx eslint '**/*.{ts,tsx}'", "plugin-helpers": "node ../../scripts/plugin_helpers", "test:jest": "../../node_modules/.bin/jest --config ./test/jest.config.js", "build": "yarn plugin-helpers build", @@ -73,4 +75,4 @@ "engines": { "yarn": "^1.21.1" } -} \ No newline at end of file +} diff --git a/public/JobHandler/callbacks/force_merge.test.tsx b/public/JobHandler/callbacks/force_merge.test.tsx index 049a971e9..858b1737b 100644 --- a/public/JobHandler/callbacks/force_merge.test.tsx +++ b/public/JobHandler/callbacks/force_merge.test.tsx @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { CoreSetup } from "opensearch-dashboards/public"; import { callbackForForceMerge, callbackForForceMergeTimeout } from "./force_merge"; import { coreServicesMock, httpClientMock } from "../../../test/mocks"; import { ListenType } from "../../lib/JobScheduler"; -import { CoreSetup } from "opensearch-dashboards/public"; const getMockFn = (response = {}, ok = true) => { return jest.fn().mockResolvedValue({ @@ -33,7 +33,7 @@ const core = ({ describe("callbackForForceMerge spec", () => { it("callback when error", async () => { httpClientMock.fetch = getMockFn({}, false); - let result = await callbackForForceMerge(forceMergeMetaData, { + const result = await callbackForForceMerge(forceMergeMetaData, { core, }); expect(result).toBe(false); diff --git a/public/JobHandler/callbacks/force_merge.tsx b/public/JobHandler/callbacks/force_merge.tsx index b1685ec5e..4c0219565 100644 --- a/public/JobHandler/callbacks/force_merge.tsx +++ b/public/JobHandler/callbacks/force_merge.tsx @@ -15,11 +15,11 @@ type ForceMergeTaskResult = TaskResult<{ successful: number; total: number; failed: number; - failures?: { + failures?: Array<{ index: string; status: string; shard: number; - }[]; + }>; }; }>; diff --git a/public/JobHandler/callbacks/open.test.tsx b/public/JobHandler/callbacks/open.test.tsx index e6e265d8f..9118e1477 100644 --- a/public/JobHandler/callbacks/open.test.tsx +++ b/public/JobHandler/callbacks/open.test.tsx @@ -2,10 +2,10 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +import { CoreSetup } from "opensearch-dashboards/public"; import { callbackForOpen, callbackForOpenTimeout } from "./open"; import { coreServicesMock, httpClientMock } from "../../../test/mocks"; import { ListenType } from "../../lib/JobScheduler"; -import { CoreSetup } from "opensearch-dashboards/public"; const getMockFn = (response = {}, ok = true) => { return jest.fn().mockResolvedValue({ @@ -32,7 +32,7 @@ const core = ({ describe("callbackForOpen spec", () => { it("callback when error", async () => { httpClientMock.fetch = getMockFn({}, false); - let result = await callbackForOpen(openMetaData, { + const result = await callbackForOpen(openMetaData, { core, }); expect(result).toBe(false); diff --git a/public/JobHandler/callbacks/open.tsx b/public/JobHandler/callbacks/open.tsx index 28056345c..daf3faf9a 100644 --- a/public/JobHandler/callbacks/open.tsx +++ b/public/JobHandler/callbacks/open.tsx @@ -2,6 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ + import React from "react"; import { CallbackType, TaskResult } from "../interface"; import { OpenJobMetaData } from "../../models/interfaces"; diff --git a/public/JobHandler/callbacks/reindex.test.tsx b/public/JobHandler/callbacks/reindex.test.tsx index aae3276f1..7badfe2fc 100644 --- a/public/JobHandler/callbacks/reindex.test.tsx +++ b/public/JobHandler/callbacks/reindex.test.tsx @@ -2,10 +2,10 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +import { CoreSetup } from "opensearch-dashboards/public"; import { callbackForReindex, callbackForReindexTimeout } from "./reindex"; import { coreServicesMock, httpClientMock } from "../../../test/mocks"; import { ListenType } from "../../lib/JobScheduler"; -import { CoreSetup } from "opensearch-dashboards/public"; const getMockFn = (response = {}, ok = true) => { return jest.fn().mockResolvedValue({ @@ -35,7 +35,7 @@ const core = ({ describe("callbackForOpen spec", () => { it("callback when error", async () => { httpClientMock.fetch = getMockFn({}, false); - let result = await callbackForReindex(reindexMetaData, { + const result = await callbackForReindex(reindexMetaData, { core, }); expect(result).toBe(false); diff --git a/public/JobHandler/callbacks/reindex.tsx b/public/JobHandler/callbacks/reindex.tsx index 16cf3f9a9..89b3fda68 100644 --- a/public/JobHandler/callbacks/reindex.tsx +++ b/public/JobHandler/callbacks/reindex.tsx @@ -13,12 +13,12 @@ import { ErrorToastContentForJob } from "../components/ErrorToastContentForJob"; type ReindexTaskResult = TaskResult<{ canceled?: string; - failures: { + failures: Array<{ cause?: { reason: string; }; id?: string; - }[]; + }>; }>; export const callbackForReindex: CallbackType = async (job: ReindexJobMetaData, { core }) => { diff --git a/public/JobHandler/callbacks/shrink.test.tsx b/public/JobHandler/callbacks/shrink.test.tsx index 293461a3f..73612fe43 100644 --- a/public/JobHandler/callbacks/shrink.test.tsx +++ b/public/JobHandler/callbacks/shrink.test.tsx @@ -2,10 +2,10 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +import { CoreSetup } from "opensearch-dashboards/public"; import { callbackForShrink, callbackForShrinkTimeout } from "./shrink"; import { coreServicesMock, httpClientMock } from "../../../test/mocks"; import { ListenType } from "../../lib/JobScheduler"; -import { CoreSetup } from "opensearch-dashboards/public"; const getMockFn = (response = {}, ok = true) => { return jest.fn().mockResolvedValue({ @@ -33,7 +33,7 @@ const core = ({ describe("callbackForOpen spec", () => { it("callback when error", async () => { httpClientMock.fetch = getMockFn({}, false); - let result = await callbackForShrink(shrinkMetaData, { + const result = await callbackForShrink(shrinkMetaData, { core, }); expect(result).toBe(false); diff --git a/public/JobHandler/callbacks/split.test.tsx b/public/JobHandler/callbacks/split.test.tsx index 04f920c2c..f7ca406f4 100644 --- a/public/JobHandler/callbacks/split.test.tsx +++ b/public/JobHandler/callbacks/split.test.tsx @@ -2,10 +2,10 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ +import { CoreSetup } from "opensearch-dashboards/public"; import { callbackForSplit, callbackForSplitTimeout } from "./split"; import { coreServicesMock, httpClientMock } from "../../../test/mocks"; import { ListenType } from "../../lib/JobScheduler"; -import { CoreSetup } from "opensearch-dashboards/public"; const getMockFn = (response = {}, ok = true) => { return jest.fn().mockResolvedValue({ @@ -33,7 +33,7 @@ const core = ({ describe("callbackForOpen spec", () => { it("callback when error", async () => { httpClientMock.fetch = getMockFn({}, false); - let result = await callbackForSplit(splitMetaData, { + const result = await callbackForSplit(splitMetaData, { core, }); expect(result).toBe(false); diff --git a/public/JobHandler/components/components.test.tsx b/public/JobHandler/components/components.test.tsx index 1556f418a..a0814ac2b 100644 --- a/public/JobHandler/components/components.test.tsx +++ b/public/JobHandler/components/components.test.tsx @@ -4,9 +4,9 @@ */ import React from "react"; import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { DetailLink } from "./DetailLink"; import { ErrorToastContentForJob } from "./ErrorToastContentForJob"; -import userEvent from "@testing-library/user-event"; import { FormatResourceWithClusterInfo, FormatResourcesWithClusterInfo } from "./FormatResourceWithClusterInfo"; describe(" spec", () => { diff --git a/public/JobHandler/interface.ts b/public/JobHandler/interface.ts index 08d42638a..377cde68f 100644 --- a/public/JobHandler/interface.ts +++ b/public/JobHandler/interface.ts @@ -12,7 +12,7 @@ export type CallbackType = ( } ) => Promise; -export type TaskResult = { +export interface TaskResult { found: boolean; _source: { completed: boolean; @@ -22,7 +22,7 @@ export type TaskResult = { reason: string; }; }; -}; +} export type RecoveryTaskResult = TaskResult<{ acknowledged: boolean; diff --git a/public/components/AdvancedSettings/AdvancedSettings.test.tsx b/public/components/AdvancedSettings/AdvancedSettings.test.tsx index 037111627..bc9c90fea 100644 --- a/public/components/AdvancedSettings/AdvancedSettings.test.tsx +++ b/public/components/AdvancedSettings/AdvancedSettings.test.tsx @@ -5,8 +5,8 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react"; -import AdvancedSettings from "./index"; import userEvent from "@testing-library/user-event"; +import AdvancedSettings from "./index"; describe(" spec", () => { it("render the component", () => { diff --git a/public/components/AdvancedSettings/index.tsx b/public/components/AdvancedSettings/index.tsx index d78419640..96f2a2109 100644 --- a/public/components/AdvancedSettings/index.tsx +++ b/public/components/AdvancedSettings/index.tsx @@ -40,8 +40,11 @@ function AdvancedSettings(props: IAdvancedSettingsProps, ref: React.Ref, "value" | "onChange"> { value?: Record; onChange?: (value: AliasSelectProps["value"]) => void; - refreshOptions: (aliasName: string) => Promise>; + refreshOptions: (aliasName: string) => Promise>>; onOptionsChange?: RemoteSelectProps["onOptionsChange"]; } const AliasSelect = forwardRef((props: AliasSelectProps, ref: React.Ref) => { const { value, onChange, refreshOptions: refreshOptionsFromProps, onOptionsChange } = props; - const optionsRef = useRef<{ label: string; [key: string]: any }[]>([]); + const optionsRef = useRef>([]); const refreshOptions: RemoteSelectProps["refreshOptions"] = ({ searchValue }) => { return refreshOptionsFromProps(searchValue || "").then((res) => { if (res?.ok) { @@ -51,12 +51,13 @@ const AliasSelect = forwardRef((props: AliasSelectProps, ref: React.Ref { - onChange && + if (onChange) { onChange( val .map((label) => optionsRef.current.find((item) => item.label === label) || { label }) .reduce((total, { label, ...others }) => ({ ...total, [label]: others || {} }), {}) ); + } }} /> ); @@ -64,5 +65,4 @@ const AliasSelect = forwardRef((props: AliasSelectProps, ref: React.Ref spec", () => { it("renders the component", () => { diff --git a/public/components/ConfirmationModal/ConfirmationModal.tsx b/public/components/ConfirmationModal/ConfirmationModal.tsx index 93d9cf746..ed6ad425b 100644 --- a/public/components/ConfirmationModal/ConfirmationModal.tsx +++ b/public/components/ConfirmationModal/ConfirmationModal.tsx @@ -26,7 +26,7 @@ interface ConfirmationModalProps { onAction: () => void; } -const ConfirmationModal: React.SFC = ({ +const ConfirmationModal: React.FC = ({ title, bodyMessage, actionMessage, diff --git a/public/components/ContentPanel/ContentPanel.tsx b/public/components/ContentPanel/ContentPanel.tsx index d3689f90c..739644239 100644 --- a/public/components/ContentPanel/ContentPanel.tsx +++ b/public/components/ContentPanel/ContentPanel.tsx @@ -43,7 +43,7 @@ const renderSubTitleText = (subTitleText: string | JSX.Element): JSX.Element | n return subTitleText; }; -const ContentPanel: React.SFC = ({ +const ContentPanel: React.FC = ({ title = "", titleSize = "l", subTitleText = "", diff --git a/public/components/ContentPanel/ContentPanelActions.tsx b/public/components/ContentPanel/ContentPanelActions.tsx index fc8136093..c2461c2b4 100644 --- a/public/components/ContentPanel/ContentPanelActions.tsx +++ b/public/components/ContentPanel/ContentPanelActions.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; import { ModalConsumer } from "../Modal"; interface ContentPanelActionsProps { - actions: { + actions: Array<{ text: string; buttonProps?: object; flexItemProps?: object; @@ -16,10 +16,10 @@ interface ContentPanelActionsProps { modal?: { onClickModal: (onShow: (component: any, props: object) => void) => () => void; }; - }[]; + }>; } -const ContentPanelActions: React.SFC = ({ actions }) => ( +const ContentPanelActions: React.FC = ({ actions }) => ( {actions.map(({ text, buttonProps = {}, flexItemProps = {}, modal = null, children }, index) => { let button = children ? ( diff --git a/public/components/CreatePolicyModal/CreatePolicyModal.tsx b/public/components/CreatePolicyModal/CreatePolicyModal.tsx index 9838b179d..c7a54c5ca 100644 --- a/public/components/CreatePolicyModal/CreatePolicyModal.tsx +++ b/public/components/CreatePolicyModal/CreatePolicyModal.tsx @@ -27,7 +27,7 @@ interface CreatePolicyModalProps { onClickContinue: (visual: boolean) => void; } -const CreatePolicyModal: React.SFC = ({ isEdit = false, onClose, onClickContinue }) => { +const CreatePolicyModal: React.FC = ({ isEdit = false, onClose, onClickContinue }) => { const [visual, setVisual] = useState(true); return ( diff --git a/public/components/EuiToolTipWrapper/index.tsx b/public/components/EuiToolTipWrapper/index.tsx index fd651068c..55bacc4cd 100644 --- a/public/components/EuiToolTipWrapper/index.tsx +++ b/public/components/EuiToolTipWrapper/index.tsx @@ -12,10 +12,10 @@ interface IEuiToolTipWrapperOptions { export interface IEuiToolTipWrapperProps { disabledReason?: | string - | { + | Array<{ visible: boolean; message: string; - }[]; + }>; } export default function EuiToolTipWrapper( diff --git a/public/components/FilterGroup/FilterGroup.test.tsx b/public/components/FilterGroup/FilterGroup.test.tsx index 7e0c63059..5ab92a622 100644 --- a/public/components/FilterGroup/FilterGroup.test.tsx +++ b/public/components/FilterGroup/FilterGroup.test.tsx @@ -5,8 +5,8 @@ import React, { useState } from "react"; import { act, render, waitFor } from "@testing-library/react"; -import FilterGroup, { IFilterGroupProps } from "./index"; import userEvent from "@testing-library/user-event"; +import FilterGroup, { IFilterGroupProps } from "./index"; const WrappedComponent = (props: IFilterGroupProps) => { const [value, onChange] = useState(); diff --git a/public/components/FilterGroup/index.tsx b/public/components/FilterGroup/index.tsx index 858d83775..0adede416 100644 --- a/public/components/FilterGroup/index.tsx +++ b/public/components/FilterGroup/index.tsx @@ -1,8 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + import { EuiFilterButton, EuiFilterButtonProps, EuiFilterGroup, EuiFilterSelectItem, EuiPopover } from "@elastic/eui"; import React, { useState } from "react"; export interface IFilterGroupProps { - options: { label: string }[]; + options: Array<{ label: string }>; value?: string[]; filterButtonProps?: EuiFilterButtonProps; onChange?: (val: IFilterGroupProps["value"]) => void; diff --git a/public/components/FormGenerator/built_in_components/index.tsx b/public/components/FormGenerator/built_in_components/index.tsx index c1e3eb3a9..15ec59ced 100644 --- a/public/components/FormGenerator/built_in_components/index.tsx +++ b/public/components/FormGenerator/built_in_components/index.tsx @@ -74,13 +74,13 @@ const componentMap: Record { - const allOptions = (options as { label: string; options?: { label: string }[] }[]).reduce((total, current) => { + const allOptions = (options as Array<{ label: string; options?: Array<{ label: string }> }>).reduce((total, current) => { if (current.options) { return [...total, ...current.options]; } else { return [...total, current]; } - }, [] as { label: string }[]); + }, [] as Array<{ label: string }>); const findItem = allOptions.find((item: { label: string }) => item.label === searchValue); if (findItem) { onChange(searchValue); @@ -111,8 +111,8 @@ const componentMap: Record[]; - onChange: (val: string[], values: EuiComboBoxOptionOption[], ...args: any) => void; + options: Array>; + onChange: (val: string[], values: Array>, ...args: any) => void; }, ref: React.Ref ) => { @@ -125,7 +125,7 @@ const componentMap: Record[]); + }, [] as Array>); const findItem = allOptions.find((item: { label: string }) => item.label === searchValue); if (findItem) { onChange( @@ -147,7 +147,7 @@ const componentMap: Record others.options.find((option) => option.value === item) || { label: item, value: item }) - .filter((item) => item !== undefined) as EuiComboBoxOptionOption[] + .filter((item) => item !== undefined) as Array> } /> ); diff --git a/public/components/FormGenerator/index.tsx b/public/components/FormGenerator/index.tsx index 2c6e449cf..0a7188603 100644 --- a/public/components/FormGenerator/index.tsx +++ b/public/components/FormGenerator/index.tsx @@ -44,7 +44,7 @@ export interface IFormGeneratorProps { onChange?: (totalValue: IFormGeneratorProps["value"], key?: FieldName, value?: any) => void; } -export interface IFormGeneratorRef extends FieldInstance {} +export type IFormGeneratorRef = FieldInstance; export { AllBuiltInComponents }; @@ -57,10 +57,10 @@ function FormGenerator(props: IFormGeneratorProps, re const field = useField({ ...fieldProps, onChange(name: FieldName, value: any) { - propsRef.current.onChange && propsRef.current.onChange({ ...field.getValues() }, name, value); + if (propsRef.current.onChange) propsRef.current.onChange({ ...field.getValues() }, name, value); }, }); - const errorMessage: Record = field.getErrors(); + const errorMessage: Record = field.getErrors(); useImperativeHandle(ref, () => ({ ...field, validatePromise: async () => { @@ -77,15 +77,19 @@ function FormGenerator(props: IFormGeneratorProps, re return result; }, })); - useEffect(() => { - if (!isEqual(field.getValues(), props.value)) { - if (propsRef.current.resetValuesWhenPropsValueChange) { - field.resetValues(props.value as T); - } else { - field.setValues(props.value as T); + useEffect( + () => { + if (!isEqual(field.getValues(), props.value)) { + if (propsRef.current.resetValuesWhenPropsValueChange) { + field.resetValues(props.value as T); + } else { + field.setValues(props.value as T); + } } - } - }, [props.value]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [props.value] + ); const formattedFormFields = useMemo(() => { return formFields.map((item) => { const { rules } = item.options || {}; @@ -117,14 +121,18 @@ function FormGenerator(props: IFormGeneratorProps, re }); }, [formFields, field]); - const finalValue = useMemo(() => { - const value = field.getValues(); - if (!blockedNameList) { - return field.getValues(); - } + const finalValue = useMemo( + () => { + const value = field.getValues(); + if (!blockedNameList) { + return field.getValues(); + } - return omit(value, blockedNameList); - }, [field.getValues(), blockedNameList]); + return omit(value, blockedNameList); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [field.getValues(), blockedNameList] + ); return ( @@ -175,7 +183,7 @@ function FormGenerator(props: IFormGeneratorProps, re if (!isEqual(val, finalValue)) { field.validatePromise(); } - propsRef.current.onChange && propsRef.current.onChange(field.getValues(), undefined, editorValue); + if (propsRef.current.onChange) propsRef.current.onChange(field.getValues(), undefined, editorValue); }} /> diff --git a/public/components/IndexDetail/IndexDetail.test.tsx b/public/components/IndexDetail/IndexDetail.test.tsx index 70b6b1d29..dfc151b28 100644 --- a/public/components/IndexDetail/IndexDetail.test.tsx +++ b/public/components/IndexDetail/IndexDetail.test.tsx @@ -6,8 +6,8 @@ import React, { useRef, forwardRef, useState } from "react"; import { render, waitFor } from "@testing-library/react"; import { renderHook } from "@testing-library/react-hooks"; -import IndexDetail, { IIndexDetailRef, IndexDetailProps } from "./IndexDetail"; import userEvent from "@testing-library/user-event"; +import IndexDetail, { IIndexDetailRef, IndexDetailProps } from "./IndexDetail"; const IndexDetailOnChangeWrapper = forwardRef((props: Omit, ref: any) => { const [value, setValue] = useState(props.value as any); diff --git a/public/components/IndexDetail/IndexDetail.tsx b/public/components/IndexDetail/IndexDetail.tsx index 01bc32138..a4b21fbdc 100644 --- a/public/components/IndexDetail/IndexDetail.tsx +++ b/public/components/IndexDetail/IndexDetail.tsx @@ -123,13 +123,14 @@ const IndexDetail = ( const hasEdit = useRef(false); const onValueChange = useCallback( (name: string | string[], val) => { - let finalValue = valueRef.current || {}; + const finalValue = valueRef.current || {}; set(finalValue, name, val); onChange({ ...finalValue }); if (name !== "index") { hasEdit.current = true; } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [onChange, value] ); const destroyRef = useRef(false); @@ -139,68 +140,72 @@ const IndexDetail = ( const aliasesRef = useRef(null); const settingsRef = useRef(null); const mappingsRef = useRef(null); - const onIndexInputBlur = useCallback(async () => { - await new Promise((resolve) => setTimeout(resolve, 200)); - if (destroyRef.current) { - return; - } - if (finalValue.index && onSimulateIndexTemplate) { - setTemplateSimulateLoading(true); - const result = await onSimulateIndexTemplate(finalValue.index); + const onIndexInputBlur = useCallback( + async () => { + await new Promise((resolve) => setTimeout(resolve, 200)); if (destroyRef.current) { return; } - setTemplateSimulateLoading(false); - if (result && result.ok) { - let onChangePromise: Promise; - if (hasEdit.current) { - onChangePromise = new Promise((resolve) => { - Modal.show({ - title: "Merge your changes with templates?", - content: - "The index name matches one or more index templates. Index aliases, settings, and mappings are inherited from matching templates. Do you want to merge your changes with templates?", - locale: { - confirm: "Merge with templates", - cancel: "Overwrite by templates", - }, - footer: ["cancel", "confirm"], - type: "confirm", - "data-test-subj": "simulate-confirm", - onCancel: () => resolve(result.response), - onOk: () => { - const formatValue: IndexItemRemote = { - index: "", - ...finalValue, - mappings: { - properties: transformArrayToObject(finalValue.mappings?.properties || []), - }, - }; - const mergedValue: IndexItemRemote = { - index: finalValue.index || "", - }; - merge(mergedValue, result.response, formatValue); - resolve(mergedValue); + if (finalValue.index && onSimulateIndexTemplate) { + setTemplateSimulateLoading(true); + const result = await onSimulateIndexTemplate(finalValue.index); + if (destroyRef.current) { + return; + } + setTemplateSimulateLoading(false); + if (result && result.ok) { + let onChangePromise: Promise; + if (hasEdit.current) { + onChangePromise = new Promise((resolve) => { + Modal.show({ + title: "Merge your changes with templates?", + content: + "The index name matches one or more index templates. Index aliases, settings, and mappings are inherited from matching templates. Do you want to merge your changes with templates?", + locale: { + confirm: "Merge with templates", + cancel: "Overwrite by templates", + }, + footer: ["cancel", "confirm"], + type: "confirm", + "data-test-subj": "simulate-confirm", + onCancel: () => resolve(result.response), + onOk: () => { + const formatValue: IndexItemRemote = { + index: "", + ...finalValue, + mappings: { + properties: transformArrayToObject(finalValue.mappings?.properties || []), + }, + }; + const mergedValue: IndexItemRemote = { + index: finalValue.index || "", + }; + merge(mergedValue, result.response, formatValue); + resolve(mergedValue); + }, + }); + }); + } else { + onChangePromise = Promise.resolve(result.response); + } + onChangePromise.then((data) => { + onChange({ + ...data, + mappings: { + properties: transformObjectToArray(data?.mappings?.properties || {}), }, }); + hasEdit.current = false; + setIsMatchingTemplate(true); }); } else { - onChangePromise = Promise.resolve(result.response); + setIsMatchingTemplate(false); } - onChangePromise.then((data) => { - onChange({ - ...data, - mappings: { - properties: transformObjectToArray(data?.mappings?.properties || {}), - }, - }); - hasEdit.current = false; - setIsMatchingTemplate(true); - }); - } else { - setIsMatchingTemplate(false); } - } - }, [finalValue.index, onSimulateIndexTemplate]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [finalValue.index, onSimulateIndexTemplate] + ); const onImportSettings = async ({ index }: { index: string }) => { if (onGetIndexDetail) { const indexDetail: IndexItemRemote = await new Promise((resolve) => { @@ -237,102 +242,106 @@ const IndexDetail = ( useImperativeHandle(ref, () => ({ validate: async () => { const result = await Promise.all([ - aliasesRef.current?.validatePromise().then((result) => result.errors), + aliasesRef.current?.validatePromise().then((aliasResult) => aliasResult.errors), mappingsRef.current?.validate(), - settingsRef.current?.validatePromise().then((result) => result.errors), + settingsRef.current?.validatePromise().then((settingsResult) => settingsResult.errors), ]); return result.every((item) => !item); }, - hasUnsavedChanges: (mode: IndicesUpdateMode) => diffJson(oldValue?.[mode], finalValue[mode]), + hasUnsavedChanges: (indMode: IndicesUpdateMode) => diffJson(oldValue?.[indMode], finalValue[indMode]), getMappingsJSONEditorValue: () => mappingsRef.current?.getJSONEditorValue() || "", simulateFromTemplate: onIndexInputBlur, importSettings: onImportSettings, })); - const formFields: IField[] = useMemo(() => { - return [ - { - rowProps: { - label: "Number of primary shards", - helpText: ( - <> -
Specify the number of primary shards for the index. Default is 1.
-
The number of primary shards cannot be changed after the index is created.
- - ), - direction: isEdit ? "hoz" : "ver", - }, - name: "index.number_of_shards", - type: readonly || (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_shards")) ? "Text" : "Number", - options: { - rules: [ - { - min: 1, - message: "Number of primary shards cannot be smaller than 1.", - }, - { - validator(rule, value, values) { - if (Number(value) !== parseInt(value)) { - return Promise.reject("Number of primary shards must be an integer."); - } + const formFields: IField[] = useMemo( + () => { + return [ + { + rowProps: { + label: "Number of primary shards", + helpText: ( + <> +
Specify the number of primary shards for the index. Default is 1.
+
The number of primary shards cannot be changed after the index is created.
+ + ), + direction: isEdit ? "hoz" : "ver", + }, + name: "index.number_of_shards", + type: readonly || (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_shards")) ? "Text" : "Number", + options: { + rules: [ + { + min: 1, + message: "Number of primary shards cannot be smaller than 1.", + }, + { + validator(rule, v, values) { + if (Number(v) !== parseInt(v, 10)) { + return Promise.reject("Number of primary shards must be an integer."); + } - return Promise.resolve(); + return Promise.resolve(); + }, }, + ], + props: { + placeholder: "Specify primary shard count.", + removeWhenEmpty: true, }, - ], - props: { - placeholder: "Specify primary shard count.", - removeWhenEmpty: true, }, }, - }, - { - rowProps: { - label: "Number of replicas", - helpText: REPLICA_NUMBER_MESSAGE, - direction: isEdit ? "hoz" : "ver", - }, - name: "index.number_of_replicas", - type: readonly || (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_replicas")) ? "Text" : "Number", - options: { - rules: [ - { - min: 0, - message: "Number of replicas cannot be smaller than 0.", - }, - { - validator(rule, value, values) { - if (Number(value) !== parseInt(value)) { - return Promise.reject("Number of replicas must be an integer."); - } + { + rowProps: { + label: "Number of replicas", + helpText: REPLICA_NUMBER_MESSAGE, + direction: isEdit ? "hoz" : "ver", + }, + name: "index.number_of_replicas", + type: readonly || (isEdit && !INDEX_DYNAMIC_SETTINGS.includes("index.number_of_replicas")) ? "Text" : "Number", + options: { + rules: [ + { + min: 0, + message: "Number of replicas cannot be smaller than 0.", + }, + { + validator(rule, v, values) { + if (Number(v) !== parseInt(v, 10)) { + return Promise.reject("Number of replicas must be an integer."); + } - return Promise.resolve(); + return Promise.resolve(); + }, }, + ], + props: { + placeholder: "Specify number of replicas.", + removeWhenEmpty: true, }, - ], - props: { - placeholder: "Specify number of replicas.", - removeWhenEmpty: true, }, }, - }, - { - rowProps: { - label: "Refresh interval", - helpText: - "Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is 1 second.", - direction: isEdit ? "hoz" : "ver", - }, - name: "index.refresh_interval", - type: readonly ? "Text" : "Input", - options: { - props: { - placeholder: "Can be set to -1 to disable refreshing.", - removeWhenEmpty: true, + { + rowProps: { + label: "Refresh interval", + helpText: + "Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is 1 second.", + direction: isEdit ? "hoz" : "ver", + }, + name: "index.refresh_interval", + type: readonly ? "Text" : "Input", + options: { + props: { + placeholder: "Can be set to -1 to disable refreshing.", + removeWhenEmpty: true, + }, }, }, - }, - ] as IField[]; - }, [isEdit, finalValue.index, templateSimulateLoading]); + ] as IField[]; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [isEdit, finalValue.index, templateSimulateLoading] + ); useEffect(() => { return () => { destroyRef.current = true; @@ -395,7 +404,7 @@ const IndexDetail = ( }, options: { props: { - refreshOptions: refreshOptions, + refreshOptions, }, rules: [...ALIAS_SELECT_RULE], }, diff --git a/public/components/IndexMapping/IndexMapping.test.tsx b/public/components/IndexMapping/IndexMapping.test.tsx index fc94f7a2a..ab718c94d 100644 --- a/public/components/IndexMapping/IndexMapping.test.tsx +++ b/public/components/IndexMapping/IndexMapping.test.tsx @@ -6,9 +6,9 @@ import React, { forwardRef, Ref, useRef, useState } from "react"; import { render, fireEvent, waitFor, act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { renderHook } from "@testing-library/react-hooks"; import IndexMapping, { IIndexMappingsRef, IndexMappingProps, transformObjectToArray } from "./IndexMapping"; import { MappingsProperties } from "../../../models/interfaces"; -import { renderHook } from "@testing-library/react-hooks"; const IndexMappingOnChangeWrapper = forwardRef((props: Partial, ref: Ref) => { const [value, setValue] = useState(props.value as any); diff --git a/public/components/IndexMapping/IndexMapping.tsx b/public/components/IndexMapping/IndexMapping.tsx index edc775177..8a981b7ef 100644 --- a/public/components/IndexMapping/IndexMapping.tsx +++ b/public/components/IndexMapping/IndexMapping.tsx @@ -23,7 +23,9 @@ const IndexMapping = ( { value: propsValue, onChange: propsOnChange, isEdit, oldValue, readonly, docVersion }: IndexMappingProps, ref: Ref ) => { + // eslint-disable-next-line react-hooks/exhaustive-deps const value = propsValue?.properties || []; + // eslint-disable-next-line react-hooks/exhaustive-deps const onChange = (val: MappingsProperties) => { propsOnChange({ ...propsValue, @@ -79,7 +81,7 @@ const IndexMapping = ( let isFirstEditableField = false; return (formValue || []).map((item, index) => { const { fieldName, ...fieldSettings } = item; - const id = [pos, index].filter((item) => item !== "").join(".properties."); + const id = [pos, index].filter((itm) => itm !== "").join(".properties."); const readonlyFlag = readonly || (isEdit && !!get(oldValue?.properties, id)); let shouldShowLabel = false; if (!readonlyFlag && !isFirstEditableField) { @@ -90,9 +92,9 @@ const IndexMapping = ( label: ( { - if (ref) { - allFieldsRef.current[id] = ref; + ref={(r) => { + if (r) { + allFieldsRef.current[id] = r; } else { delete allFieldsRef.current[id]; } @@ -100,12 +102,12 @@ const IndexMapping = ( readonly={readonlyFlag} value={item} id={`mapping-visual-editor-${id}`} - onFieldNameCheck={(fieldName) => { + onFieldNameCheck={(innerFieldName) => { const hasDuplicateName = (formValue || []) .filter((sibItem, sibIndex) => sibIndex < index) - .some((sibItem) => sibItem.fieldName === fieldName); + .some((sibItem) => sibItem.fieldName === innerFieldName); if (hasDuplicateName) { - return `Duplicate field name [${fieldName}], please change your field name`; + return `Duplicate field name [${innerFieldName}], please change your field name`; } return ""; @@ -133,6 +135,7 @@ const IndexMapping = ( iconWhenExpanded: , }; if (fieldSettings.properties) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (payload.icon = ), (payload.iconWhenExpanded = ), (payload.children = transformValueToTreeItems(fieldSettings.properties, id)); @@ -141,6 +144,7 @@ const IndexMapping = ( return payload; }); }; + // eslint-disable-next-line react-hooks/exhaustive-deps const transformedTreeItems = useMemo(() => transformValueToTreeItems(value), [value]); const newValue = useMemo(() => { const oldValueKeys = (oldValue?.properties || []).map((item) => item.fieldName); diff --git a/public/components/IndexMapping/interfaces.ts b/public/components/IndexMapping/interfaces.ts index 02fb4f019..8028b4aae 100644 --- a/public/components/IndexMapping/interfaces.ts +++ b/public/components/IndexMapping/interfaces.ts @@ -4,15 +4,15 @@ */ import { MappingsProperties, MappingsPropertiesObject } from "../../../models/interfaces"; -export type IndexMappingsAll = { +export interface IndexMappingsAll { properties?: MappingsProperties; [key: string]: any; -}; +} -export type IndexMappingsObjectAll = { +export interface IndexMappingsObjectAll { properties?: MappingsPropertiesObject; [key: string]: any; -}; +} export interface IndexMappingProps { value?: IndexMappingsAll; diff --git a/public/components/JSONDiffEditor/JSONDiffEditor.tsx b/public/components/JSONDiffEditor/JSONDiffEditor.tsx index f89ee3cd9..0f68e2ab3 100644 --- a/public/components/JSONDiffEditor/JSONDiffEditor.tsx +++ b/public/components/JSONDiffEditor/JSONDiffEditor.tsx @@ -24,12 +24,12 @@ const JSONDiffEditor = forwardRef(({ value, onChange, ...others }: JSONDiffEdito return; } try { - const value = editorRef.current?.getModifiedEditor().getValue(); - if (!value) { + const v = editorRef.current?.getModifiedEditor().getValue(); + if (!v) { throw new Error("Value cannot be empty"); } - JSON.parse(value); - onChange && onChange(value); + JSON.parse(v); + if (onChange) onChange(v); } catch (e) { setConfirmModalVisible(true); } @@ -50,6 +50,7 @@ const JSONDiffEditor = forwardRef(({ value, onChange, ...others }: JSONDiffEdito } } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [setEditorValue, isReady, inputRef.current] ); const valueRef = useRef(editorValue); @@ -64,7 +65,9 @@ const JSONDiffEditor = forwardRef(({ value, onChange, ...others }: JSONDiffEdito editorRef.current?.getDomNode().addEventListener("click", onClickContainer.current); editorRef.current?.getModifiedEditor().getDomNode()?.setAttribute("data-test-subj", "codeEditorContainer"); return () => { + // eslint-disable-next-line react-hooks/exhaustive-deps document.body.removeEventListener("click", onClickOutsideHandler.current); + // eslint-disable-next-line react-hooks/exhaustive-deps editorRef.current?.getDomNode().removeEventListener("click", onClickContainer.current); }; }, [isReady]); @@ -92,8 +95,8 @@ const JSONDiffEditor = forwardRef(({ value, onChange, ...others }: JSONDiffEdito onChange={(e) => { try { JSON.parse(e.target.value); - onChange && onChange(e.target.value); - } catch (e) { + if (onChange) onChange(e.target.value); + } catch (err) { // do nothing } }} diff --git a/public/components/JSONDiffEditor/JSONTextArea.tsx b/public/components/JSONDiffEditor/JSONTextArea.tsx index 9158885f5..e9392c16a 100644 --- a/public/components/JSONDiffEditor/JSONTextArea.tsx +++ b/public/components/JSONDiffEditor/JSONTextArea.tsx @@ -52,8 +52,8 @@ const JSONDiffEditor = forwardRef(({ value, onChange, ...others }: JSONDiffEdito onBlur={(e) => { try { JSON.parse(e.target.value); - onChange && onChange(e.target.value); - } catch (e) { + if (onChange) onChange(e.target.value); + } catch (err) { // do nothing setConfirmModalVisible(true); } diff --git a/public/components/JSONEditor/JSONEditor.test.tsx b/public/components/JSONEditor/JSONEditor.test.tsx index 7d015727a..d54a7856b 100644 --- a/public/components/JSONEditor/JSONEditor.test.tsx +++ b/public/components/JSONEditor/JSONEditor.test.tsx @@ -6,8 +6,8 @@ import React, { useRef } from "react"; import "@testing-library/jest-dom/extend-expect"; import { fireEvent, render, waitFor } from "@testing-library/react"; -import JSONEditor, { IJSONEditorRef } from "./JSONEditor"; import { renderHook } from "@testing-library/react-hooks"; +import JSONEditor, { IJSONEditorRef } from "./JSONEditor"; async function inputTextArea(props: { textareaInput: HTMLTextAreaElement; nowValue: string; newValue: string }) { const { textareaInput, nowValue, newValue } = props; diff --git a/public/components/JSONEditor/JSONEditor.tsx b/public/components/JSONEditor/JSONEditor.tsx index 2599c3918..bc2cd5443 100644 --- a/public/components/JSONEditor/JSONEditor.tsx +++ b/public/components/JSONEditor/JSONEditor.tsx @@ -67,7 +67,7 @@ const JSONEditor = forwardRef(({ value, onChange, disabled, ...others }: JSONEdi } try { JSON.parse(tempEditorValue); - onChange && onChange(tempEditorValue); + if (onChange) onChange(tempEditorValue); setConfirmModalVisible(false); } catch (e) { setConfirmModalVisible(true); diff --git a/public/components/JSONModal/JSONModal.tsx b/public/components/JSONModal/JSONModal.tsx index 5013e89e1..961fd57ea 100644 --- a/public/components/JSONModal/JSONModal.tsx +++ b/public/components/JSONModal/JSONModal.tsx @@ -21,7 +21,7 @@ interface JSONModalProps { onClose: () => void; } -const JSONModal: React.SFC = ({ title, json, onClose }) => { +const JSONModal: React.FC = ({ title, json, onClose }) => { return ( diff --git a/public/components/MappingLabel/MappingLabel.tsx b/public/components/MappingLabel/MappingLabel.tsx index 63e718b68..cf9959f3c 100644 --- a/public/components/MappingLabel/MappingLabel.tsx +++ b/public/components/MappingLabel/MappingLabel.tsx @@ -2,6 +2,9 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ + +/* eslint-disable jsx-a11y/click-events-have-key-events */ + import React, { forwardRef, useCallback, useRef, useImperativeHandle } from "react"; import { EuiIcon, @@ -15,12 +18,12 @@ import { EuiFormRowProps, } from "@elastic/eui"; import { set, pick, isEqual } from "lodash"; +import { useEffect } from "react"; import { MappingsProperties } from "../../../models/interfaces"; import { AllBuiltInComponents } from "../FormGenerator"; import useField, { transformNameToString } from "../../lib/field"; import { INDEX_MAPPING_TYPES, INDEX_MAPPING_TYPES_WITH_CHILDREN } from "../../utils/constants"; import SimplePopover from "../SimplePopover"; -import { useEffect } from "react"; interface IMappingLabel { value: MappingsProperties[number]; @@ -72,6 +75,7 @@ export const MappingLabel = forwardRef((props: IMappingLabel, forwardedRef: Reac } return propsRef.current.onChange(newValue, k, v); }, + // eslint-disable-next-line react-hooks/exhaustive-deps [propsRef.current.value, propsRef.current.onChange] ); const field = useField({ @@ -82,11 +86,15 @@ export const MappingLabel = forwardRef((props: IMappingLabel, forwardedRef: Reac onChange: onFieldChange, unmountComponent: true, }); - useEffect(() => { - if (!isEqual(propsRef.current.value, field.getValues())) { - field.resetValues(propsRef.current.value); - } - }, [propsRef.current.value]); + useEffect( + () => { + if (!isEqual(propsRef.current.value, field.getValues())) { + field.resetValues(propsRef.current.value); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [propsRef.current.value] + ); const value = field.getValues(); const type = value.type; useImperativeHandle(forwardedRef, () => ({ @@ -143,8 +151,8 @@ export const MappingLabel = forwardRef((props: IMappingLabel, forwardedRef: Reac message: "Field name is required, please input", }, { - validator: (rule, value) => { - const checkResult = onFieldNameCheck(value); + validator: (rule, v) => { + const checkResult = onFieldNameCheck(v); if (checkResult) { return Promise.reject(checkResult); } @@ -182,6 +190,7 @@ export const MappingLabel = forwardRef((props: IMappingLabel, forwardedRef: Reac {moreFields.map((item) => { + // eslint-disable-next-line no-shadow const { label, type, ...others } = item; const RenderComponent = AllBuiltInComponents[type]; return ( diff --git a/public/components/Modal/Modal.test.tsx b/public/components/Modal/Modal.test.tsx index 4367cff61..563bc7a6a 100644 --- a/public/components/Modal/Modal.test.tsx +++ b/public/components/Modal/Modal.test.tsx @@ -16,7 +16,7 @@ describe(" spec", () => { const { container } = render( - {services => services && } + {(services) => services && } ); @@ -34,7 +34,7 @@ describe(" spec", () => {
- {services => services && } + {(services) => services && } {({ onShow }) => ( onShow(Modal, { text: "interesting text" })}> diff --git a/public/components/Modal/ModalRoot.tsx b/public/components/Modal/ModalRoot.tsx index fc6b1e9a0..cb1cf05b3 100644 --- a/public/components/Modal/ModalRoot.tsx +++ b/public/components/Modal/ModalRoot.tsx @@ -12,7 +12,7 @@ interface ModalRootProps { } // All modals will have access to the BrowserServices if they need it -const ModalRoot: React.SFC = ({ services }) => ( +const ModalRoot: React.FC = ({ services }) => ( {({ component: Komponent, diff --git a/public/components/MonacoJSONEditor/MonacoJSONEditor.tsx b/public/components/MonacoJSONEditor/MonacoJSONEditor.tsx index 43901c52c..978025dc4 100644 --- a/public/components/MonacoJSONEditor/MonacoJSONEditor.tsx +++ b/public/components/MonacoJSONEditor/MonacoJSONEditor.tsx @@ -28,13 +28,13 @@ const MonacoJSONEditor = forwardRef( return; } try { - const value = editorRef.current?.getValue(); - if (!value) { + const v = editorRef.current?.getValue(); + if (!v) { throw new Error("Value can not be empty"); } - JSON.parse(value); + JSON.parse(v); setConfirmModalVisible(false); - onChange && onChange(value); + if (onChange) onChange(v); } catch (e) { setConfirmModalVisible(true); } @@ -49,6 +49,7 @@ const MonacoJSONEditor = forwardRef( } } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [setEditorValue, isReady, inputRef.current] ); const valueRef = useRef(editorValue); @@ -73,6 +74,7 @@ const MonacoJSONEditor = forwardRef( useEffect(() => { return () => { + // eslint-disable-next-line react-hooks/exhaustive-deps onClickOutsideHandler.current(); }; }, []); @@ -100,8 +102,8 @@ const MonacoJSONEditor = forwardRef( onChange={(e) => { try { JSON.parse(e.target.value); - onChange && onChange(e.target.value); - } catch (e) { + if (onChange) onChange(e.target.value); + } catch (err) { // do nothing } }} diff --git a/public/components/MonacoJSONEditor/hooks.tsx b/public/components/MonacoJSONEditor/hooks.tsx index c202624c4..5c180deb3 100644 --- a/public/components/MonacoJSONEditor/hooks.tsx +++ b/public/components/MonacoJSONEditor/hooks.tsx @@ -1,3 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + import { monaco } from "@osd/monaco"; import { euiThemeVars } from "@osd/ui-shared-deps/theme"; import { useEffect, useRef } from "react"; @@ -25,6 +30,7 @@ export function useDiagnosticsOptions(props: { monaco?: typeof monaco; diagnosti }); } return () => { + // eslint-disable-next-line react-hooks/exhaustive-deps props.monaco?.languages.json.jsonDefaults.setDiagnosticsOptions(oldOptionsSettingsRef.current || {}); }; }, [props.monaco, props.diagnosticsOptions]); @@ -54,5 +60,6 @@ export function useModel(props: { editor?: monaco.editor.IStandaloneCodeEditor; return () => { props.editor?.getModel()?.dispose(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); } diff --git a/public/components/MonacoJSONEditor/interface.ts b/public/components/MonacoJSONEditor/interface.ts index 9920759ec..1e3f82d08 100644 --- a/public/components/MonacoJSONEditor/interface.ts +++ b/public/components/MonacoJSONEditor/interface.ts @@ -1,11 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + import { monaco } from "@osd/monaco"; import { JSONSchema4 } from "@types/json-schema"; import { JSONEditorProps } from "../JSONEditor"; export type DiagnosticsOptions = Omit & { - schemas?: (Omit["schemas"][number], "schema"> & { - schema?: JSONSchema4; - })[]; + schemas?: Array< + Omit["schemas"][number], "schema"> & { + schema?: JSONSchema4; + } + >; }; export interface MonacoJSONEditorProps extends JSONEditorProps { diff --git a/public/components/PolicyModal/PolicyModal.test.tsx b/public/components/PolicyModal/PolicyModal.test.tsx index 527aec6b9..b46957555 100644 --- a/public/components/PolicyModal/PolicyModal.test.tsx +++ b/public/components/PolicyModal/PolicyModal.test.tsx @@ -31,6 +31,7 @@ describe(" spec", () => { policy={{ policy: { name: "policy" } }} // replace with random policy w/ seed onClose={() => {}} onEdit={() => { + // eslint-disable-next-line no-empty { } }} diff --git a/public/components/PolicyModal/PolicyModal.tsx b/public/components/PolicyModal/PolicyModal.tsx index 5d1798c72..b967b572a 100644 --- a/public/components/PolicyModal/PolicyModal.tsx +++ b/public/components/PolicyModal/PolicyModal.tsx @@ -28,7 +28,7 @@ interface PolicyModalProps { onEdit: (visual: boolean) => void; } -const PolicyModal: React.SFC = ({ policyId, policy, errorMessage, onClose, onEdit }) => { +const PolicyModal: React.FC = ({ policyId, policy, errorMessage, onClose, onEdit }) => { const policyString = JSON.stringify(policy, null, 4); return ( diff --git a/public/components/RemoteSelect/RemoteSelect.test.tsx b/public/components/RemoteSelect/RemoteSelect.test.tsx index e30ae7e30..8facc2b73 100644 --- a/public/components/RemoteSelect/RemoteSelect.test.tsx +++ b/public/components/RemoteSelect/RemoteSelect.test.tsx @@ -5,8 +5,8 @@ import React, { useState } from "react"; import { render, waitFor } from "@testing-library/react"; -import RemoteSelect, { RemoteSelectProps } from "./index"; import userEvent from "@testing-library/user-event"; +import RemoteSelect, { RemoteSelectProps } from "./index"; const onChangeMock = jest.fn(); diff --git a/public/components/RemoteSelect/index.tsx b/public/components/RemoteSelect/index.tsx index 55c18369b..9afd998fc 100644 --- a/public/components/RemoteSelect/index.tsx +++ b/public/components/RemoteSelect/index.tsx @@ -12,13 +12,13 @@ import { ServerResponse } from "../../../server/models/types"; export interface RemoteSelectProps extends Omit, "value" | "onChange"> { value?: string[]; onChange?: (value: Required["value"]) => void; - onOptionsChange?: (options: { label: string; [key: string]: any }[]) => void; - refreshOptions: (params: { searchValue?: string }) => Promise>; + onOptionsChange?: (options: Array<{ label: string; [key: string]: any }>) => void; + refreshOptions: (params: { searchValue?: string }) => Promise>>; } const RemoteSelect = forwardRef((props: RemoteSelectProps, ref: React.Ref) => { const { value = [], onChange, refreshOptions: refreshOptionsFromProps, onOptionsChange, ...others } = props; - const [allOptions, setAllOptions] = useState([] as { label: string }[]); + const [allOptions, setAllOptions] = useState([] as Array<{ label: string }>); const [isLoading, setIsLoading] = useState(false); const destroyRef = useRef(false); const refreshOptionsWithoutDebounce = useCallback( @@ -28,7 +28,7 @@ const RemoteSelect = forwardRef((props: RemoteSelectProps, ref: React.Ref) => { + .then((res: ServerResponse>) => { if (destroyRef.current) { return; } @@ -47,17 +47,26 @@ const RemoteSelect = forwardRef((props: RemoteSelectProps, ref: React.Ref { - refreshOptionsWithoutDebounce({ searchValue: "" }); - return () => { - destroyRef.current = true; - }; - }, []); - useEffect(() => { - onOptionsChange && onOptionsChange(allOptions); - }, [allOptions]); - const onCreateOption = (searchValue: string, flattenedOptions: { label: string }[] = []) => { + useEffect( + () => { + refreshOptionsWithoutDebounce({ searchValue: "" }); + return () => { + destroyRef.current = true; + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + useEffect( + () => { + if (onOptionsChange) onOptionsChange(allOptions); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [allOptions] + ); + const onCreateOption = (searchValue: string, flattenedOptions: Array<{ label: string }> = []) => { const normalizedSearchValue = searchValue.trim().toLowerCase(); if (!normalizedSearchValue) { @@ -71,7 +80,7 @@ const RemoteSelect = forwardRef((props: RemoteSelectProps, ref: React.Ref option.label.trim().toLowerCase() === normalizedSearchValue) === -1) { setAllOptions([...allOptions, newOption]); - onChange && onChange([...value, newOption.label]); + if (onChange) onChange([...value, newOption.label]); } }; return ( @@ -80,8 +89,8 @@ const RemoteSelect = forwardRef((props: RemoteSelectProps, ref: React.Ref void} selectedOptions={value?.map((item) => ({ label: item }))} - onChange={(value) => { - onChange && onChange(value.map((item) => item.label)); + onChange={(v) => { + if (onChange) onChange(v.map((item) => item.label)); }} options={allOptions} isLoading={isLoading} diff --git a/public/components/SimplePopover/SimplePopover.test.tsx b/public/components/SimplePopover/SimplePopover.test.tsx index eab359d28..1a82f6642 100644 --- a/public/components/SimplePopover/SimplePopover.test.tsx +++ b/public/components/SimplePopover/SimplePopover.test.tsx @@ -5,8 +5,8 @@ import React from "react"; import { render, waitFor } from "@testing-library/react"; -import SimplePopover, { loopToGetPath } from "./SimplePopover"; import userEvent from "@testing-library/user-event"; +import SimplePopover, { loopToGetPath } from "./SimplePopover"; describe(" spec", () => { it("renders the component", () => { diff --git a/public/components/SimplePopover/SimplePopover.tsx b/public/components/SimplePopover/SimplePopover.tsx index 8c0b741a6..b42b723ce 100644 --- a/public/components/SimplePopover/SimplePopover.tsx +++ b/public/components/SimplePopover/SimplePopover.tsx @@ -44,15 +44,20 @@ const SimplePopover: React.FC = (props) => { }; } - const outsideClick = useCallback(() => { - setTimeout(() => { - if (destroyRef.current) { - return; - } - setPopVisible(false); - }, 0); - }, [popVisible, setPopVisible]); + const outsideClick = useCallback( + () => { + setTimeout(() => { + if (destroyRef.current) { + return; + } + setPopVisible(false); + }, 0); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [popVisible, setPopVisible] + ); + // eslint-disable-next-line react-hooks/exhaustive-deps const outsideHover = useCallback( throttle((e: MouseEvent) => { if (popVisible && popoverRef.current && panelRef.current) { @@ -65,14 +70,18 @@ const SimplePopover: React.FC = (props) => { [popVisible, setPopVisible] ); - useEffect(() => { - if (popVisible && triggerType === "click") { - window.addEventListener("click", outsideClick); - } - return () => { - window.removeEventListener("click", outsideClick); - }; - }, [outsideClick, triggerType]); + useEffect( + () => { + if (popVisible && triggerType === "click") { + window.addEventListener("click", outsideClick); + } + return () => { + window.removeEventListener("click", outsideClick); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [outsideClick, triggerType] + ); useEffect(() => { if (popVisible && triggerType === "hover") { diff --git a/public/components/SwitchableEditor/SwitchableEditor.test.tsx b/public/components/SwitchableEditor/SwitchableEditor.test.tsx index 1bcc0c283..fee7359a4 100644 --- a/public/components/SwitchableEditor/SwitchableEditor.test.tsx +++ b/public/components/SwitchableEditor/SwitchableEditor.test.tsx @@ -6,8 +6,8 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; import { render, waitFor } from "@testing-library/react"; -import SwitchableEditor from "./index"; import userEvent from "@testing-library/user-event"; +import SwitchableEditor from "./index"; describe(" spec", () => { it("renders the component", async () => { diff --git a/public/components/SwitchableEditor/SwitchableEditor.tsx b/public/components/SwitchableEditor/SwitchableEditor.tsx index 153dc2c95..0f5c12bce 100644 --- a/public/components/SwitchableEditor/SwitchableEditor.tsx +++ b/public/components/SwitchableEditor/SwitchableEditor.tsx @@ -14,7 +14,7 @@ export interface SwitchableEditorProps extends JSONDiffEditorProps, Pick) => { diff --git a/public/components/UnsavedChangesBottomBar/UnsavedChangesBottomBar.tsx b/public/components/UnsavedChangesBottomBar/UnsavedChangesBottomBar.tsx index d08805159..5cc6c1eb1 100644 --- a/public/components/UnsavedChangesBottomBar/UnsavedChangesBottomBar.tsx +++ b/public/components/UnsavedChangesBottomBar/UnsavedChangesBottomBar.tsx @@ -8,7 +8,7 @@ import classNames from "classnames"; import BottomBar from "../BottomBar"; import "./index.scss"; -export type CustomFormRowProps = { +export interface CustomFormRowProps { unsavedCount: number; formErrorsCount?: number; onClickCancel?: () => void; @@ -22,7 +22,7 @@ export type CustomFormRowProps = { }) => React.ReactChild; confirmButtonProps?: EuiButtonProps; cancelButtonprops?: EuiButtonEmptyProps; -}; +} export default function UnsavedChangesBottomBar(props: CustomFormRowProps) { const { unsavedCount, onClickCancel, onClickSubmit, submitButtonDataTestSubj, formErrorsCount } = props; @@ -32,9 +32,11 @@ export default function UnsavedChangesBottomBar(props: CustomFormRowProps) { setLoading(true); try { await onClickSubmit(); + // eslint-disable-next-line no-empty } catch (e) { } finally { if (destroyRef.current) { + // eslint-disable-next-line no-unsafe-finally return; } setLoading(false); @@ -53,6 +55,7 @@ export default function UnsavedChangesBottomBar(props: CustomFormRowProps) { ), + // eslint-disable-next-line react-hooks/exhaustive-deps [onClickCancel] ); @@ -73,6 +76,7 @@ export default function UnsavedChangesBottomBar(props: CustomFormRowProps) { /> ), + // eslint-disable-next-line react-hooks/exhaustive-deps [onClick, submitButtonDataTestSubj, loading] ); diff --git a/public/containers/ChannelSelect/ChannelSelect.test.tsx b/public/containers/ChannelSelect/ChannelSelect.test.tsx index abec5d7b3..3f3f367ae 100644 --- a/public/containers/ChannelSelect/ChannelSelect.test.tsx +++ b/public/containers/ChannelSelect/ChannelSelect.test.tsx @@ -6,11 +6,11 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; import { render, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import ChannelSelect, { ChannelSelectProps } from "./ChannelSelect"; import { browserServicesMock, coreServicesMock } from "../../../test/mocks"; import { ServicesContext } from "../../services"; import { CoreServicesContext } from "../../components/core_services"; -import userEvent from "@testing-library/user-event"; function renderWithServiceAndCore(props: ChannelSelectProps) { return { diff --git a/public/containers/ChannelSelect/ChannelSelect.tsx b/public/containers/ChannelSelect/ChannelSelect.tsx index 785d7d1c0..0afa3b3b5 100644 --- a/public/containers/ChannelSelect/ChannelSelect.tsx +++ b/public/containers/ChannelSelect/ChannelSelect.tsx @@ -10,8 +10,8 @@ import { AllBuiltInComponents } from "../../components/FormGenerator"; import "./index.scss"; export interface ChannelSelectProps { - value?: { id: string }[]; - onChange: (val: { id: string }[]) => void; + value?: Array<{ id: string }>; + onChange: (val: Array<{ id: string }>) => void; "data-test-subj"?: string; } @@ -26,7 +26,7 @@ const ChannelSelect = (props: ChannelSelectProps) => { placeholder="Select channel" isLoading={loading} options={channels.map((channel) => ({ value: channel.config_id, label: channel.name, className: "valid-option" }))} - onChange={(val, options: EuiComboBoxOptionOption[]) => { + onChange={(val, options: Array>) => { onChange( options.map((item) => ({ id: item.value || "", diff --git a/public/containers/ChannelSelect/hooks.tsx b/public/containers/ChannelSelect/hooks.tsx index 2332e0c5a..fe21d00c8 100644 --- a/public/containers/ChannelSelect/hooks.tsx +++ b/public/containers/ChannelSelect/hooks.tsx @@ -1,3 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + import { useContext, useState, useEffect, useRef, useCallback } from "react"; import { ServicesContext } from "../../services"; import { BrowserServices } from "../../models/interfaces"; @@ -5,7 +10,7 @@ import { FeatureChannelList, GetChannelsResponse } from "../../../server/models/ import { ServerResponse } from "../../../server/models/types"; let listenCount = 0; -let promise: Promise> | undefined = undefined; +let promise: Promise> | undefined; const LISTEN_KEY = "GET_CHANNELS_LISTEN"; const getChannels = async (props: { services: BrowserServices; force?: boolean }): Promise> => { @@ -27,36 +32,44 @@ export const useChannels = () => { const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(true); const destroyRef = useRef(false); - const refresh = useCallback((force?: boolean) => { - setLoading(true); - getChannels({ - services, - force, - }) - .then((res) => { - if (destroyRef.current) { - return; - } - if (res && res.ok) { - setChannels(res.response.channel_list); - } + const refresh = useCallback( + (force?: boolean) => { + setLoading(true); + getChannels({ + services, + force, }) - .finally(() => { - if (destroyRef.current) { - return; - } - setLoading(false); - }); - }, []); + .then((res) => { + if (destroyRef.current) { + return; + } + if (res && res.ok) { + setChannels(res.response.channel_list); + } + }) + .finally(() => { + if (destroyRef.current) { + return; + } + setLoading(false); + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); const listenHandler = useCallback(() => { refresh(); }, [refresh]); - useEffect(() => { - refresh(); - return () => { - destroyRef.current = true; - }; - }, []); + useEffect( + () => { + refresh(); + return () => { + destroyRef.current = true; + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); useEffect(() => { window.addEventListener(LISTEN_KEY, listenHandler); diff --git a/public/containers/ClearCacheModal/ClearCacheModal.test.tsx b/public/containers/ClearCacheModal/ClearCacheModal.test.tsx index 23e4973b2..2c130802a 100644 --- a/public/containers/ClearCacheModal/ClearCacheModal.test.tsx +++ b/public/containers/ClearCacheModal/ClearCacheModal.test.tsx @@ -5,16 +5,16 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; +import { CoreStart } from "opensearch-dashboards/public"; +import { render, fireEvent, waitFor } from "@testing-library/react"; +import { act } from "react-dom/test-utils"; import { browserServicesMock, coreServicesMock } from "../../../test/mocks"; import { CoreServicesContext } from "../../components/core_services"; import { ServicesContext } from "../../services"; import { BrowserServices } from "../../models/interfaces"; import { ModalProvider } from "../../components/Modal"; -import { CoreStart } from "opensearch-dashboards/public"; -import { render, fireEvent, waitFor } from "@testing-library/react"; import ClearCacheModal, { ClearCacheModalProps } from "./ClearCacheModal"; import { INDEX_OP_TARGET_TYPE } from "../../utils/constants"; -import { act } from "react-dom/test-utils"; function renderWithRouter( coreServicesContext: CoreStart | null, @@ -51,7 +51,7 @@ describe(" spec", () => { selectedItems: [], visible: true, type: INDEX_OP_TARGET_TYPE.INDEX, - onClose: onClose, + onClose, }); await waitFor(() => { expect(getByText("Cache will be cleared for all open indexes.")).toBeInTheDocument(); diff --git a/public/containers/ClearCacheModal/ClearCacheModal.tsx b/public/containers/ClearCacheModal/ClearCacheModal.tsx index 0855da739..f26c2dc61 100644 --- a/public/containers/ClearCacheModal/ClearCacheModal.tsx +++ b/public/containers/ClearCacheModal/ClearCacheModal.tsx @@ -5,12 +5,6 @@ import React, { useCallback, useContext, useEffect, useState } from "react"; import { CoreStart } from "opensearch-dashboards/public"; -import { CatIndex, DataStream } from "../../../server/models/interfaces"; -import { IAlias } from "../../pages/Aliases/interface"; -import { ServicesContext } from "../../services"; -import { CoreServicesContext } from "../../components/core_services"; -import { INDEX_OP_BLOCKS_TYPE, INDEX_OP_TARGET_TYPE } from "../../utils/constants"; -import { getErrorMessage, filterBlockedItems } from "../../utils/helpers"; import { EuiButton, EuiButtonEmpty, @@ -22,6 +16,12 @@ import { EuiModalHeaderTitle, EuiSpacer, } from "@elastic/eui"; +import { CatIndex, DataStream } from "../../../server/models/interfaces"; +import { IAlias } from "../../pages/Aliases/interface"; +import { ServicesContext } from "../../services"; +import { CoreServicesContext } from "../../components/core_services"; +import { INDEX_OP_BLOCKS_TYPE, INDEX_OP_TARGET_TYPE } from "../../utils/constants"; +import { getErrorMessage, filterBlockedItems } from "../../utils/helpers"; export interface ClearCacheModalProps { selectedItems: CatIndex[] | DataStream[] | IAlias[]; @@ -121,11 +121,11 @@ export default function ClearCacheModal(props: ClearCacheModalProps) { let toast = ""; if (unBlockedItems.length > 1) { toast = `Cache for ${unBlockedItems.length} ${type} [${unBlockedItems.join(", ")}] have been successfully cleared.`; - } else if (unBlockedItems.length == 1) { + } else if (unBlockedItems.length === 1) { toast = `Cache for ${unBlockedItems[0]} has been successfully cleared.`; } - if (!selectedItems || selectedItems.length == 0) { + if (!selectedItems || selectedItems.length === 0) { toast = "Cache for all open indexes have been successfully cleared."; } coreServices.notifications.toasts.addSuccess(toast); @@ -150,22 +150,26 @@ export default function ClearCacheModal(props: ClearCacheModalProps) { } }, [services, coreServices, onClose, unBlockedItems, selectedItems, type]); - useEffect(() => { - if (visible && selectedItems.length > 0 && selectedItems.length == blockedItems.length) { - if (selectedItems.length == 1) { - coreServices.notifications.toasts.addDanger({ - title: "Unable to clear cache", - text: `Cache cannot be cleared for ${blockedItems[0]} because it is closed or blocked.`, - }); - } else { - coreServices.notifications.toasts.addDanger({ - title: "Unable to clear cache", - text: `Cache cannot be cleared for the selected ${type} because they are closed or blocked.`, - }); + useEffect( + () => { + if (visible && selectedItems.length > 0 && selectedItems.length === blockedItems.length) { + if (selectedItems.length === 1) { + coreServices.notifications.toasts.addDanger({ + title: "Unable to clear cache", + text: `Cache cannot be cleared for ${blockedItems[0]} because it is closed or blocked.`, + }); + } else { + coreServices.notifications.toasts.addDanger({ + title: "Unable to clear cache", + text: `Cache cannot be cleared for the selected ${type} because they are closed or blocked.`, + }); + } + onClose(); } - onClose(); - } - }, [visible, unBlockedItems, blockedItems, coreServices, onClose, type]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [visible, unBlockedItems, blockedItems, coreServices, onClose, type] + ); if (!visible || loading) { return null; @@ -186,7 +190,7 @@ export default function ClearCacheModal(props: ClearCacheModalProps) { )}
-