diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 70e02152f..2379489d7 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -16,6 +16,7 @@ window.isDockerInstalled = true; window.isDockerComposeInstalled = true; window.isMicrometerProject; + window.isDigmathonModeEnabled = true; window.assetsRefreshInterval; window.assetsSearch = true; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d4002a9f0..4574f965a 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,6 +1,6 @@ import type { Preview } from "@storybook/react"; import { StoryFn } from "@storybook/react"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { cancelMessage, initializeDigmaMessageListener, @@ -13,17 +13,28 @@ import { Mode } from "../src/globals"; const preview: Preview = { decorators: [ (Story: StoryFn, context): JSX.Element => { + const [isInitialized, setIsInitialized] = useState(false); // TODO: Fix types - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const theme = context.globals.theme as Mode; - initializeDigmaMessageListener(dispatcher); - window.sendMessageToDigma = sendMessage; - window.cancelMessageToDigma = cancelMessage; - return ( + useEffect(() => { + const removeDigmaMessageListener = + initializeDigmaMessageListener(dispatcher); + window.sendMessageToDigma = sendMessage; + window.cancelMessageToDigma = cancelMessage; + setIsInitialized(true); + + return () => { + removeDigmaMessageListener(); + }; + }, []); + + return isInitialized ? ( + ) : ( + <>Initializing... ); } ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c01357f2..a2da032a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,5 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", "stylelint.validate": ["typescript"], - "cSpell.words": ["Digma", "UNDISMISS"] + "cSpell.words": ["digma", "digmathon", "digmo", "undismiss"] } diff --git a/assets/index.ejs b/assets/index.ejs index f47fa0bfe..e82c4626d 100644 --- a/assets/index.ejs +++ b/assets/index.ejs @@ -32,6 +32,10 @@ window.isDockerInstalled; window.isDockerComposeInstalled; window.isMicrometerProject; + window.productKey; + window.userId; + window.isDigmathonModeEnabled; + window.isDigmathonGameFinished; <% for (var i = 0; i < environmentVariables.length; i++) { %> window.<%= environmentVariables[i] %>;<% } %> diff --git a/public/images/DigmoWithAmazonGiftCard.svg b/public/images/DigmoWithAmazonGiftCard.svg new file mode 100644 index 000000000..acbf82de3 --- /dev/null +++ b/public/images/DigmoWithAmazonGiftCard.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/confettiBackground.svg b/public/images/confettiBackground.svg new file mode 100644 index 000000000..bf8328cd6 --- /dev/null +++ b/public/images/confettiBackground.svg @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/BottleneckInsightCard.svg b/public/images/insightCards/BottleneckInsightCard.svg new file mode 100644 index 000000000..ab0d6687f --- /dev/null +++ b/public/images/insightCards/BottleneckInsightCard.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/EndpointHighNumberOfQueriesInsightCard.svg b/public/images/insightCards/EndpointHighNumberOfQueriesInsightCard.svg new file mode 100644 index 000000000..aa7137fa1 --- /dev/null +++ b/public/images/insightCards/EndpointHighNumberOfQueriesInsightCard.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/EndpointSessionInViewInsightCard.svg b/public/images/insightCards/EndpointSessionInViewInsightCard.svg new file mode 100644 index 000000000..de4affc5b --- /dev/null +++ b/public/images/insightCards/EndpointSessionInViewInsightCard.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/HotSpotInsightCard.svg b/public/images/insightCards/HotSpotInsightCard.svg new file mode 100644 index 000000000..58abf5153 --- /dev/null +++ b/public/images/insightCards/HotSpotInsightCard.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/NPlusOneInsightCard.svg b/public/images/insightCards/NPlusOneInsightCard.svg new file mode 100644 index 000000000..da70bbda6 --- /dev/null +++ b/public/images/insightCards/NPlusOneInsightCard.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/QueryOptimizationInsightCard.svg b/public/images/insightCards/QueryOptimizationInsightCard.svg new file mode 100644 index 000000000..7e6d73307 --- /dev/null +++ b/public/images/insightCards/QueryOptimizationInsightCard.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/SpanNexusInsightCard.svg b/public/images/insightCards/SpanNexusInsightCard.svg new file mode 100644 index 000000000..72870cfb0 --- /dev/null +++ b/public/images/insightCards/SpanNexusInsightCard.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/SpanScalingInsightCard.svg b/public/images/insightCards/SpanScalingInsightCard.svg new file mode 100644 index 000000000..d9a89501d --- /dev/null +++ b/public/images/insightCards/SpanScalingInsightCard.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/insightCards/SpanUsagesInsightCard.svg b/public/images/insightCards/SpanUsagesInsightCard.svg new file mode 100644 index 000000000..d0add0c67 --- /dev/null +++ b/public/images/insightCards/SpanUsagesInsightCard.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/actions.ts b/src/actions.ts index 2795a686f..2c3f83dae 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -41,5 +41,10 @@ export const actions = addPrefix(ACTION_PREFIX, { CHANGE_VIEW: "CHANGE_VIEW", GET_INSIGHT_STATS: "GET_INSIGHT_STATS", SET_INSIGHT_STATS: "SET_INSIGHT_STATS", - CHANGE_ENVIRONMENT: "CHANGE_ENVIRONMENT" + CHANGE_ENVIRONMENT: "CHANGE_ENVIRONMENT", + SET_DIGMATHON_MODE: "SET_DIGMATHON_MODE", + SET_PRODUCT_KEY: "SET_PRODUCT_KEY", + SET_USER_ID: "SET_USER_ID", + SET_IS_DIGMATHON_GAME_FINISHED: "SET_IS_DIGMATHON_GAME_FINISHED", + FINISH_DIGMATHON_GAME: "FINISH_DIGMATHON_GAME" }); diff --git a/src/api/index.ts b/src/api/index.ts index 335183426..01b562ac1 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -10,12 +10,18 @@ const isDigmaMessageEvent = (e: MessageEvent): e is DigmaMessageEvent => export const initializeDigmaMessageListener = ( dispatcher: ActionDispatcher ) => { - window.addEventListener("message", (e) => { + const handleDigmaMessage = (e: MessageEvent) => { if (isDigmaMessageEvent(e)) { console.debug("Digma message received: ", e); dispatcher.dispatch(e.timeStamp, e.data.action, e.data.payload); } - }); + }; + + window.addEventListener("message", handleDigmaMessage); + + return () => { + window.removeEventListener("message", handleDigmaMessage); + }; }; export const sendMessage = ( diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointBottleneckHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointBottleneckHighlightCard/index.tsx index 1d4c78638..f7d2d1618 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointBottleneckHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointBottleneckHighlightCard/index.tsx @@ -10,7 +10,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EndpointBottleneckMetrics, EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointBottleneckHighlightCardProps } from "./types"; @@ -65,7 +65,7 @@ export const EndpointBottleneckHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointChattyApiV2HighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointChattyApiV2HighlightCard/index.tsx index 5a5a5527f..3c462213c 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointChattyApiV2HighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointChattyApiV2HighlightCard/index.tsx @@ -7,7 +7,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EndpointChattyApiV2Metrics, EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointChattyApiV2HighlightCardProps } from "./types"; @@ -35,7 +35,7 @@ export const EndpointChattyApiV2HighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointHighNumberOfQueriesHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointHighNumberOfQueriesHighlightCard/index.tsx index 03b21e3a6..9a1fd7efa 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointHighNumberOfQueriesHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointHighNumberOfQueriesHighlightCard/index.tsx @@ -10,7 +10,7 @@ import { EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointHighNumberOfQueriesHighlightCardProps } from "./types"; @@ -52,7 +52,7 @@ export const EndpointHighNumberOfQueriesHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointQueryOptimizationV2HighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointQueryOptimizationV2HighlightCard/index.tsx index fce8e8763..37257f58f 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointQueryOptimizationV2HighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointQueryOptimizationV2HighlightCard/index.tsx @@ -11,7 +11,7 @@ import { EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointQueryOptimizationV2HighlightCardProps } from "./types"; @@ -39,7 +39,7 @@ export const EndpointQueryOptimizationV2HighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointSessionInViewHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointSessionInViewHighlightCard/index.tsx index 887bfa4d9..f1f05d8df 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointSessionInViewHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointSessionInViewHighlightCard/index.tsx @@ -6,7 +6,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EndpointSessionInViewMetrics, EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointSessionInViewHighlightCardProps } from "./types"; @@ -23,7 +23,7 @@ export const EndpointSessionInViewHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointSlowdownSourceHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointSlowdownSourceHighlightCard/index.tsx index 90854b544..f74a03b15 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointSlowdownSourceHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointSlowdownSourceHighlightCard/index.tsx @@ -8,7 +8,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EndpointSlowdownSourceMetrics, EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointSlowdownSourceHighlightCardProps } from "./types"; @@ -39,7 +39,7 @@ export const EndpointSlowdownSourceHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/EndpointSpanNPlusOneHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/EndpointSpanNPlusOneHighlightCard/index.tsx index ab379c54d..202829407 100644 --- a/src/components/Highlights/TopIssues/highlightCards/EndpointSpanNPlusOneHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/EndpointSpanNPlusOneHighlightCard/index.tsx @@ -10,7 +10,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EndpointSpanNPlusOneMetrics, EnvironmentData } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { EndpointSpanNPlusOneHighlightCardProps } from "./types"; @@ -57,7 +57,7 @@ export const EndpointSpanNPlusOneHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/HotSpotHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/HotSpotHighlightCard/index.tsx index 39faccf1c..28f0875f0 100644 --- a/src/components/Highlights/TopIssues/highlightCards/HotSpotHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/HotSpotHighlightCard/index.tsx @@ -6,7 +6,7 @@ import { TableText } from "../../../common/TableText"; import { HighlightCard } from "../../common/HighlightCard"; import { EnvironmentData, HotSpotMetrics } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { HotSpotHighlightCardProps } from "./types"; export const HotSpotHighlightCard = ({ data }: HotSpotHighlightCardProps) => { @@ -28,7 +28,7 @@ export const HotSpotHighlightCard = ({ data }: HotSpotHighlightCardProps) => { const columns = addEnvironmentColumns(columnHelper, metricsColumns); const handleTableRowClick = (row: Row>) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/SpaNPlusOneHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/SpaNPlusOneHighlightCard/index.tsx index 302e5fde3..29ce0fb4a 100644 --- a/src/components/Highlights/TopIssues/highlightCards/SpaNPlusOneHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/SpaNPlusOneHighlightCard/index.tsx @@ -9,7 +9,7 @@ import { TableText } from "../../../common/TableText"; import { HighlightCard } from "../../common/HighlightCard"; import { EnvironmentData, SpaNPlusOneMetrics } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { SpaNPlusOneHighlightCardProps } from "./types"; export const SpaNPlusOneHighlightCard = ({ @@ -66,7 +66,7 @@ export const SpaNPlusOneHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/SpanEndpointBottleneckHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/SpanEndpointBottleneckHighlightCard/index.tsx index dabc0b809..80685de4d 100644 --- a/src/components/Highlights/TopIssues/highlightCards/SpanEndpointBottleneckHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/SpanEndpointBottleneckHighlightCard/index.tsx @@ -9,7 +9,7 @@ import { TableText } from "../../../common/TableText"; import { HighlightCard } from "../../common/HighlightCard"; import { EnvironmentData, SpanEndpointBottleneckMetrics } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { SpanEndpointBottleneckHighlightCardProps } from "./types"; export const SpanEndpointBottleneckHighlightCard = ({ @@ -63,7 +63,7 @@ export const SpanEndpointBottleneckHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/SpanQueryOptimizationHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/SpanQueryOptimizationHighlightCard/index.tsx index 1396e9200..b6ea7f6bc 100644 --- a/src/components/Highlights/TopIssues/highlightCards/SpanQueryOptimizationHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/SpanQueryOptimizationHighlightCard/index.tsx @@ -10,7 +10,7 @@ import { AssetLink } from "../../common/AssetLink"; import { HighlightCard } from "../../common/HighlightCard"; import { EnvironmentData, SpanQueryOptimizationMetrics } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { DescriptionContainer } from "../styles"; import { SpanQueryOptimizationHighlightCardProps } from "./types"; @@ -70,7 +70,7 @@ export const SpanQueryOptimizationHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/SpanScalingHighlightCard/index.tsx b/src/components/Highlights/TopIssues/highlightCards/SpanScalingHighlightCard/index.tsx index b627e4ab4..93c20cef8 100644 --- a/src/components/Highlights/TopIssues/highlightCards/SpanScalingHighlightCard/index.tsx +++ b/src/components/Highlights/TopIssues/highlightCards/SpanScalingHighlightCard/index.tsx @@ -6,7 +6,7 @@ import { TableText } from "../../../common/TableText"; import { HighlightCard } from "../../common/HighlightCard"; import { EnvironmentData, SpanScalingMetrics } from "../../types"; import { addEnvironmentColumns } from "../addEnvironmentColumns"; -import { goToEnvironmentIssues } from "../goToEnvironmentIssues"; +import { handleEnvironmentTableRowClick } from "../goToEnvironmentIssues"; import { SpanScalingHighlightCardProps } from "./types"; export const SpanScalingHighlightCard = ({ @@ -36,7 +36,7 @@ export const SpanScalingHighlightCard = ({ const handleTableRowClick = ( row: Row> ) => { - goToEnvironmentIssues( + handleEnvironmentTableRowClick( config.environments, row.original.environmentName, data.insightType diff --git a/src/components/Highlights/TopIssues/highlightCards/goToEnvironmentIssues.ts b/src/components/Highlights/TopIssues/highlightCards/goToEnvironmentIssues.ts index ad64e2eb9..1b231e6c9 100644 --- a/src/components/Highlights/TopIssues/highlightCards/goToEnvironmentIssues.ts +++ b/src/components/Highlights/TopIssues/highlightCards/goToEnvironmentIssues.ts @@ -8,7 +8,7 @@ import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserA import { Environment } from "../../../common/App/types"; import { trackingEvents } from "../../tracking"; -export const goToEnvironmentIssues = ( +export const handleEnvironmentTableRowClick = ( environments: Environment[] | undefined, environmentNameToSelect: string, insightType: InsightType diff --git a/src/components/Highlights/TopIssues/useTopIssuesData.ts b/src/components/Highlights/TopIssues/useTopIssuesData.ts index 7511d33de..44588936a 100644 --- a/src/components/Highlights/TopIssues/useTopIssuesData.ts +++ b/src/components/Highlights/TopIssues/useTopIssuesData.ts @@ -26,26 +26,26 @@ export const useTopIssuesData = () => { } }); }, [config.scope?.span?.spanCodeObjectId, config.environments]); - const previousGetData = usePrevious(getData); + useEffect(() => { + if (previousGetData && previousGetData !== getData) { + window.clearTimeout(refreshTimerId.current); + + getData(); + } + }, [previousGetData, getData]); + useEffect(() => { if ( - (previousGetData && previousGetData !== getData) || - (previousLastSetDataTimeStamp && - previousLastSetDataTimeStamp !== lastSetDataTimeStamp) + previousLastSetDataTimeStamp && + previousLastSetDataTimeStamp !== lastSetDataTimeStamp ) { - window.clearTimeout(refreshTimerId.current); refreshTimerId.current = window.setTimeout(() => { getData(); }, REFRESH_INTERVAL); } - }, [ - previousLastSetDataTimeStamp, - lastSetDataTimeStamp, - getData, - previousGetData - ]); + }, [previousLastSetDataTimeStamp, lastSetDataTimeStamp, getData]); useEffect(() => { const handleTopIssuesData = (data: any, timeStamp: number) => { diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/index.tsx index e4194a387..4cfcc1d77 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/index.tsx @@ -96,7 +96,9 @@ export const InsightHeader = ({ + + + } /> diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/styles.ts index 6c8b5bf2e..c9ac3bcb5 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/styles.ts +++ b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/InsightHeader/styles.ts @@ -51,3 +51,7 @@ export const Description = styled.div` display: flex; color: ${({ theme }) => theme.colors.v3.text.secondary}; `; + +export const InsightIconContainer = styled.div` + display: flex; +`; diff --git a/src/components/Insights/useInsightsData.ts b/src/components/Insights/useInsightsData.ts index f787e5a64..e56a16c0f 100644 --- a/src/components/Insights/useInsightsData.ts +++ b/src/components/Insights/useInsightsData.ts @@ -21,16 +21,16 @@ interface UseInsightDataProps { query: InsightsQuery; } -const getData = (query: ScopedInsightsQuery, state?: GlobalState) => { +const getData = (scopedQuery: ScopedInsightsQuery, state?: GlobalState) => { const getDataQuery: InsightsDataQuery = { - displayName: query.searchQuery, - sortBy: query.sorting.criterion, - sortOrder: query.sorting.order, - page: query.page, - scopedSpanCodeObjectId: query.scopedSpanCodeObjectId, - showDismissed: query.showDismissed, - insightViewType: query.insightViewType, - showUnreadOnly: query.showUnreadOnly + displayName: scopedQuery.searchQuery, + sortBy: scopedQuery.sorting.criterion, + sortOrder: scopedQuery.sorting.order, + page: scopedQuery.page, + scopedSpanCodeObjectId: scopedQuery.scopedSpanCodeObjectId, + showDismissed: scopedQuery.showDismissed, + insightViewType: scopedQuery.insightViewType, + showUnreadOnly: scopedQuery.showUnreadOnly }; window.sendMessageToDigma({ @@ -41,7 +41,7 @@ const getData = (query: ScopedInsightsQuery, state?: GlobalState) => { }); const globalStateSlice = - query.insightViewType === "Analytics" ? "analytics" : "insights"; + scopedQuery.insightViewType === "Analytics" ? "analytics" : "insights"; window.sendMessageToDigma({ action: globalActions.UPDATE_STATE, @@ -55,7 +55,10 @@ const getData = (query: ScopedInsightsQuery, state?: GlobalState) => { }); }; -export const useInsightsData = (props: UseInsightDataProps) => { +export const useInsightsData = ({ + refreshInterval, + query +}: UseInsightDataProps) => { const [data, setData] = useState({ insightsStatus: InsightsStatus.LOADING, insights: [], @@ -71,16 +74,16 @@ export const useInsightsData = (props: UseInsightDataProps) => { const config = useContext(ConfigContext); const { scope, environment, state } = config; - const query = useMemo( + const scopedQuery = useMemo( () => ({ - ...props.query, + ...query, scopedSpanCodeObjectId: scope?.span?.spanCodeObjectId || null }), - [props.query, scope] + [query, scope] ); useEffect(() => { - getData(query, state); + getData(scopedQuery, state); setIsInitialLoading(true); setIsLoading(true); @@ -113,13 +116,9 @@ export const useInsightsData = (props: UseInsightDataProps) => { useEffect(() => { if (previousLastSetDataTimeStamp !== lastSetDataTimeStamp) { window.clearTimeout(refreshTimerId.current); - refreshTimerId.current = window.setTimeout( - (insightsQuery: ScopedInsightsQuery) => { - getData(insightsQuery, state); - }, - props.refreshInterval, - query - ); + refreshTimerId.current = window.setTimeout(() => { + getData(scopedQuery, state); + }, refreshInterval); } return () => { @@ -128,26 +127,20 @@ export const useInsightsData = (props: UseInsightDataProps) => { }, [ lastSetDataTimeStamp, previousLastSetDataTimeStamp, - query, environment, - props.refreshInterval + scopedQuery, + refreshInterval ]); useEffect(() => { + getData(scopedQuery, state); setIsLoading(true); - getData( - { - ...props.query, - scopedSpanCodeObjectId: scope?.span?.spanCodeObjectId || null - }, - state - ); - }, [props.query, scope, environment]); + }, [scopedQuery, scope?.span?.spanCodeObjectId, environment?.originalName]); return { isInitialLoading, data, isLoading, - refresh: () => getData(query, state) + refresh: () => getData(scopedQuery, state) }; }; diff --git a/src/components/InstallationWizard/FinishStep/index.tsx b/src/components/InstallationWizard/FinishStep/index.tsx index 36d8b409d..2845f3ad7 100644 --- a/src/components/InstallationWizard/FinishStep/index.tsx +++ b/src/components/InstallationWizard/FinishStep/index.tsx @@ -1,10 +1,13 @@ +import { useContext } from "react"; import { DefaultTheme, useTheme } from "styled-components"; import { GETTING_STARTED_VIDEO_URL } from "../../../constants"; import { openURLInDefaultBrowser } from "../../../utils/actions/openURLInDefaultBrowser"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { ConfigContext } from "../../common/App/ConfigContext"; import { getThemeKind } from "../../common/App/styles"; import { CircleLoader } from "../../common/CircleLoader"; import { Link } from "../../common/Link"; +import { KeyIcon } from "../../common/icons/16px/KeyIcon"; import { ChatIcon } from "../../common/icons/ChatIcon"; import { CheckmarkCircleInvertedIcon } from "../../common/icons/CheckmarkCircleInvertedIcon"; import { GearIcon } from "../../common/icons/GearIcon"; @@ -42,6 +45,7 @@ const getErrorIconColor = (theme: DefaultTheme) => { export const FinishStep = (props: FinishStepProps) => { const theme = useTheme(); const themeKind = getThemeKind(theme); + const config = useContext(ConfigContext); const handleGettingStartedVideoLinkClick = () => { sendUserActionTrackingEvent( @@ -68,20 +72,47 @@ export const FinishStep = (props: FinishStepProps) => { )} + {config.isDigmathonModeEnabled && ( + + + Product key(optional) + + + If you've received a product key, please enter it here + + + + + + )} - Stay up to date(optional) + Stay up to date + {!props.productKey && ( + (optional) + )} Enter your E-mail address to be the first to get Digma updates - - + + {props.errors.email && ( + + + {props.errors.email} + + )} {props.isEmailValid === false && ( @@ -105,7 +136,7 @@ export const FinishStep = (props: FinishStepProps) => { /> )} - + diff --git a/src/components/InstallationWizard/FinishStep/styles.ts b/src/components/InstallationWizard/FinishStep/styles.ts index 3bc407e74..9882ad22d 100644 --- a/src/components/InstallationWizard/FinishStep/styles.ts +++ b/src/components/InstallationWizard/FinishStep/styles.ts @@ -57,14 +57,14 @@ export const RunOrDebugIllustration = styled.img` margin: 7% 17%; `; -export const EmailField = styled.div` +export const TextField = styled.div` display: flex; flex-direction: column; gap: 4px; position: relative; `; -export const EmailInput = styled.input` +export const TextInput = styled.input` font-size: 14px; padding: 8px 10px; border-radius: 4px; diff --git a/src/components/InstallationWizard/FinishStep/types.ts b/src/components/InstallationWizard/FinishStep/types.ts index c43afea77..cf15f739f 100644 --- a/src/components/InstallationWizard/FinishStep/types.ts +++ b/src/components/InstallationWizard/FinishStep/types.ts @@ -1,4 +1,5 @@ import { ChangeEvent } from "react"; +import { FieldsErrors } from "../types"; export interface FinishStepProps { quickstartURL?: string; @@ -7,4 +8,7 @@ export interface FinishStepProps { email: string; isEmailValid?: boolean; isEmailValidating: boolean; + productKey: string; + onProductKeyInputChange: (e: ChangeEvent) => void; + errors: FieldsErrors; } diff --git a/src/components/InstallationWizard/InstallationWizard.stories.tsx b/src/components/InstallationWizard/InstallationWizard.stories.tsx index 731d36360..e1aac04e9 100644 --- a/src/components/InstallationWizard/InstallationWizard.stories.tsx +++ b/src/components/InstallationWizard/InstallationWizard.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; - import { InstallationWizard } from "."; +import { actions as globalActions } from "../../actions"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { @@ -17,4 +17,113 @@ export default meta; type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args -export const Default: Story = {}; +export const NoDigmaInstalled: Story = { + play: () => { + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_INSTALLED, + payload: { + isDockerInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_COMPOSE_INSTALLED, + payload: { + isDockerComposeInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DIGMA_ENGINE_INSTALLED, + payload: { + isDigmaEngineInstalled: false + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_DIGMA_STATUS, + payload: { + connection: { + type: null, + status: false + }, + runningDigmaInstances: [] + } + }); + } +}; + +export const DigmaEngineInstalledAndRunning: Story = { + play: () => { + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_INSTALLED, + payload: { + isDockerInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_COMPOSE_INSTALLED, + payload: { + isDockerComposeInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DIGMA_ENGINE_INSTALLED, + payload: { + isDigmaEngineInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_DIGMA_STATUS, + payload: { + connection: { + type: "local", + status: true + }, + runningDigmaInstances: ["localEngine"] + } + }); + } +}; + +export const DigmaEngineInstalledAndStopped: Story = { + play: () => { + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_INSTALLED, + payload: { + isDockerInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DOCKER_COMPOSE_INSTALLED, + payload: { + isDockerComposeInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_IS_DIGMA_ENGINE_INSTALLED, + payload: { + isDigmaEngineInstalled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_DIGMA_STATUS, + payload: { + connection: { + type: null, + status: false + }, + runningDigmaInstances: [] + } + }); + } +}; diff --git a/src/components/InstallationWizard/Step/index.tsx b/src/components/InstallationWizard/Step/index.tsx index 40145cbd8..f0884a73f 100644 --- a/src/components/InstallationWizard/Step/index.tsx +++ b/src/components/InstallationWizard/Step/index.tsx @@ -69,7 +69,7 @@ export const Step = (props: StepProps) => { > diff --git a/src/components/InstallationWizard/Step/styles.ts b/src/components/InstallationWizard/Step/styles.ts index 796afe862..24754cbc5 100644 --- a/src/components/InstallationWizard/Step/styles.ts +++ b/src/components/InstallationWizard/Step/styles.ts @@ -127,8 +127,8 @@ export const Number = styled.span` align-items: center; justify-content: center; position: absolute; - background: ${({ theme, status }) => - status === "completed" ? "none" : getNumberBackgroundColor(theme)}; + background: ${({ theme, $status }) => + $status === "completed" ? "none" : getNumberBackgroundColor(theme)}; ${({ theme, $transitionClassName, $transitionDuration }) => { return ` diff --git a/src/components/InstallationWizard/Step/types.ts b/src/components/InstallationWizard/Step/types.ts index 567886b13..a9c0d2c37 100644 --- a/src/components/InstallationWizard/Step/types.ts +++ b/src/components/InstallationWizard/Step/types.ts @@ -37,5 +37,5 @@ export interface NumberContainerProps { } export interface NumberProps extends TransitionProps { - status: StepStatus; + $status: StepStatus; } diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index 12f2aae87..1a6974558 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -19,6 +19,7 @@ import { StepData, StepStatus } from "./Step/types"; import { actions } from "./actions"; import * as s from "./styles"; import { trackingEvents } from "./tracking"; +import { FieldsErrors } from "./types"; const DIGMA_DOCKER_EXTENSION_URL = "https://open.docker.com/extensions/marketplace?extensionId=digmaai/digma-docker-extension"; @@ -53,6 +54,14 @@ const getStepStatus = (index: number, currentStep: number): StepStatus => { return "not-completed"; }; +const getFieldsErrors = (productKey: string, email: string): FieldsErrors => { + if (productKey.length > 0 && email.length === 0) { + return { email: "Please enter your email" }; + } + + return {}; +}; + export const InstallationWizard = () => { const config = useContext(ConfigContext); const [currentStep, setCurrentStep] = useState(firstStep); @@ -81,11 +90,13 @@ export const InstallationWizard = () => { // const theme = useTheme(); // const themeKind = getThemeKind(theme); const [email, setEmail] = useState(config.userEmail); + const [productKey, setProductKey] = useState(config.productKey); const [isEmailValid, setIsEmailValid] = useState( email.length > 0 ? isValidEmailFormat(email) : undefined ); const [isEmailValidating, setIsEmailValidating] = useState(false); const debouncedEmail = useDebounce(email, 1000); + const [errors, setErrors] = useState({}); // const [ // isDigmaCloudNotificationCheckboxChecked, // setIsDigmaCloudNotificationCheckboxChecked @@ -226,12 +237,21 @@ export const InstallationWizard = () => { const value = e.target.value.trim(); if (email !== value) { + setErrors({}); setIsEmailValid(undefined); setIsEmailValidating(true); setEmail(value); } }; + const handleProductKeyInputChange = (e: ChangeEvent) => { + const value = e.target.value.trim(); + + if (productKey !== value) { + setProductKey(value); + } + }; + const handleCloseButtonClick = () => { window.sendMessageToDigma({ action: actions.CLOSE @@ -239,12 +259,19 @@ export const InstallationWizard = () => { }; const handleFinishButtonClick = () => { - window.sendMessageToDigma({ - action: actions.FINISH, - payload: { - ...(debouncedEmail.length > 0 ? { email: debouncedEmail } : {}) - } - }); + const errors = getFieldsErrors(productKey, email); + + setErrors(errors); + + if (Object.keys(errors).length === 0) { + window.sendMessageToDigma({ + action: actions.FINISH, + payload: { + ...(debouncedEmail.length > 0 ? { email: debouncedEmail } : {}), + ...(productKey.length > 0 ? { productKey } : {}) + } + }); + } }; const handleSlackLinkClick = () => { @@ -319,6 +346,9 @@ export const InstallationWizard = () => { onEmailInputChange={handleEmailInputChange} isEmailValid={isEmailValid} isEmailValidating={isEmailValidating} + productKey={productKey} + errors={errors} + onProductKeyInputChange={handleProductKeyInputChange} /> ) } diff --git a/src/components/InstallationWizard/types.ts b/src/components/InstallationWizard/types.ts index 5e60dda22..18fd26762 100644 --- a/src/components/InstallationWizard/types.ts +++ b/src/components/InstallationWizard/types.ts @@ -17,3 +17,12 @@ export interface FinishStepFooterContentProps { $transitionClassName: string; $transitionDuration: number; } + +export interface FieldValidationResult { + isValid?: boolean; + error?: string; +} + +export interface FieldsErrors { + [key: string]: string | undefined; +} diff --git a/src/components/Notifications/index.tsx b/src/components/Notifications/index.tsx index 0eda44ba9..81a1118bb 100644 --- a/src/components/Notifications/index.tsx +++ b/src/components/Notifications/index.tsx @@ -44,6 +44,9 @@ export const trackingEvents = addPrefix( " " ); +/** + * @deprecated + */ export const Notifications = (props: NotificationsProps) => { const [data, setData] = useState(); const previousData = usePrevious(data); diff --git a/src/components/RecentActivity/Digmathon/CongratulationsView/index.tsx b/src/components/RecentActivity/Digmathon/CongratulationsView/index.tsx new file mode 100644 index 000000000..0205d3332 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/CongratulationsView/index.tsx @@ -0,0 +1,64 @@ +import { useContext, useEffect } from "react"; +import { sendTrackingEvent } from "../../../../utils/actions/sendTrackingEvent"; +import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; +import { ConfigContext } from "../../../common/App/ConfigContext"; +import { ConfigContextData } from "../../../common/App/types"; +import { CheckmarkCircleIcon } from "../../../common/icons/12px/CheckmarkCircleIcon"; +import { trackingEvents } from "../../tracking"; +import { DigmathonInsightData } from "../../types"; +import * as s from "./styles"; +import { CongratulationsViewProps } from "./types"; + +const EMAIL_ADDRESS = "digmathon@digma.ai"; + +const getEmailURL = ( + data: DigmathonInsightData[], + config: ConfigContextData +) => { + const userId = config.userId || config.userRegistrationEmail || ""; + const subject = `Digmathon Challenge Completed! [${userId}]`; + + const foundInsights = data + .filter((x) => x.isFound) + .map((x) => x.data?.title || x.type) + .join(", "); + const body = [ + "Insights found:", + foundInsights, + "Please send back the reward to this email!" + ].join("%0D%0A%0D%0A"); + + return `mailto:${EMAIL_ADDRESS}?subject=${subject}&body=${body}`; +}; + +export const CongratulationsView = ({ data }: CongratulationsViewProps) => { + const config = useContext(ConfigContext); + const emailURL = getEmailURL(data, config); + + const handleContactLinkClick = () => { + sendUserActionTrackingEvent( + trackingEvents.DIGMATHON_VIEW_CONTACT_LINK_CLICKED + ); + }; + + useEffect(() => { + sendTrackingEvent(trackingEvents.DIGMATHON_CONGRATULATIONS_VIEWED); + }, []); + + return ( + + + + + + Congratulations! + You've successfully reached the Digma insights goal. Please click + the link below to send us an email and claim your reward. Feel free to + keep using Digma locally for free, forever! + + + {EMAIL_ADDRESS} + + + ); +}; diff --git a/src/components/RecentActivity/Digmathon/CongratulationsView/styles.ts b/src/components/RecentActivity/Digmathon/CongratulationsView/styles.ts new file mode 100644 index 000000000..879796b09 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/CongratulationsView/styles.ts @@ -0,0 +1,46 @@ +import styled from "styled-components"; +import { + bodyRegularTypography, + footnoteRegularTypography, + subscriptRegularTypography +} from "../../../common/App/typographies"; +import { Link } from "../../../common/v3/Link"; + +export const Container = styled.div` + background: url("images/confettiBackground.svg") no-repeat center top / cover; + display: flex; + flex-direction: column; + gap: 16px; + justify-content: center; + align-items: center; + flex-grow: 1; +`; + +export const IconContainer = styled.div` + display: flex; + color: ${({ theme }) => theme.colors.v3.status.success}; +`; + +export const TextContainer = styled.div` + ${footnoteRegularTypography} + + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + text-align: center; + color: ${({ theme }) => theme.colors.v3.text.secondary}; + width: 288px; +`; + +export const Title = styled.span` + ${bodyRegularTypography} + + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; + +export const ContactLink = styled(Link)` + ${subscriptRegularTypography} + + padding: 4px 0; +`; diff --git a/src/components/RecentActivity/Digmathon/CongratulationsView/types.ts b/src/components/RecentActivity/Digmathon/CongratulationsView/types.ts new file mode 100644 index 000000000..9fe5e814b --- /dev/null +++ b/src/components/RecentActivity/Digmathon/CongratulationsView/types.ts @@ -0,0 +1,5 @@ +import { DigmathonInsightData } from "../../types"; + +export interface CongratulationsViewProps { + data: DigmathonInsightData[]; +} diff --git a/src/components/RecentActivity/Digmathon/Digmathon.stories.tsx b/src/components/RecentActivity/Digmathon/Digmathon.stories.tsx new file mode 100644 index 000000000..a3ecc1d2b --- /dev/null +++ b/src/components/RecentActivity/Digmathon/Digmathon.stories.tsx @@ -0,0 +1,47 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Digmathon } from "."; +import { actions } from "../actions"; +import { mockedDigmathonProgressData } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Recent Activity/Digmathon", + component: Digmathon, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = { + play: () => { + setTimeout(() => { + window.setTimeout(() => { + window.postMessage({ + type: "digma", + action: actions.SET_DIGMATHON_PROGRESS_DATA, + payload: mockedDigmathonProgressData + }); + }); + }, 0); + } +}; + +export const Congratulations: Story = { + play: () => { + setTimeout(() => { + window.setTimeout(() => { + window.postMessage({ + type: "digma", + action: actions.SET_DIGMATHON_PROGRESS_DATA, + payload: mockedDigmathonProgressData + }); + }); + }, 0); + } +}; diff --git a/src/components/RecentActivity/Digmathon/DigmathonInsightCard/DigmathonInsightCard.stories.tsx b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/DigmathonInsightCard.stories.tsx new file mode 100644 index 000000000..39e23920a --- /dev/null +++ b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/DigmathonInsightCard.stories.tsx @@ -0,0 +1,37 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { DigmathonInsightCard } from "."; +import { InsightType } from "../../../Insights/types"; +import { getDigmathonInsightCardData } from "../getDigmathonInsightData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Recent Activity/Digmathon/DigmathonInsightCard", + component: DigmathonInsightCard, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +const data = getDigmathonInsightCardData(InsightType.EndpointSpanNPlusOne); + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args +export const Default: Story = { + args: { + number: 1, + data, + isActive: false + } +}; + +export const Active: Story = { + args: { + number: 1, + data, + isActive: true + } +}; diff --git a/src/components/RecentActivity/Digmathon/DigmathonInsightCard/index.tsx b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/index.tsx new file mode 100644 index 000000000..9f489def4 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/index.tsx @@ -0,0 +1,18 @@ +import * as s from "./styles"; +import { DigmathonInsightCardProps } from "./types"; + +export const DigmathonInsightCard = ({ + number, + data, + isActive +}: DigmathonInsightCardProps) => ( + + + {number} + + {data.title} + {data.description} + + {data.illustration} + +); diff --git a/src/components/RecentActivity/Digmathon/DigmathonInsightCard/styles.ts b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/styles.ts new file mode 100644 index 000000000..205d0cd22 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/styles.ts @@ -0,0 +1,76 @@ +import styled from "styled-components"; +import { + bodyBoldTypography, + caption1BoldTypography, + footnoteRegularTypography +} from "../../../common/App/typographies"; +import { ContainerProps } from "./types"; + +export const Container = styled.div` + display: flex; + gap: 8px; + padding-top: 16px; + padding-left: 16px; + opacity: ${({ $isActive }) => ($isActive ? 1 : 0.35)}; + border-radius: 8px; + border: 2px solid + ${({ theme, $isActive }) => + $isActive + ? theme.colors.v3.icon.brandSecondary + : theme.colors.stroke.primary}; + background: ${({ theme, $isActive }) => + $isActive ? theme.colors.v3.surface.brandPrimary : "none"}; + height: 114px; + min-width: 191px; + box-sizing: border-box; + overflow: hidden; + position: relative; +`; + +export const NumberContainer = styled.div` + ${caption1BoldTypography} + + display: flex; + height: 16px; + width: 16px; + align-items: center; + justify-content: center; + border: 1px solid ${({ theme }) => theme.colors.v3.surface.brandTertiary}; + border-radius: 50%; + flex-shrink: 0; +`; + +export const TextContainer = styled.div` + display: flex; + flex-direction: column; + color: ${({ theme }) => theme.colors.v3.text.primary}; + width: 143px; + flex-shrink: 0; +`; + +export const Title = styled.span` + ${bodyBoldTypography} +`; + +export const Description = styled.span` + ${footnoteRegularTypography} +`; + +export const IllustrationContainer = styled.div` + min-width: 300px; + overflow: hidden; + margin-left: auto; + z-index: 1; +`; + +export const GradientBackground = styled.div` + width: 326px; + height: 326px; + border-radius: 326px; + opacity: 0.35; + background: ${({ theme }) => theme.colors.v3.icon.primary}; + filter: blur(67px); + position: absolute; + right: -220px; + bottom: -168px; +`; diff --git a/src/components/RecentActivity/Digmathon/DigmathonInsightCard/types.ts b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/types.ts new file mode 100644 index 000000000..9da2fc505 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/DigmathonInsightCard/types.ts @@ -0,0 +1,11 @@ +import { DigmathonInsightCardData } from "../../types"; + +export type DigmathonInsightCardProps = { + number: number; + data: DigmathonInsightCardData; + isActive: boolean; +}; + +export type ContainerProps = { + $isActive: boolean; +}; diff --git a/src/components/RecentActivity/Digmathon/ProgressView/index.tsx b/src/components/RecentActivity/Digmathon/ProgressView/index.tsx new file mode 100644 index 000000000..6be8d3704 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/ProgressView/index.tsx @@ -0,0 +1,56 @@ +import { useEffect } from "react"; +import { sendTrackingEvent } from "../../../../utils/actions/sendTrackingEvent"; +import { trackingEvents } from "../../tracking"; +import { DigmathonInsightCard } from "../DigmathonInsightCard"; +import * as s from "./styles"; +import { ProgressViewProps } from "./types"; + +export const ProgressView = ({ data, foundIssuesCount }: ProgressViewProps) => { + useEffect(() => { + sendTrackingEvent(trackingEvents.DIGMATHON_PROGRESS_VIEWED); + }, []); + + if (foundIssuesCount === 0) { + return ( + + + + + Start Digmathon + + To get started run your code with Digma and begin unlocking + issues. Check back here to see your progress! + + + + + ); + } + + return ( + <> + + Search for issues + + Improve your code, and win a gift card + + + {foundIssuesCount} out of{" "} + {data.length} issues found + + + + {data.map((x, i) => + x.data ? ( + + ) : null + )} + + + ); +}; diff --git a/src/components/RecentActivity/Digmathon/ProgressView/styles.ts b/src/components/RecentActivity/Digmathon/ProgressView/styles.ts new file mode 100644 index 000000000..1f1da0ce3 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/ProgressView/styles.ts @@ -0,0 +1,73 @@ +import styled from "styled-components"; +import { + bodyMediumTypography, + footnoteMediumTypography, + footnoteRegularTypography, + subscriptRegularTypography +} from "../../../common/App/typographies"; + +export const Header = styled.div` + ${footnoteRegularTypography} + + display: flex; + gap: 8px; + height: 32px; + flex-shrink: 0; + align-items: center; + color: ${({ theme }) => theme.colors.v3.text.primary}; + padding: 0 12px; +`; + +export const HeaderTitle = styled.span` + ${footnoteMediumTypography} +`; + +export const HeaderDescription = styled.span` + color: ${({ theme }) => theme.colors.v3.text.secondary}; +`; + +export const IssuesCounter = styled.span` + margin-left: auto; +`; + +export const FoundIssuesNumber = styled.span` + color: ${({ theme }) => theme.colors.v3.icon.brandSecondary}; +`; + +export const CardsContainer = styled.div` + display: grid; + grid-gap: 8px 10px; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr; + padding: 0 12px 12px; +`; + +export const EmptyStateContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-grow: 1; +`; + +export const EmptyStateContentContainer = styled.div` + width: 290px; + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const EmptyStateTextContainer = styled.div` + ${subscriptRegularTypography} + + display: flex; + flex-direction: column; + gap: 4px; + text-align: center; + color: ${({ theme }) => theme.colors.v3.text.secondary}; +`; + +export const EmptyStateTitle = styled.span` + ${bodyMediumTypography} + + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; diff --git a/src/components/RecentActivity/Digmathon/ProgressView/types.ts b/src/components/RecentActivity/Digmathon/ProgressView/types.ts new file mode 100644 index 000000000..86771d029 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/ProgressView/types.ts @@ -0,0 +1,6 @@ +import { DigmathonInsightData } from "../../types"; + +export interface ProgressViewProps { + data: DigmathonInsightData[]; + foundIssuesCount: number; +} diff --git a/src/components/RecentActivity/Digmathon/getDigmathonInsightData.tsx b/src/components/RecentActivity/Digmathon/getDigmathonInsightData.tsx new file mode 100644 index 000000000..c71f8cc2e --- /dev/null +++ b/src/components/RecentActivity/Digmathon/getDigmathonInsightData.tsx @@ -0,0 +1,90 @@ +import { InsightType } from "../../Insights/types"; +import { DigmathonInsightCardData } from "../types"; + +export const getDigmathonInsightCardData = ( + type: InsightType +): DigmathonInsightCardData | undefined => { + switch (type) { + case InsightType.SpanScaling: + return { + title: "Span Scaling", + description: "How well does this scale?", + illustration: ( + + ) + }; + case InsightType.SpanNexus: + return { + title: "Code Nexus", + description: "The most important piece in your code Jenga tower", + illustration: ( + + ) + }; + case InsightType.EndpointQueryOptimizationV2: + case InsightType.EndpointQueryOptimization: + case InsightType.SpanQueryOptimization: + return { + title: "Query Optimization Suggested", + description: "Caching anyone?", + illustration: ( + + ) + }; + case InsightType.HotSpot: + return { + title: "Error Hotspot", + description: "Where errors congregate", + illustration: + }; + case InsightType.EndpointSpanNPlusOne: + case InsightType.EndpointSpaNPlusOne: + case InsightType.SpaNPlusOne: + return { + title: "N+1 Select", + description: "Excuse me sir, your abstraction is leaking", + illustration: ( + + ) + }; + case InsightType.EndpointSessionInView: + return { + title: "Open Session in View", + description: "Why'd you go and do that?", + illustration: ( + + ) + }; + case InsightType.SpanUsages: + return { + title: "Top Usage", + description: "Know where you're coming from to know where you're going", + illustration: ( + + ) + }; + case InsightType.EndpointHighNumberOfQueries: + return { + title: "High Number of Queries", + description: "Stop hitting that DB", + illustration: ( + + ) + }; + case InsightType.EndpointBottleneck: + case InsightType.SpanEndpointBottleneck: + return { + title: "Bottleneck", + description: "Only one way to handle clogged pipes", + illustration: ( + + ) + }; + } +}; diff --git a/src/components/RecentActivity/Digmathon/index.tsx b/src/components/RecentActivity/Digmathon/index.tsx new file mode 100644 index 000000000..34e9aba28 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/index.tsx @@ -0,0 +1,73 @@ +import { useEffect } from "react"; +import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { NewCircleLoader } from "../../common/NewCircleLoader"; +import { ChevronIcon } from "../../common/icons/16px/ChevronIcon"; +import { Direction } from "../../common/icons/types"; +import { trackingEvents } from "../tracking"; +import { DigmathonInsightData } from "../types"; +import { CongratulationsView } from "./CongratulationsView"; +import { ProgressView } from "./ProgressView"; +import * as s from "./styles"; +import { DigmathonProgressProps } from "./types"; + +export const Digmathon = ({ + data, + getData, + foundIssuesCount, + isDigmathonCompleted, + onGoBack +}: DigmathonProgressProps) => { + useEffect(() => { + getData(); + }, []); + + const handleGoBackButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.DIGMATHON_VIEW_BACK_BUTTON_CLICKED + ); + onGoBack(); + }; + + const handleExitButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.DIGMATHON_VIEW_EXIT_BUTTON_CLICKED + ); + onGoBack(); + }; + + const renderContent = (data: DigmathonInsightData[]) => + isDigmathonCompleted ? ( + + ) : ( + + ); + + return ( + + + + + Back + + + Digmathon + + + {data ? ( + renderContent(data) + ) : ( + + + + )} + + ); +}; diff --git a/src/components/RecentActivity/Digmathon/mockData.ts b/src/components/RecentActivity/Digmathon/mockData.ts new file mode 100644 index 000000000..6cbc87177 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/mockData.ts @@ -0,0 +1,21 @@ +import { InsightType } from "../../Insights/types"; +import { DigmathonProgressData } from "../types"; + +export const mockedDigmathonProgressData: DigmathonProgressData = { + insights: [ + InsightType.SpanScaling, + InsightType.SpanNexus, + InsightType.EndpointQueryOptimizationV2, + InsightType.EndpointQueryOptimization, + InsightType.SpanQueryOptimization, + InsightType.HotSpot, + InsightType.EndpointSpanNPlusOne, + InsightType.EndpointSpaNPlusOne, + InsightType.SpaNPlusOne, + InsightType.EndpointSessionInView, + InsightType.SpanUsages, + InsightType.EndpointHighNumberOfQueries, + InsightType.EndpointBottleneck, + InsightType.SpanEndpointBottleneck + ] +}; diff --git a/src/components/RecentActivity/Digmathon/styles.ts b/src/components/RecentActivity/Digmathon/styles.ts new file mode 100644 index 000000000..0f8644688 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/styles.ts @@ -0,0 +1,71 @@ +import styled from "styled-components"; +import { + subscriptRegularTypography, + subscriptSemiboldTypography +} from "../../common/App/typographies"; +import { Button } from "../../common/v3/Button"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + background: ${({ theme }) => theme.colors.v3.surface.primary}; + min-height: 100%; +`; + +export const Header = styled.div` + display: flex; + height: 44px; + align-items: center; + background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader}; + box-shadow: 0 9px 24px 0 rgb(0 0 0 / 30%); + flex-shrink: 0; + gap: 12px; + padding: 0 12px; +`; + +export const BackButton = styled.button` + ${subscriptRegularTypography} + + font-family: inherit; + color: ${({ theme }) => theme.colors.v3.text.tertiary}; + border: none; + background: none; + padding: 0; + display: flex; + gap: 4px; + align-items: center; + cursor: pointer; +`; + +export const Divider = styled.div` + margin-right: 4px; + border-radius: 1px; + width: 1px; + height: 13px; + background: ${({ theme }) => theme.colors.v3.stroke.primary}; +`; + +export const HeaderTitle = styled.span` + ${subscriptSemiboldTypography} + + padding-left: 4px; + color: ${({ theme }) => theme.colors.v3.text.primary}; +`; + +export const ExitButton = styled(Button)` + margin-left: auto; +`; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + overflow: auto; + height: 100%; +`; + +export const LoaderContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-grow: 1; +`; diff --git a/src/components/RecentActivity/Digmathon/types.ts b/src/components/RecentActivity/Digmathon/types.ts new file mode 100644 index 000000000..043c130c1 --- /dev/null +++ b/src/components/RecentActivity/Digmathon/types.ts @@ -0,0 +1,9 @@ +import { DigmathonInsightData } from "../types"; + +export interface DigmathonProgressProps { + data?: DigmathonInsightData[]; + getData: () => void; + foundIssuesCount: number; + isDigmathonCompleted: boolean; + onGoBack: () => void; +} diff --git a/src/components/RecentActivity/EnvironmentPanel/index.tsx b/src/components/RecentActivity/EnvironmentPanel/index.tsx index 71ee6aa96..c438f2b2d 100644 --- a/src/components/RecentActivity/EnvironmentPanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentPanel/index.tsx @@ -14,6 +14,7 @@ import { NewButton } from "../../common/NewButton"; import { NewPopover } from "../../common/NewPopover"; import { ToggleSwitch } from "../../common/ToggleSwitch"; import { PlusIcon } from "../../common/icons/12px/PlusIcon"; +import { ConfettiIcon } from "../../common/icons/16px/ConfettiIcon"; import { HammerIcon } from "../../common/icons/16px/HammerIcon"; import { OpenTelemetryLogoIcon } from "../../common/icons/16px/OpenTelemetryLogoIcon"; import { SlackLogoIcon } from "../../common/icons/16px/SlackLogoIcon"; @@ -170,6 +171,9 @@ export const EnvironmentPanel = (props: EnvironmentPanelProps) => { }; const handleTroubleshootingClick = () => { + sendUserActionTrackingEvent(trackingEvents.KEBAB_MENU_ITEM_CLICKED, { + item: "Troubleshooting" + }); window.sendMessageToDigma({ action: globalActions.OPEN_TROUBLESHOOTING_GUIDE }); @@ -177,10 +181,20 @@ export const EnvironmentPanel = (props: EnvironmentPanelProps) => { }; const handleSlackLinkClick = () => { + sendUserActionTrackingEvent(trackingEvents.KEBAB_MENU_ITEM_CLICKED, { + item: "Digma Channel" + }); openURLInDefaultBrowser(SLACK_WORKSPACE_URL); setIsKebabMenuOpen(false); }; + const handleDigmathonModeMenuItemClick = () => { + sendUserActionTrackingEvent(trackingEvents.KEBAB_MENU_ITEM_CLICKED, { + item: "Digmathon mode" + }); + props.onDigmathonModeButtonClick(); + }; + return ( { onOpenChange={setIsKebabMenuOpen} isOpen={isKebabMenuOpen} content={ - + { label: "Digma Channel", icon: , onClick: handleSlackLinkClick - } + }, + ...(config.isDigmathonModeEnabled && config.productKey + ? [ + { + id: "digmathon", + label: "Digmathon!", + icon: , + onClick: handleDigmathonModeMenuItemClick + } + ] + : []) ]} /> diff --git a/src/components/RecentActivity/EnvironmentPanel/types.ts b/src/components/RecentActivity/EnvironmentPanel/types.ts index 94f7682a8..87985d3b8 100644 --- a/src/components/RecentActivity/EnvironmentPanel/types.ts +++ b/src/components/RecentActivity/EnvironmentPanel/types.ts @@ -6,6 +6,7 @@ export interface EnvironmentPanelProps { onEnvironmentSelect: (environment: ExtendedEnvironment) => void; onEnvironmentAdd: (environment: string) => void; onEnvironmentDelete: (environment: string) => void; + onDigmathonModeButtonClick: () => void; } export type ViewMode = "table" | "list"; diff --git a/src/components/RecentActivity/RecentActivity.stories.tsx b/src/components/RecentActivity/RecentActivity.stories.tsx index 5864b156f..5c6275816 100644 --- a/src/components/RecentActivity/RecentActivity.stories.tsx +++ b/src/components/RecentActivity/RecentActivity.stories.tsx @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/react"; import { RecentActivity } from "."; +import { actions as globalActions } from "../../actions"; +import { mockedDigmathonProgressData } from "./Digmathon/mockData"; import { mockData as liveData } from "./LiveView/mockData"; import { actions } from "./actions"; import { RecentActivityData } from "./types"; @@ -673,3 +675,41 @@ export const OpenRegistrationDialog: Story = { }); } }; + +export const EnableDigmathonMode: Story = { + play: () => { + window.postMessage({ + type: "digma", + action: globalActions.SET_DIGMATHON_MODE, + payload: { + isDigmathonModeEnabled: true + } + }); + window.postMessage({ + type: "digma", + action: globalActions.SET_PRODUCT_KEY, + payload: { + productKey: "digmathon" + } + }); + window.postMessage({ + type: "digma", + action: actions.SET_DIGMATHON_PROGRESS_DATA, + payload: { + insights: [] + } + }); + } +}; + +export const OpenCongratulationsDigmathonView: Story = { + play: () => { + setTimeout(() => { + window.postMessage({ + type: "digma", + action: actions.SET_DIGMATHON_PROGRESS_DATA, + payload: mockedDigmathonProgressData + }); + }, 0); + } +}; diff --git a/src/components/RecentActivity/actions.ts b/src/components/RecentActivity/actions.ts index 57125d570..691fbcd50 100644 --- a/src/components/RecentActivity/actions.ts +++ b/src/components/RecentActivity/actions.ts @@ -19,5 +19,7 @@ export const actions = addPrefix(ACTION_PREFIX, { SET_REMOTE_ENVIRONMENT_CONNECTION_CHECK_RESULT: "SET_REMOTE_ENVIRONMENT_CONNECTION_CHECK_RESULT", FINISH_ORG_DIGMA_SETUP: "FINISH_ORG_DIGMA_SETUP", - OPEN_REGISTRATION_DIALOG: "OPEN_REGISTRATION_DIALOG" + OPEN_REGISTRATION_DIALOG: "OPEN_REGISTRATION_DIALOG", + GET_DIGMATHON_PROGRESS_DATA: "GET_DIGMATHON_PROGRESS_DATA", + SET_DIGMATHON_PROGRESS_DATA: "SET_DIGMATHON_PROGRESS_DATA" }); diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 544095d36..66b37f483 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -6,6 +6,7 @@ import { actions as globalActions } from "../../actions"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; import { trackingEvents as globalTrackingEvents } from "../../trackingEvents"; +import { isBoolean } from "../../typeGuards/isBoolean"; import { ChangeEnvironmentPayload } from "../../types"; import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; import { groupBy } from "../../utils/groupBy"; @@ -18,6 +19,7 @@ import { DigmaLogoFlatIcon } from "../common/icons/DigmaLogoFlatIcon"; import { ListIcon } from "../common/icons/ListIcon"; import { TableIcon } from "../common/icons/TableIcon"; import { DeleteEnvironmentConfirmation } from "./DeleteEnvironmentConfirmation"; +import { Digmathon } from "./Digmathon"; import { EnvironmentInstructionsPanel } from "./EnvironmentInstructionsPanel"; import { EnvironmentPanel } from "./EnvironmentPanel"; import { ViewMode } from "./EnvironmentPanel/types"; @@ -37,6 +39,7 @@ import { RecentActivityProps, ViewModeOption } from "./types"; +import { useDigmathonProgressData } from "./useDigmathonProgressData"; export const RECENT_ACTIVITY_CONTAINER_ID = "recent-activity"; @@ -99,7 +102,15 @@ export const RecentActivity = (props: RecentActivityProps) => { config.userRegistrationEmail ); const previousEnvironment = usePrevious(config.environment); + const [isDigmathonMode, setIsDigmathonMode] = useState(false); const { observe, entry } = useDimensions(); + const { + data: digmathonProgressData, + getData: getDigmathonProgressData, + foundIssuesCount, + isDigmathonCompleted + } = useDigmathonProgressData(); + const previousIsDigmathonCompleted = usePrevious(isDigmathonCompleted); const environmentActivities = useMemo( () => (data ? groupBy(data.entries, (x) => x.environment) : {}), @@ -182,6 +193,16 @@ export const RecentActivity = (props: RecentActivityProps) => { } }, [props.liveData]); + useEffect(() => { + if ( + isBoolean(previousIsDigmathonCompleted) && + previousIsDigmathonCompleted !== isDigmathonCompleted && + isDigmathonCompleted + ) { + setIsDigmathonMode(true); + } + }, [previousIsDigmathonCompleted, isDigmathonCompleted]); + useEffect(() => { if ( previousUserRegistrationEmail !== config.userRegistrationEmail && @@ -409,6 +430,14 @@ export const RecentActivity = (props: RecentActivityProps) => { } }; + const handleDigmathonModeButtonClick = () => { + setIsDigmathonMode(true); + }; + + const handleDigmathonGoBack = () => { + setIsDigmathonMode(false); + }; + const renderContent = () => { if (selectedEnvironment?.isPending) { switch (selectedEnvironment.type) { @@ -466,45 +495,56 @@ export const RecentActivity = (props: RecentActivityProps) => { return ( - - - {/* + {isDigmathonMode ? ( + + ) : ( + + + {/* */} - - - - {!selectedEnvironment?.isPending && ( - - Recent Activity - - - )} - {!config.isObservabilityEnabled && } - - - - {renderContent()} - - - - {liveData && ( - - - - )} - - + + + + {!selectedEnvironment?.isPending && ( + + Recent Activity + + + )} + {!config.isObservabilityEnabled && } + + + + {renderContent()} + + + + {liveData && ( + + + + )} + + + )} {environmentToDelete && ( { + window.sendMessageToDigma({ + action: actions.GET_DIGMATHON_PROGRESS_DATA + }); +}; + +const getIsFound = ( + data: DigmathonProgressData | undefined, + type: InsightType +) => { + if (!data) { + return false; + } + + switch (type) { + case InsightType.EndpointQueryOptimizationV2: + case InsightType.EndpointQueryOptimization: + case InsightType.SpanQueryOptimization: + return data.insights.some((x) => + [ + InsightType.EndpointQueryOptimizationV2, + InsightType.EndpointQueryOptimization, + InsightType.SpanQueryOptimization + ].includes(x) + ); + case InsightType.EndpointSpanNPlusOne: + case InsightType.EndpointSpaNPlusOne: + case InsightType.SpaNPlusOne: + return data.insights.some((x) => + [ + InsightType.EndpointSpanNPlusOne, + InsightType.EndpointSpaNPlusOne, + InsightType.SpaNPlusOne + ].includes(x) + ); + case InsightType.EndpointBottleneck: + case InsightType.SpanEndpointBottleneck: + return data.insights.some((x) => + [ + InsightType.EndpointBottleneck, + InsightType.SpanEndpointBottleneck + ].includes(x) + ); + default: + return data.insights.includes(type); + } +}; + +export const useDigmathonProgressData = () => { + const [data, setData] = useState(); + const foundIssuesCount = data?.filter((x) => x.isFound).length || 0; + const isDigmathonCompleted = + foundIssuesCount >= REQUIRED_COUNT_OF_FOUND_ISSUES; + const previousIsDigmathonCompleted = usePrevious(isDigmathonCompleted); + + useEffect(() => { + const handleSetDigmaProgressData = (data: unknown) => { + const payload = data as DigmathonProgressData; + const insights: DigmathonInsightData[] = [ + { + type: InsightType.SpanScaling, + data: getDigmathonInsightCardData(InsightType.SpanScaling), + isFound: getIsFound(payload, InsightType.SpanScaling) + }, + { + type: InsightType.SpanNexus, + data: getDigmathonInsightCardData(InsightType.SpanNexus), + isFound: getIsFound(payload, InsightType.SpanNexus) + }, + { + type: InsightType.SpanQueryOptimization, + data: getDigmathonInsightCardData(InsightType.SpanQueryOptimization), + isFound: getIsFound(payload, InsightType.SpanQueryOptimization) + }, + { + type: InsightType.HotSpot, + data: getDigmathonInsightCardData(InsightType.HotSpot), + isFound: getIsFound(payload, InsightType.HotSpot) + }, + { + type: InsightType.SpaNPlusOne, + data: getDigmathonInsightCardData(InsightType.SpaNPlusOne), + isFound: getIsFound(payload, InsightType.SpaNPlusOne) + }, + { + type: InsightType.EndpointSessionInView, + data: getDigmathonInsightCardData(InsightType.EndpointSessionInView), + isFound: getIsFound(payload, InsightType.EndpointSessionInView) + }, + { + type: InsightType.SpanUsages, + data: getDigmathonInsightCardData(InsightType.SpanUsages), + isFound: getIsFound(payload, InsightType.SpanUsages) + }, + { + type: InsightType.EndpointHighNumberOfQueries, + data: getDigmathonInsightCardData( + InsightType.EndpointHighNumberOfQueries + ), + isFound: getIsFound(payload, InsightType.EndpointHighNumberOfQueries) + }, + { + type: InsightType.EndpointBottleneck, + data: getDigmathonInsightCardData(InsightType.EndpointBottleneck), + isFound: getIsFound(payload, InsightType.EndpointBottleneck) + } + ]; + + setData(insights); + }; + + dispatcher.addActionListener( + actions.SET_DIGMATHON_PROGRESS_DATA, + handleSetDigmaProgressData + ); + + return () => { + dispatcher.removeActionListener( + actions.SET_DIGMATHON_PROGRESS_DATA, + handleSetDigmaProgressData + ); + }; + }, []); + + useEffect(() => { + if ( + previousIsDigmathonCompleted !== isDigmathonCompleted && + isDigmathonCompleted + ) { + window.sendMessageToDigma({ + action: globalActions.FINISH_DIGMATHON_GAME + }); + } + }, [isDigmathonCompleted, previousIsDigmathonCompleted]); + + return { + data, + getData, + foundIssuesCount, + isDigmathonCompleted + }; +}; diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts index 01aeb6906..c343bcbaa 100644 --- a/src/components/common/App/ConfigContext.ts +++ b/src/components/common/App/ConfigContext.ts @@ -28,7 +28,11 @@ export const initialState = { scope: undefined, isMicrometerProject: window.isMicrometerProject === true, state: undefined, - insightStats: undefined + insightStats: undefined, + productKey: isString(window.productKey) ? window.productKey : "", + isDigmathonModeEnabled: window.isDigmathonModeEnabled === true, + userId: isString(window.userId) ? window.userId : "", + isDigmathonGameFinished: window.isDigmathonGameFinished === true }; export const ConfigContext = createContext(initialState); diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 12ca1fef9..feaf600c4 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -236,6 +236,33 @@ export const App = (props: AppProps) => { })); }; + const handleSetIsDigmathonModeEnabled = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmathonModeEnabled)) { + setConfig((config) => ({ + ...config, + isDigmathonModeEnabled: data.isDigmathonModeEnabled as boolean + })); + } + }; + + const handleSetProductKey = (data: unknown) => { + if (isObject(data) && isString(data.productKey)) { + setConfig((config) => ({ + ...config, + productKey: data.productKey as string + })); + } + }; + + const handleIsDigmathonGameFinished = (data: unknown) => { + if (isObject(data) && isBoolean(data.isDigmathonGameFinished)) { + setConfig((config) => ({ + ...config, + isDigmathonGameFinished: data.isDigmathonGameFinished as boolean + })); + } + }; + dispatcher.addActionListener(actions.SET_THEME, handleSetTheme); dispatcher.addActionListener(actions.SET_MAIN_FONT, handleSetMainFont); dispatcher.addActionListener(actions.SET_CODE_FONT, handleSetCodeFont); @@ -295,6 +322,15 @@ export const App = (props: AppProps) => { actions.SET_INSIGHT_STATS, handleSetInsightStats ); + dispatcher.addActionListener( + actions.SET_DIGMATHON_MODE, + handleSetIsDigmathonModeEnabled + ); + dispatcher.addActionListener(actions.SET_PRODUCT_KEY, handleSetProductKey); + dispatcher.addActionListener( + actions.SET_IS_DIGMATHON_GAME_FINISHED, + handleIsDigmathonGameFinished + ); return () => { dispatcher.removeActionListener(actions.SET_THEME, handleSetTheme); @@ -362,6 +398,18 @@ export const App = (props: AppProps) => { actions.SET_INSIGHT_STATS, handleSetInsightStats ); + dispatcher.removeActionListener( + actions.SET_DIGMATHON_MODE, + handleSetIsDigmathonModeEnabled + ); + dispatcher.removeActionListener( + actions.SET_PRODUCT_KEY, + handleSetProductKey + ); + dispatcher.removeActionListener( + actions.SET_IS_DIGMATHON_GAME_FINISHED, + handleIsDigmathonGameFinished + ); }; }, []); diff --git a/src/components/common/App/types.ts b/src/components/common/App/types.ts index dceae5331..cc6754401 100644 --- a/src/components/common/App/types.ts +++ b/src/components/common/App/types.ts @@ -103,6 +103,10 @@ export interface ConfigContextData { isMicrometerProject: boolean; state?: GlobalState; insightStats?: InsightStats; + productKey: string; + isDigmathonModeEnabled: boolean; + userId: string; + isDigmathonGameFinished: boolean; } export interface InsightStats { diff --git a/src/components/common/App/typographies.ts b/src/components/common/App/typographies.ts index 1f2035b6e..73dde4e97 100644 --- a/src/components/common/App/typographies.ts +++ b/src/components/common/App/typographies.ts @@ -65,6 +65,12 @@ export const caption1RegularTypography = css` line-height: ${typographies.captionOne.lineHeight}px; `; +export const caption1BoldTypography = css` + font-size: ${typographies.captionOne.fontSize}px; + font-weight: ${typographies.captionOne.fontWeight.bold}; + line-height: ${typographies.captionOne.lineHeight}px; +`; + export const caption2RegularTypography = css` font-size: ${typographies.captionTwo.fontSize}px; font-weight: ${typographies.captionTwo.fontWeight.regular}; @@ -77,6 +83,18 @@ export const footnoteRegularTypography = css` line-height: ${typographies.footNote.lineHeight}px; `; +export const footnoteMediumTypography = css` + font-size: ${typographies.footNote.fontSize}px; + font-weight: ${typographies.footNote.fontWeight.medium}; + line-height: ${typographies.footNote.lineHeight}px; +`; + +export const footnoteBoldTypography = css` + font-size: ${typographies.footNote.fontSize}px; + font-weight: ${typographies.footNote.fontWeight.bold}; + line-height: ${typographies.footNote.lineHeight}px; +`; + export const subscriptRegularTypography = css` font-size: ${typographies.subscript.fontSize}px; font-weight: ${typographies.subscript.fontWeight.regular}; @@ -101,14 +119,20 @@ export const bodyRegularTypography = css` line-height: ${typographies.body.lineHeight}px; `; -export const bodySemiboldTypography = css` +export const bodyMediumTypography = css` font-size: ${typographies.body.fontSize}px; font-weight: ${typographies.body.fontWeight.medium}; line-height: ${typographies.body.lineHeight}px; `; -export const footnoteBoldTypography = css` - font-size: ${typographies.footNote.fontSize}px; - font-weight: ${typographies.footNote.fontWeight.bold}; - line-height: ${typographies.footNote.lineHeight}px; +export const bodySemiboldTypography = css` + font-size: ${typographies.body.fontSize}px; + font-weight: ${typographies.body.fontWeight.semibold}; + line-height: ${typographies.body.lineHeight}px; +`; + +export const bodyBoldTypography = css` + font-size: ${typographies.body.fontSize}px; + font-weight: ${typographies.body.fontWeight.bold}; + line-height: ${typographies.body.lineHeight}px; `; diff --git a/src/components/common/icons/16px/ConfettiIcon.tsx b/src/components/common/icons/16px/ConfettiIcon.tsx new file mode 100644 index 000000000..849ca3f32 --- /dev/null +++ b/src/components/common/icons/16px/ConfettiIcon.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const ConfettiIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const ConfettiIcon = React.memo(ConfettiIconComponent); diff --git a/src/components/common/icons/16px/KeyIcon.tsx b/src/components/common/icons/16px/KeyIcon.tsx new file mode 100644 index 000000000..09d814277 --- /dev/null +++ b/src/components/common/icons/16px/KeyIcon.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const KeyIconIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const KeyIcon = React.memo(KeyIconIconComponent); diff --git a/src/components/common/icons/20px/CheckmarkCircleIcon.tsx b/src/components/common/icons/20px/CheckmarkCircleIcon.tsx new file mode 100644 index 000000000..683854b7c --- /dev/null +++ b/src/components/common/icons/20px/CheckmarkCircleIcon.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import { IconProps } from "../types"; + +const CheckmarkCircleIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + + ); +}; + +export const CheckmarkCircleIcon = React.memo(CheckmarkCircleIconComponent); diff --git a/src/globals.d.ts b/src/globals.d.ts index ea8026560..0fa4a948b 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -55,6 +55,10 @@ declare global { testsRefreshInterval?: unknown; wizardSkipInstallationStep?: unknown; wizardFirstLaunch?: unknown; + productKey?: unknown; + isDigmathonModeEnabled?: unknown; + userId?: unknown; + isDigmathonGameFinished?: unknown; } } diff --git a/src/utils/actions/openURLInDefaultBrowser.ts b/src/utils/actions/openURLInDefaultBrowser.ts index 72e3cd573..e1f8c9986 100644 --- a/src/utils/actions/openURLInDefaultBrowser.ts +++ b/src/utils/actions/openURLInDefaultBrowser.ts @@ -1,8 +1,13 @@ import { actions } from "../../actions"; import { isString } from "../../typeGuards/isString"; +interface OpenURLInDefaultBrowserPayload { + url: string; + title?: string; +} + export const openURLInDefaultBrowser = (url: string, title?: string) => { - window.sendMessageToDigma({ + window.sendMessageToDigma({ action: actions.OPEN_URL_IN_DEFAULT_BROWSER, payload: { url, diff --git a/webpackEntries.ts b/webpackEntries.ts index 0c8776af6..50ef32efe 100644 --- a/webpackEntries.ts +++ b/webpackEntries.ts @@ -1,6 +1,7 @@ import path from "path"; export const entries: AppEntries = { + // deprecated assets: { entry: path.resolve(__dirname, "./src/containers/Assets/index.tsx"), environmentVariables: [ @@ -17,6 +18,7 @@ export const entries: AppEntries = { entry: path.resolve(__dirname, "./src/containers/Documentation/index.tsx"), environmentVariables: ["documentationPage"] }, + // deprecated insights: { entry: path.resolve(__dirname, "./src/containers/Insights/index.tsx"), environmentVariables: ["insightsRefreshInterval"] @@ -41,6 +43,7 @@ export const entries: AppEntries = { navigation: { entry: path.resolve(__dirname, "./src/containers/Navigation/index.tsx") }, + // deprecated notifications: { entry: path.resolve(__dirname, "./src/containers/Notifications/index.tsx"), environmentVariables: [ @@ -56,6 +59,7 @@ export const entries: AppEntries = { "recentActivityIsEnvironmentManagementEnabled" ] }, + // deprecated tests: { entry: path.resolve(__dirname, "./src/containers/Tests/index.tsx"), environmentVariables: ["testsRefreshInterval"]