Skip to content

Commit

Permalink
[TASK-989] feat: test destination connection (#1339)
Browse files Browse the repository at this point in the history
Added a new step to check the connection of a destination before
configuring it.
  • Loading branch information
alonkeyval authored Jul 8, 2024
1 parent ed5b023 commit 38d2730
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 75 deletions.
16 changes: 9 additions & 7 deletions frontend/endpoints/destinations.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ type DestinationsCategory struct {
}

type DestinationTypesCategoryItem struct {
Type common.DestinationType `json:"type"`
DisplayName string `json:"display_name"`
ImageUrl string `json:"image_url"`
SupportedSignals SupportedSignals `json:"supported_signals"`
Type common.DestinationType `json:"type"`
DisplayName string `json:"display_name"`
ImageUrl string `json:"image_url"`
SupportedSignals SupportedSignals `json:"supported_signals"`
TestConnectionSupported bool `json:"test_connection_supported"`
}

type SupportedSignals struct {
Expand Down Expand Up @@ -570,9 +571,10 @@ func getDestinationSecretFields(c *gin.Context, odigosns string, dest *v1alpha1.

func DestinationTypeConfigToCategoryItem(destConfig destinations.Destination) DestinationTypesCategoryItem {
return DestinationTypesCategoryItem{
Type: destConfig.Metadata.Type,
DisplayName: destConfig.Metadata.DisplayName,
ImageUrl: GetImageURL(destConfig.Spec.Image),
Type: destConfig.Metadata.Type,
DisplayName: destConfig.Metadata.DisplayName,
ImageUrl: GetImageURL(destConfig.Spec.Image),
TestConnectionSupported: destConfig.Spec.TestConnectionSupported,
SupportedSignals: SupportedSignals{
Traces: ObservabilitySignalSupport{
Supported: destConfig.Spec.Signals.Traces.Supported,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useMemo } from 'react';
import React from 'react';
import { SETUP } from '@/utils';
import { DestinationType } from '@/types';
import { styled } from 'styled-components';
import FormDangerZone from './form.danger.zone';
import { BackIcon } from '@keyval-dev/design-system';
import { Conditions, KeyvalText } from '@/design.system';
import { CreateConnectionForm, QuickHelp } from '@/components/setup';
import { CreateConnectionForm } from '@/components/setup';
import { ManageDestinationHeader } from '../manage.destination.header/manage.destination.header';

interface ManageDestinationProps {
Expand Down Expand Up @@ -37,18 +37,6 @@ export function ManageDestination({
onSubmit,
onDelete,
}: ManageDestinationProps) {
const videoList = useMemo(
() =>
destinationType?.fields
?.filter((field) => field?.video_url)
?.map((field) => ({
name: field.display_name,
src: field.video_url,
thumbnail_url: field.thumbnail_url,
})),
[destinationType]
);

return (
<>
{onBackClick && (
Expand All @@ -65,7 +53,7 @@ export function ManageDestination({
destinationNameValue={selectedDestination?.name}
dynamicFieldsValues={selectedDestination?.fields}
checkboxValues={selectedDestination?.signals}
supportedSignals={selectedDestination?.supported_signals}
destination={selectedDestination}
onSubmit={(data) => onSubmit(data)}
/>
{onDelete && (
Expand All @@ -74,7 +62,6 @@ export function ManageDestination({
</div>
<>
<Conditions conditions={selectedDestination?.conditions} />
{videoList?.length > 0 && <QuickHelp data={videoList} />}
</>
</CreateConnectionWrapper>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function ConnectionsIcons({
}) {
return icon ? (
<ConnectionsIconsWrapper>
<ConnectIcon />
<ConnectIcon size={48} />
<Divider />
<KeyvalImage
src={icon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const FieldWrapper = styled.div`

export const CreateDestinationButtonWrapper = styled.div`
margin-top: 48px;
height: 36px;
gap: 16px;
width: 362px;
display: flex;
flex-direction: column;
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';
import React, { useState, useEffect } from 'react';
import theme from '@/styles/palette';
import { useKeyDown } from '@/hooks';
import { Field } from '@/types/destinations';
import { useCheckConnection, useKeyDown } from '@/hooks';
import { Field, SelectedDestination } from '@/types';
import { renderFields } from './dynamic.fields';
import {
cleanObjectEmptyStringsValues,
Expand All @@ -14,6 +14,7 @@ import {
KeyvalButton,
KeyvalCheckbox,
KeyvalInput,
KeyvalLoader,
KeyvalText,
} from '@/design.system';
import {
Expand All @@ -25,12 +26,8 @@ import {

interface CreateConnectionFormProps {
fields: Field[];
destination: SelectedDestination;
onSubmit: (formData: DestinationBody) => void;
supportedSignals: {
[key: string]: {
supported: boolean;
};
};
dynamicFieldsValues?: {
[key: string]: any;
};
Expand Down Expand Up @@ -66,20 +63,26 @@ const sanitizeDynamicFields = (
export function CreateConnectionForm({
fields,
onSubmit,
supportedSignals,
destination,
dynamicFieldsValues,
destinationNameValue,
checkboxValues,
}: CreateConnectionFormProps) {
const [selectedMonitors, setSelectedMonitors] = useState(MONITORS);
const [isCreateButtonDisabled, setIsCreateButtonDisabled] = useState(true);
const [isConnectionTested, setIsConnectionTested] = useState({
enabled: null,
message: '',
});
const [dynamicFields, setDynamicFields] = useState(
sanitizeDynamicFields(fields, dynamicFieldsValues)
);
const [destinationName, setDestinationName] = useState<string>(
destinationNameValue || ''
);

const { checkDestinationConnection, isLoading } = useCheckConnection();

useEffect(() => {
setInitialDynamicFields();
}, [fields]);
Expand All @@ -90,7 +93,7 @@ export function CreateConnectionForm({

useEffect(() => {
filterSupportedMonitors();
}, [supportedSignals]);
}, [destination]);

useKeyDown('Enter', handleKeyPress);

Expand Down Expand Up @@ -123,7 +126,7 @@ export function CreateConnectionForm({
}));

setSelectedMonitors(
data.filter(({ id }) => supportedSignals[id]?.supported)
data.filter(({ id }) => destination?.supported_signals?.[id]?.supported)
);
}

Expand Down Expand Up @@ -151,6 +154,10 @@ export function CreateConnectionForm({
};

function handleDynamicFieldChange(name: string, value: string) {
if (isConnectionTested.enabled !== null) {
setIsConnectionTested({ enabled: null, message: '' });
}

setDynamicFields((prevFields) => ({ ...prevFields, [name]: value }));
}

Expand Down Expand Up @@ -181,10 +188,31 @@ export function CreateConnectionForm({
name: destinationName,
signals,
fields,
type: destination.type,
};
onSubmit(body);
}

async function handleCheckDestinationConnection() {
const signals = selectedMonitors.reduce(
(acc, { id, checked }) => ({ ...acc, [id]: checked }),
{}
);

const stringifyFields = stringifyNonStringValues(dynamicFields);
const fields = cleanObjectEmptyStringsValues(stringifyFields);

const body = {
name: destinationName,
signals,
fields,
type: destination.type,
};
try {
checkDestinationConnection(body, setIsConnectionTested);
} catch (error) {}
}

return (
<>
<KeyvalText size={18} weight={600}>
Expand Down Expand Up @@ -217,7 +245,35 @@ export function CreateConnectionForm({
</FieldWrapper>
{renderFields(fields, dynamicFields, handleDynamicFieldChange)}
<CreateDestinationButtonWrapper>
<KeyvalButton disabled={isCreateButtonDisabled} onClick={onCreateClick}>
{destination?.test_connection_supported && (
<KeyvalButton
variant="secondary"
disabled={isCreateButtonDisabled}
onClick={handleCheckDestinationConnection}
>
{isLoading ? (
<KeyvalLoader width={9} height={9} />
) : isConnectionTested.enabled === null ? (
<KeyvalText color={theme.text.secondary} size={14} weight={600}>
{'Check Connection'}
</KeyvalText>
) : isConnectionTested.enabled ? (
<KeyvalText color={theme.colors.success} size={14} weight={600}>
{'Connection Successful'}
</KeyvalText>
) : (
<KeyvalText color={theme.colors.error} size={14} weight={600}>
{isConnectionTested.message}
</KeyvalText>
)}
</KeyvalButton>
)}
<KeyvalButton
disabled={
isCreateButtonDisabled || isConnectionTested.enabled === false
}
onClick={onCreateClick}
>
<KeyvalText color={theme.colors.dark_blue} size={14} weight={600}>
{dynamicFieldsValues
? SETUP.UPDATE_DESTINATION
Expand Down
38 changes: 7 additions & 31 deletions frontend/webapp/containers/setup/connection/connection.section.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import React, { useLayoutEffect, useMemo, useState } from 'react';
import React from 'react';
import { useConnect } from '@/hooks';
import { useQuery } from 'react-query';
import { QUERIES } from '@/utils/constants';
import { getDestination } from '@/services';
import { KeyvalLoader } from '@/design.system';
import { useSearchParams } from 'next/navigation';
import { CreateConnectionForm, QuickHelp } from '@/components/setup';
import { CreateConnectionForm } from '@/components/setup';
import {
LoaderWrapper,
CreateConnectionContainer,
} from './connection.section.styled';

export interface DestinationBody {
name: string;
type?: string;
type: string;
signals: {
[key: string]: boolean;
};
Expand All @@ -22,11 +21,8 @@ export interface DestinationBody {
};
}

export function ConnectionSection({ supportedSignals }) {
const [type, setType] = useState<string>('');

export function ConnectionSection({ destination, type }) {
const { connect } = useConnect();
const searchParams = useSearchParams();

const { isLoading, data } = useQuery(
[QUERIES.API_DESTINATION_TYPE],
Expand All @@ -36,27 +32,8 @@ export function ConnectionSection({ supportedSignals }) {
}
);

useLayoutEffect(onPageLoad, []);

function onPageLoad() {
const search = searchParams.get('type');
search && setType(search);
}

const videoList = useMemo(
() =>
data?.fields
?.filter((field) => field?.video_url)
?.map((field) => ({
name: field.display_name,
src: field.video_url,
thumbnail_url: field.thumbnail_url,
})),
[data]
);

function createDestination(formData: DestinationBody) {
connect({ type, ...formData });
connect({ ...formData });
}

if (isLoading)
Expand All @@ -70,12 +47,11 @@ export function ConnectionSection({ supportedSignals }) {
<CreateConnectionContainer>
{data?.fields && (
<CreateConnectionForm
fields={data?.fields}
fields={data.fields}
onSubmit={createDestination}
supportedSignals={supportedSignals}
destination={destination}
/>
)}
{videoList?.length > 0 && <QuickHelp data={videoList} />}
</CreateConnectionContainer>
);
}
6 changes: 3 additions & 3 deletions frontend/webapp/containers/setup/connection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
const SEARCH_PARAM_TYPE = 'type';

export function ConnectDestinationContainer() {
const [type, setType] = useState<string>('');
const [selectedDestination, setSelectedDestination] =
useState<SelectedDestination>();

Expand All @@ -26,6 +27,7 @@ export function ConnectDestinationContainer() {

function getCurrentDestination() {
const type = searchParams.get(SEARCH_PARAM_TYPE);
setType(type as string);
if (destinationsTypes) {
const data = getCurrentDestinationByType(type as string);
setSelectedDestination(data);
Expand All @@ -46,9 +48,7 @@ export function ConnectDestinationContainer() {
return (
<KeyvalCard type={'secondary'} header={{ body: cardHeaderBody }}>
<SetupBackButton onBackClick={() => router.back()} />
<ConnectionSection
supportedSignals={selectedDestination?.supported_signals}
/>
<ConnectionSection type={type} destination={selectedDestination} />
</KeyvalCard>
);
}
1 change: 1 addition & 0 deletions frontend/webapp/hooks/destinations/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './useDestinations';
export * from './useCheckConnection';
29 changes: 29 additions & 0 deletions frontend/webapp/hooks/destinations/useCheckConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AxiosError } from 'axios';
import { useMutation } from 'react-query';
import { checkConnection } from '@/services';

export function useCheckConnection() {
const { mutateAsync, isLoading } = useMutation(checkConnection);

const checkDestinationConnection = async (body, callback) => {
try {
await mutateAsync(body, {
onSuccess: (res) =>
callback({
enabled: true,
message: res.message,
}),
onError: (error: AxiosError) =>
callback({
enabled: false,
message: 'Please check your input and try again.',
}),
});
} catch (error) {}
};

return {
isLoading,
checkDestinationConnection,
};
}
Loading

0 comments on commit 38d2730

Please sign in to comment.