Skip to content

Commit

Permalink
[Observability Onboarding] Prevent showing duplcated AWS services in …
Browse files Browse the repository at this point in the history
…Firehose flow (elastic#201613)

Closes elastic#200931 

Switched to using AWS service list as a base to showing the detected
services in the UI instead of the list of populated indices as multiple
indices can be related to a single service.

### How to test

1. Go to Firehose flow `/observabilityOnboarding/firehose`
2. Open Kibana dev tools in another tab
3. Ingest documents related into multiple data streams which related to
a single AWS service:
```
POST logs-aws.apigateway_logs-default/_doc
{
  "@timestamp": "2024-11-25T13:32:01.000Z",
  "some": 111,
  "aws.kinesis.name": "Elastic-CloudwatchLogs"
}

POST metrics-aws.apigateway_metrics-default/_doc
{
    "@timestamp": "2024-11-25T13:31:01.000Z",
    "agent": {
      "type": "firehose"
    },
    "aws": {
      "cloudwatch": {
        "namespace": "AWS/ApiGateway"
      },
      "exporter": {
        "arn": "arn:aws:cloudwatch:us-west-2:975050175126:metric-stream/Elastic-CloudwatchLogsAndMetricsToFirehose-CloudWatchMetricStream-Nhb4NhzPdL4J"
      }
    },
    "cloud": {
      "account": {
        "id": "975050175126"
      },
      "provider": "aws",
      "region": "us-west-2"
    }
}
```
4. Make sure you see only one entry for the service appear in the
Firehose flow
  • Loading branch information
mykolaharmash authored Dec 4, 2024
1 parent eb8f261 commit a2fb9b7
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common';
import { AWSIndexName } from '../../../../common/aws_firehose';
import { ObservabilityOnboardingContextValue } from '../../../plugin';

interface AWSServiceGetStartedConfig {
export interface AWSServiceGetStartedConfig {
id: string;
indexNameList: AWSIndexName[];
title: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@ import { EuiIcon, EuiSpacer, EuiText, useGeneratedHtmlId } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
import useInterval from 'react-use/lib/useInterval';
import { union } from 'lodash';
import { unionBy } from 'lodash';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { ObservabilityOnboardingAppServices } from '../../..';
import {
FIREHOSE_CLOUDFORMATION_STACK_NAME,
FIREHOSE_LOGS_STREAM_NAME,
type AWSIndexName,
} from '../../../../common/aws_firehose';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { AccordionWithIcon } from '../shared/accordion_with_icon';
import { GetStartedPanel } from '../shared/get_started_panel';
import { useAWSServiceGetStartedList } from './use_aws_service_get_started_list';
import {
type AWSServiceGetStartedConfig,
useAWSServiceGetStartedList,
} from './use_aws_service_get_started_list';
import { AutoRefreshCallout } from './auto_refresh_callout';
import { ProgressCallout } from './progress_callout';
import { HAS_DATA_FETCH_INTERVAL } from './utils';
Expand All @@ -36,12 +38,12 @@ interface Props {

export function VisualizeData({ onboardingId, selectedCreateStackOption }: Props) {
const accordionId = useGeneratedHtmlId({ prefix: 'accordion' });
const [orderedPopulatedAWSLogsIndexList, setOrderedPopulatedAWSLogsIndexList] = useState<
AWSIndexName[]
const [orderedVisibleAWSServiceList, setOrderedVisibleAWSServiceList] = useState<
AWSServiceGetStartedConfig[]
>([]);
const [shouldShowDataReceivedToast, setShouldShowDataReceivedToast] = useState<boolean>(true);
const {
data: populatedAWSLogsIndexList,
data: populatedAWSIndexList,
status,
refetch,
} = useFetcher((callApi) => {
Expand All @@ -60,12 +62,13 @@ export function VisualizeData({ onboardingId, selectedCreateStackOption }: Props
context: { cloudServiceProvider },
},
} = useKibana<ObservabilityOnboardingAppServices>();
const awsServiceGetStartedConfigList = useAWSServiceGetStartedList();

useEffect(() => {
if (
shouldShowDataReceivedToast &&
Array.isArray(populatedAWSLogsIndexList) &&
populatedAWSLogsIndexList.length > 0
Array.isArray(populatedAWSIndexList) &&
populatedAWSIndexList.length > 0
) {
notifications?.toasts.addSuccess(
{
Expand All @@ -90,17 +93,27 @@ export function VisualizeData({ onboardingId, selectedCreateStackOption }: Props
setShouldShowDataReceivedToast(false);
}

setOrderedPopulatedAWSLogsIndexList((currentList) =>
setOrderedVisibleAWSServiceList((currentList) =>
/**
* Using union() to ensure items in the array are unique
* add stay in the insertion order to keep the order of
* the AWS services in the UI.
* unionBy() ensures uniqueness of the resulting list
* and preserves the order of the first list passed to it,
* which in turn keeps already visible services in the UI
* in place and new services are only appended to the end.
*/
union(currentList, populatedAWSLogsIndexList)
unionBy(
currentList,
awsServiceGetStartedConfigList.filter(({ indexNameList }) =>
indexNameList.some((indexName) => populatedAWSIndexList?.includes(indexName))
),
'id'
)
);
}, [notifications?.toasts, populatedAWSLogsIndexList, shouldShowDataReceivedToast]);

const awsServiceGetStartedConfigList = useAWSServiceGetStartedList();
}, [
awsServiceGetStartedConfigList,
notifications?.toasts,
populatedAWSIndexList,
shouldShowDataReceivedToast,
]);

useInterval(() => {
if (REQUEST_PENDING_STATUS_LIST.includes(status)) {
Expand All @@ -110,7 +123,7 @@ export function VisualizeData({ onboardingId, selectedCreateStackOption }: Props
refetch();
}, HAS_DATA_FETCH_INTERVAL);

if (populatedAWSLogsIndexList === undefined) {
if (populatedAWSIndexList === undefined) {
return null;
}

Expand All @@ -127,56 +140,48 @@ export function VisualizeData({ onboardingId, selectedCreateStackOption }: Props

<EuiSpacer size="m" />

{orderedPopulatedAWSLogsIndexList.length === 0 && <ProgressCallout />}
{orderedPopulatedAWSLogsIndexList.length > 0 && <AutoRefreshCallout />}
{orderedVisibleAWSServiceList.length === 0 && <ProgressCallout />}
{orderedVisibleAWSServiceList.length > 0 && <AutoRefreshCallout />}

<EuiSpacer size="m" />

<div data-test-subj="observabilityOnboardingAWSServiceList">
{orderedPopulatedAWSLogsIndexList.map((indexName, index) => {
const getStartedConfig = awsServiceGetStartedConfigList.find(({ indexNameList }) =>
indexNameList.includes(indexName)
);

if (!getStartedConfig) {
return null;
{orderedVisibleAWSServiceList.map(
({ id, actionLinks, title, logoURL, previewImage }, index) => {
return (
<AccordionWithIcon
data-test-subj={`observabilityOnboardingAWSService-${id}`}
key={id}
id={`${accordionId}_${id}`}
icon={<EuiIcon type={logoURL} size="l" />}
title={title}
initialIsOpen={true}
borders={
index === 0 || index === orderedVisibleAWSServiceList.length - 1
? 'none'
: 'horizontal'
}
>
<GetStartedPanel
onboardingFlowType="firehose"
dataset={id}
telemetryEventContext={{
firehose: {
selectedCreateStackOption,
cloudServiceProvider,
},
}}
integration="aws"
newTab
isLoading={false}
actionLinks={actionLinks}
previewImage={previewImage}
onboardingId={onboardingId}
/>
</AccordionWithIcon>
);
}

const { id, actionLinks, title, logoURL, previewImage } = getStartedConfig;

return (
<AccordionWithIcon
data-test-subj={`observabilityOnboardingAWSService-${id}`}
key={id}
id={`${accordionId}_${id}`}
icon={<EuiIcon type={logoURL} size="l" />}
title={title}
initialIsOpen={true}
borders={
index === 0 || index === orderedPopulatedAWSLogsIndexList.length - 1
? 'none'
: 'horizontal'
}
>
<GetStartedPanel
onboardingFlowType="firehose"
dataset={indexName}
telemetryEventContext={{
firehose: {
selectedCreateStackOption,
cloudServiceProvider,
},
}}
integration="aws"
newTab
isLoading={false}
actionLinks={actionLinks}
previewImage={previewImage}
onboardingId={onboardingId}
/>
</AccordionWithIcon>
);
})}
)}
</div>
</>
);
Expand Down

0 comments on commit a2fb9b7

Please sign in to comment.