diff --git a/shared/ui/Stream/EntityAssociator.tsx b/shared/ui/Stream/EntityAssociator.tsx index bd12c5b7b55..a8e020c1370 100644 --- a/shared/ui/Stream/EntityAssociator.tsx +++ b/shared/ui/Stream/EntityAssociator.tsx @@ -3,7 +3,7 @@ import { GetObservabilityEntitiesRequestType, WarningOrError, } from "@codestream/protocols/agent"; -import React, { PropsWithChildren, useState } from "react"; +import React, { useRef, PropsWithChildren, useEffect, useState } from "react"; import { components, OptionProps } from "react-select"; import styled from "styled-components"; @@ -16,7 +16,6 @@ import { useAppDispatch } from "../utilities/hooks"; import { WarningBox } from "./WarningBox"; import { isEmpty as _isEmpty } from "lodash"; import { DropdownWithSearch } from "./DropdownWithSearch"; -import { useResizeDetector } from "react-resize-detector"; interface EntityAssociatorProps { title?: string; @@ -66,7 +65,8 @@ export const EntityAssociator = React.memo((props: PropsWithChildren(null); const [isLoading, setIsLoading] = useState(false); const [warningOrErrors, setWarningOrErrors] = useState(undefined); - const { width: entitySearchWidth, ref: entitySearchRef } = useResizeDetector(); + const elementRef = useRef(null); + const [width, setWidth] = useState(0); async function loadEntities(search: string, _loadedOptions, additional?: AdditionalType) { const { servicesToExcludeFromSearch } = props; @@ -168,12 +168,28 @@ export const EntityAssociator = React.memo((props: PropsWithChildren { + const handleResize = () => { + if (elementRef.current) { + //@ts-ignore + const elementWidth = elementRef.current?.offsetWidth; + setWidth(elementWidth); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [elementRef]); + return ( {props.title &&

{props.title}

} {props.label &&

{props.label}

} {warningOrErrors && } -
+
diff --git a/shared/ui/Stream/RepositoryAssociatorServiceSearch.tsx b/shared/ui/Stream/RepositoryAssociatorServiceSearch.tsx index f90d570b006..04425ff4250 100644 --- a/shared/ui/Stream/RepositoryAssociatorServiceSearch.tsx +++ b/shared/ui/Stream/RepositoryAssociatorServiceSearch.tsx @@ -5,7 +5,7 @@ import { DidChangeDataNotificationType, ChangeDataType, } from "@codestream/protocols/agent"; -import React, { PropsWithChildren, useState } from "react"; +import React, { useEffect, useRef, PropsWithChildren, useState } from "react"; import { components, OptionProps } from "react-select"; import styled from "styled-components"; import { useSelector } from "react-redux"; @@ -16,7 +16,6 @@ import { Button } from "../src/components/Button"; import { NoContent } from "../src/components/Pane"; import { useAppDispatch } from "../utilities/hooks"; import { DropdownWithSearch } from "./DropdownWithSearch"; -import { useResizeDetector } from "react-resize-detector"; import { CodeStreamState } from "../store"; import { useDidMount } from "@codestream/webview/utilities/hooks"; @@ -61,7 +60,8 @@ export const RepositoryAssociatorServiceSearch = React.memo( const dispatch = useAppDispatch(); const [selected, setSelected] = useState(null); const [isLoading, setIsLoading] = useState(false); - const { width: repoSearchWidth, ref: repoSearchRef } = useResizeDetector(); + const elementRef = useRef(null); + const [width, setWidth] = useState(0); const derivedState = useSelector((state: CodeStreamState) => { return { @@ -175,6 +175,21 @@ export const RepositoryAssociatorServiceSearch = React.memo( }); }; + useEffect(() => { + const handleResize = () => { + if (elementRef.current) { + //@ts-ignore + const elementWidth = elementRef.current?.offsetWidth; + setWidth(elementWidth); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [elementRef]); + return (
@@ -184,7 +199,7 @@ export const RepositoryAssociatorServiceSearch = React.memo(
-
+
diff --git a/shared/ui/src/__tests__/Stream/Observability.test.tsx b/shared/ui/src/__tests__/Stream/Observability.test.tsx index d7c29e0f814..792593d3799 100644 --- a/shared/ui/src/__tests__/Stream/Observability.test.tsx +++ b/shared/ui/src/__tests__/Stream/Observability.test.tsx @@ -33,6 +33,9 @@ import { RemoteType, } from "@codestream/protocols/agent"; import { CSRepository, CSTeam, CSUser } from "@codestream/protocols/api"; +import { Observability } from "@codestream/webview/Stream/Observability"; +import { PaneState } from "@codestream/webview/src/components/Pane"; +import { lightTheme } from "@codestream/webview/src/themes"; import { CodeStreamState } from "@codestream/webview/store"; import { isFeatureEnabled } from "@codestream/webview/store/apiVersioning/reducer"; import { CodeErrorsState } from "@codestream/webview/store/codeErrors/types"; @@ -42,10 +45,15 @@ import * as providerSelectors from "@codestream/webview/store/providers/reducer" import { TeamsState } from "@codestream/webview/store/teams/types"; import translations from "@codestream/webview/translations/en"; import { HostApi } from "@codestream/webview/webview-api"; -import { afterEach, beforeEach, describe, jest } from "@jest/globals"; +import { afterEach, beforeEach, describe, it, jest } from "@jest/globals"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import * as React from "react"; +import { act } from "react-dom/test-utils"; import { IntlProvider } from "react-intl"; +import { Provider } from "react-redux"; +import configureStore from "redux-mock-store"; import thunk from "redux-thunk"; +import { ThemeProvider } from "styled-components"; jest.mock("@codestream/webview/store/apiVersioning/reducer"); jest.mock("@codestream/webview/webview-api"); @@ -439,63 +447,60 @@ describe("Observability", () => { jest.resetAllMocks(); }); - //////////////// - // @TODO - Get resize-observer polyfill working so we can test this use case. - //////////////// - - // it("should trigger O11y rendered with Services when all calls happy", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // const mockStore = configureStore(middlewares); - - // await act(async () => { - // render( - // wrapIntl( - // - // - // - // - // - // ), - // { container } - // ); - // }); + it("should trigger O11y rendered with Services when all calls happy", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + wrapIntl( + + + + + + ), + { container } + ); + }); - // await waitFor(() => { - // expect(mockTrack).toHaveBeenCalledTimes(1); - // expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { - // meta_data: `state: services`, - // event_type: "modal_display", - // }); - // }); - // }); + await waitFor(() => { + expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { + meta_data: `state: services`, + event_type: "modal_display", + }); + }); + }); - // it("should trigger O11y rendered with No Entities getEntities returns 0", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // const mockStore = configureStore(middlewares); + it("should trigger O11y rendered with No Entities getEntities returns 0", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + const mockStore = configureStore(middlewares); - // mockGetEntityCount.mockImplementation(_params => { - // return { entityCount: 0 }; - // }); + mockGetEntityCount.mockImplementation(_params => { + return { entityCount: 0 }; + }); - // await act(async () => { - // render( - // - // - // - // - // , - // { container } - // ); - // }); + await act(async () => { + render( + + + + + , + { container } + ); + }); - // await waitFor(() => { - // expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { - // meta_data: `state: no_entities`, - // event_type: "modal_display", - // }); - // }); - // }); + await waitFor(() => { + expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { + meta_data: `state: no_entities`, + event_type: "modal_display", + }); + }); + }); + // @TODO - Get resize-observer polyfill working so we can test this use case. // it("should trigger O11y rendered with No Services when no associated repos", async () => { // mockProviderSelectors.isConnected.mockReturnValue(true); // const mockStore = configureStore(middlewares); @@ -542,225 +547,225 @@ describe("Observability", () => { // }); // }); - // it("should trigger O11y rendered with Not Connected when NR not setup", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(false); - // const mockStore = configureStore(middlewares); - - // await act(async () => { - // render( - // - // - // - // - // , - // { container } - // ); - // }); - - // await waitFor(() => { - // expect(mockTrack).toHaveBeenCalledTimes(1); - // expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { - // meta_data: "state: Not Connected", - // event_type: "modal_display", - // }); - // }); - // }); - - // it("should trigger service clicked with all services when all calls happy", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // mockServiceClickedMethods(); - // const mockStore = configureStore(middlewares); - - // await act(async () => { - // render( - // wrapIntl( - // - // - // - // - // - // ), - // { container } - // ); - // }); + it("should trigger O11y rendered with Not Connected when NR not setup", async () => { + mockProviderSelectors.isConnected.mockReturnValue(false); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + + + + + , + { container } + ); + }); - // expect(mockTrack).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockTrack).toHaveBeenCalledWith("codestream/o11y displayed", { + meta_data: "state: Not Connected", + event_type: "modal_display", + }); + }); + }); - // // Close - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); - // }); + it("should trigger service clicked with all services when all calls happy", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + mockServiceClickedMethods(); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + wrapIntl( + + + + + + ), + { container } + ); + }); - // // And expand to trigger service clicked - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); - // }); + expect(mockTrack).toHaveBeenCalledTimes(1); - // await waitFor(() => { - // expect(mockTrack).toHaveBeenCalledTimes(2); - // expect(mockTrack).toHaveBeenNthCalledWith(2, "codestream/service displayed", { - // entity_guid: undefined, - // account_id: undefined, - // meta_data: `errors_listed: true`, - // meta_data_2: `slos_listed: true`, - // meta_data_3: `vulnerabilities_listed: false`, - // event_type: "modal_display", - // }); - // }); - // }); + // Close + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); + }); - // it("should trigger service clicked without errors listed", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // mockServiceClickedMethods(); + // And expand to trigger service clicked + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); + }); - // mockGetObservabilityErrors.mockImplementation(_params => { - // return { - // repos: [], - // }; - // }); + await waitFor(() => { + expect(mockTrack).toHaveBeenCalledTimes(2); + expect(mockTrack).toHaveBeenNthCalledWith(2, "codestream/service displayed", { + entity_guid: undefined, + account_id: undefined, + meta_data: `errors_listed: true`, + meta_data_2: `slos_listed: true`, + meta_data_3: `vulnerabilities_listed: false`, + event_type: "modal_display", + }); + }); + }); - // const mockStore = configureStore(middlewares); + it("should trigger service clicked without errors listed", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + mockServiceClickedMethods(); - // await act(async () => { - // render( - // wrapIntl( - // - // - // - // - // - // ), - // { container } - // ); - // }); + mockGetObservabilityErrors.mockImplementation(_params => { + return { + repos: [], + }; + }); - // expect(mockTrack).toHaveBeenCalledTimes(1); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + wrapIntl( + + + + + + ), + { container } + ); + }); - // // Close - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); - // }); + expect(mockTrack).toHaveBeenCalledTimes(1); - // // And expand to trigger service clicked - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); - // }); + // Close + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); + }); - // // await waitFor(() => { - // // expect(mockTrack).toHaveBeenCalledTimes(2); - // // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { - // // "Errors Listed": false, - // // "SLOs Listed": true, - // // "CLM Anomalies Listed": true, - // // "Vulnerabilities Listed": false, - // // }); - // // }); - // }); + // And expand to trigger service clicked + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); + }); - // it("should trigger service clicked without SLOs listed", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // mockServiceClickedMethods(); + // await waitFor(() => { + // expect(mockTrack).toHaveBeenCalledTimes(2); + // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { + // "Errors Listed": false, + // "SLOs Listed": true, + // "CLM Anomalies Listed": true, + // "Vulnerabilities Listed": false, + // }); + // }); + }); - // mockGetServiceLevelObjectives.mockImplementation(_params => { - // return { - // serviceLevelObjectives: [], - // }; - // }); + it("should trigger service clicked without SLOs listed", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + mockServiceClickedMethods(); - // const mockStore = configureStore(middlewares); + mockGetServiceLevelObjectives.mockImplementation(_params => { + return { + serviceLevelObjectives: [], + }; + }); - // await act(async () => { - // render( - // wrapIntl( - // - // - // - // - // - // ), - // { container } - // ); - // }); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + wrapIntl( + + + + + + ), + { container } + ); + }); - // expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockTrack).toHaveBeenCalledTimes(1); - // // Close - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); - // }); + // Close + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); + }); - // // And expand to trigger service clicked - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); - // }); + // And expand to trigger service clicked + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); + }); - // // await waitFor(() => { - // // expect(mockTrack).toHaveBeenCalledTimes(3); - // // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { - // // "Errors Listed": true, - // // "CLM Anomalies Listed": true, - // // "SLOs Listed": false, - // // "Vulnerabilities Listed": false, - // // }); - // // }); - // }); + // await waitFor(() => { + // expect(mockTrack).toHaveBeenCalledTimes(3); + // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { + // "Errors Listed": true, + // "CLM Anomalies Listed": true, + // "SLOs Listed": false, + // "Vulnerabilities Listed": false, + // }); + // }); + }); - // it("should trigger service clicked without anomalies listed", async () => { - // mockProviderSelectors.isConnected.mockReturnValue(true); - // mockServiceClickedMethods(); - - // mockGetObservabilityAnomalies.mockImplementation(_params => { - // return { - // errorRate: [], - // responseTime: [], - // isSupported: true, - // didNotifyNewAnomalies: false, - // }; - // }); + it("should trigger service clicked without anomalies listed", async () => { + mockProviderSelectors.isConnected.mockReturnValue(true); + mockServiceClickedMethods(); - // const mockStore = configureStore(middlewares); + mockGetObservabilityAnomalies.mockImplementation(_params => { + return { + errorRate: [], + responseTime: [], + isSupported: true, + didNotifyNewAnomalies: false, + }; + }); - // await act(async () => { - // render( - // wrapIntl( - // - // - // - // - // - // ), - // { container } - // ); - // }); + const mockStore = configureStore(middlewares); + + await act(async () => { + render( + wrapIntl( + + + + + + ), + { container } + ); + }); - // expect(mockTrack).toHaveBeenCalledTimes(1); + expect(mockTrack).toHaveBeenCalledTimes(1); - // // Close - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); - // }); + // Close + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.queryByTestId("entity-name-abcd1234-collapsed")).toBeInTheDocument(); + }); - // // And expand to trigger service clicked - // fireEvent.click(screen.getByTestId("entity-name-abcd1234")); - // await waitFor(() => { - // expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); - // }); + // And expand to trigger service clicked + fireEvent.click(screen.getByTestId("entity-name-abcd1234")); + await waitFor(() => { + expect(screen.getByTestId("entity-name-abcd1234-expanded")).toBeInTheDocument(); + }); - // // await waitFor(() => { - // // expect(mockTrack).toHaveBeenCalledTimes(2); - // // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { - // // "Errors Listed": true, - // // "CLM Anomalies Listed": false, - // // "SLOs Listed": true, - // // "Vulnerabilities Listed": false, - // // }); - // // }); - // }); + // await waitFor(() => { + // expect(mockTrack).toHaveBeenCalledTimes(2); + // expect(mockTrack).toHaveBeenNthCalledWith(2, "NR Service Clicked", { + // "Errors Listed": true, + // "CLM Anomalies Listed": false, + // "SLOs Listed": true, + // "Vulnerabilities Listed": false, + // }); + // }); + }); });