From 8308265a622da7241d067b384232495f1e8ba1b3 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 4 Oct 2022 23:09:50 +0300 Subject: [PATCH] wip: Restructure twitter integration --- .../apps/slack2/auth/verify-credentials.ts | 2 +- .../src/apps/twitter/triggers/my-tweets.ts | 2 +- .../apps/twitter2/auth/create-auth-data.ts | 22 ++- .../apps/twitter2/auth/is-still-verified.ts | 3 +- .../apps/twitter2/auth/verify-credentials.ts | 7 +- .../apps/twitter2/common/generate-request.ts | 39 ++++++ .../apps/twitter2/common/get-current-user.ts | 14 ++ .../twitter2/common/get-user-by-username.ts | 25 ++++ .../apps/twitter2/common/get-user-tweets.ts | 54 ++++++++ .../index.ts => oauth-client.ts} | 7 +- .../apps/twitter2/triggers/my-tweets/index.ts | 37 +++++ .../src/helpers/global-variable/connection.ts | 7 +- packages/backend/src/models/execution-step.ts | 9 +- packages/backend/src/models/flow.ts | 2 +- packages/backend/src/models/step.ts | 2 +- packages/backend/src/models/user.ts | 6 +- packages/backend/src/services/processor.ts | 4 +- packages/types/index.d.ts | 15 +- .../ChooseAppAndEventSubstep/index.tsx | 131 +++++++++++------- .../src/components/PowerInput/Suggestions.tsx | 71 +++++----- .../web/src/components/PowerInput/data.ts | 35 +++-- 21 files changed, 358 insertions(+), 136 deletions(-) create mode 100644 packages/backend/src/apps/twitter2/common/generate-request.ts create mode 100644 packages/backend/src/apps/twitter2/common/get-current-user.ts create mode 100644 packages/backend/src/apps/twitter2/common/get-user-by-username.ts create mode 100644 packages/backend/src/apps/twitter2/common/get-user-tweets.ts rename packages/backend/src/apps/twitter2/common/{oauth-client/index.ts => oauth-client.ts} (62%) create mode 100644 packages/backend/src/apps/twitter2/triggers/my-tweets/index.ts diff --git a/packages/backend/src/apps/slack2/auth/verify-credentials.ts b/packages/backend/src/apps/slack2/auth/verify-credentials.ts index 1cd195c9..3a58850f 100644 --- a/packages/backend/src/apps/slack2/auth/verify-credentials.ts +++ b/packages/backend/src/apps/slack2/auth/verify-credentials.ts @@ -1,5 +1,5 @@ import qs from 'qs'; -import { IGlobalVariableForConnection } from '../../../helpers/global-variable/connection'; +import { IGlobalVariableForConnection } from '@automatisch/types'; const verifyCredentials = async ($: IGlobalVariableForConnection) => { const headers = { diff --git a/packages/backend/src/apps/twitter/triggers/my-tweets.ts b/packages/backend/src/apps/twitter/triggers/my-tweets.ts index dd88b69c..60b049b7 100644 --- a/packages/backend/src/apps/twitter/triggers/my-tweets.ts +++ b/packages/backend/src/apps/twitter/triggers/my-tweets.ts @@ -7,7 +7,7 @@ export default class MyTweets { this.client = client; } - async run(lastInternalId: string) { + async run() { return this.getTweets(lastInternalId); } diff --git a/packages/backend/src/apps/twitter2/auth/create-auth-data.ts b/packages/backend/src/apps/twitter2/auth/create-auth-data.ts index 548b1769..9c30c8a9 100644 --- a/packages/backend/src/apps/twitter2/auth/create-auth-data.ts +++ b/packages/backend/src/apps/twitter2/auth/create-auth-data.ts @@ -1,8 +1,12 @@ -import { IJSONObject, IField } from '@automatisch/types'; -import oauthClient from '../common/oauth-client'; +import generateRequest from '../common/generate-request'; +import { + IJSONObject, + IField, + IGlobalVariableForConnection, +} from '@automatisch/types'; import { URLSearchParams } from 'url'; -export default async function createAuthData($: any) { +export default async function createAuthData($: IGlobalVariableForConnection) { try { const oauthRedirectUrlField = $.app.fields.find( (field: IField) => field.key == 'oAuthRedirectUrl' @@ -10,18 +14,10 @@ export default async function createAuthData($: any) { const callbackUrl = oauthRedirectUrlField.value; - const requestData = { - url: `${$.app.baseUrl}/oauth/request_token`, + const response = await generateRequest($, { + requestPath: '/oauth/request_token', method: 'POST', data: { oauth_callback: callbackUrl }, - }; - - const authHeader = oauthClient($).toHeader( - oauthClient($).authorize(requestData) - ); - - const response = await $.http.post(`/oauth/request_token`, null, { - headers: { ...authHeader }, }); const responseData = Object.fromEntries(new URLSearchParams(response.data)); diff --git a/packages/backend/src/apps/twitter2/auth/is-still-verified.ts b/packages/backend/src/apps/twitter2/auth/is-still-verified.ts index dbbdac52..cf2d9a4c 100644 --- a/packages/backend/src/apps/twitter2/auth/is-still-verified.ts +++ b/packages/backend/src/apps/twitter2/auth/is-still-verified.ts @@ -1,6 +1,7 @@ +import { IGlobalVariableForConnection } from '@automatisch/types'; import getCurrentUser from '../common/get-current-user'; -const isStillVerified = async ($: any) => { +const isStillVerified = async ($: IGlobalVariableForConnection) => { try { await getCurrentUser($); return true; diff --git a/packages/backend/src/apps/twitter2/auth/verify-credentials.ts b/packages/backend/src/apps/twitter2/auth/verify-credentials.ts index 6747045a..332efdbb 100644 --- a/packages/backend/src/apps/twitter2/auth/verify-credentials.ts +++ b/packages/backend/src/apps/twitter2/auth/verify-credentials.ts @@ -1,7 +1,10 @@ -const verifyCredentials = async ($: any) => { +import { IGlobalVariableForConnection } from '@automatisch/types'; +import { URLSearchParams } from 'url'; + +const verifyCredentials = async ($: IGlobalVariableForConnection) => { try { const response = await $.http.post( - `/oauth/access_token?oauth_verifier=${$.auth.oauthVerifier}&oauth_token=${$.auth.accessToken}`, + `/oauth/access_token?oauth_verifier=${$.auth.data.oauthVerifier}&oauth_token=${$.auth.data.accessToken}`, null ); diff --git a/packages/backend/src/apps/twitter2/common/generate-request.ts b/packages/backend/src/apps/twitter2/common/generate-request.ts new file mode 100644 index 00000000..df6ee2dd --- /dev/null +++ b/packages/backend/src/apps/twitter2/common/generate-request.ts @@ -0,0 +1,39 @@ +import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; +import oauthClient from './oauth-client'; +import { Token } from 'oauth-1.0a'; + +type IGenereateRequestOptons = { + requestPath: string; + method: string; + data?: IJSONObject; +}; + +const generateRequest = async ( + $: IGlobalVariableForConnection, + options: IGenereateRequestOptons +) => { + const { requestPath, method, data } = options; + + const token: Token = { + key: $.auth.data.accessToken as string, + secret: $.auth.data.accessSecret as string, + }; + + const requestData = { + url: `${$.app.baseUrl}${requestPath}`, + method, + data, + }; + + const authHeader = oauthClient($).toHeader( + oauthClient($).authorize(requestData, token) + ); + + const response = await $.http.post(`/oauth/request_token`, null, { + headers: { ...authHeader }, + }); + + return response; +}; + +export default generateRequest; diff --git a/packages/backend/src/apps/twitter2/common/get-current-user.ts b/packages/backend/src/apps/twitter2/common/get-current-user.ts new file mode 100644 index 00000000..12dfd7b5 --- /dev/null +++ b/packages/backend/src/apps/twitter2/common/get-current-user.ts @@ -0,0 +1,14 @@ +import { IGlobalVariableForConnection } from '@automatisch/types'; +import generateRequest from './generate-request'; + +const getCurrentUser = async ($: IGlobalVariableForConnection) => { + const response = await generateRequest($, { + requestPath: '/2/users/me', + method: 'GET', + }); + + const currentUser = response.data.data; + return currentUser; +}; + +export default getCurrentUser; diff --git a/packages/backend/src/apps/twitter2/common/get-user-by-username.ts b/packages/backend/src/apps/twitter2/common/get-user-by-username.ts new file mode 100644 index 00000000..e7e35d16 --- /dev/null +++ b/packages/backend/src/apps/twitter2/common/get-user-by-username.ts @@ -0,0 +1,25 @@ +import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; +import generateRequest from './generate-request'; + +const getUserByUsername = async ( + $: IGlobalVariableForConnection, + username: string +) => { + const response = await generateRequest($, { + requestPath: `/2/users/by/username/${username}`, + method: 'GET', + }); + + if (response.data.errors) { + const errorMessages = response.data.errors + .map((error: IJSONObject) => error.detail) + .join(' '); + + throw new Error(`Error occured while fetching user data: ${errorMessages}`); + } + + const user = response.data.data; + return user; +}; + +export default getUserByUsername; diff --git a/packages/backend/src/apps/twitter2/common/get-user-tweets.ts b/packages/backend/src/apps/twitter2/common/get-user-tweets.ts new file mode 100644 index 00000000..219820e7 --- /dev/null +++ b/packages/backend/src/apps/twitter2/common/get-user-tweets.ts @@ -0,0 +1,54 @@ +import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; +import { URLSearchParams } from 'url'; +import omitBy from 'lodash/omitBy'; +import isEmpty from 'lodash/isEmpty'; +import generateRequest from './generate-request'; + +const getUserTweets = async ( + $: IGlobalVariableForConnection, + userId: string, + lastInternalId?: string +) => { + let response; + const tweets: IJSONObject[] = []; + + do { + const params: IJSONObject = { + since_id: lastInternalId, + pagination_token: response?.data?.meta?.next_token, + }; + + const queryParams = new URLSearchParams(omitBy(params, isEmpty)); + + const requestPath = `/2/users/${userId}/tweets${ + queryParams.toString() ? `?${queryParams.toString()}` : '' + }`; + + response = await generateRequest($, { + requestPath, + method: 'GET', + }); + + if (response.data.meta.result_count > 0) { + response.data.data.forEach((tweet: IJSONObject) => { + if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) { + tweets.push(tweet); + } else { + return; + } + }); + } + } while (response.data.meta.next_token && lastInternalId); + + if (response.data.errors) { + const errorMessages = response.data.errors + .map((error: IJSONObject) => error.detail) + .join(' '); + + throw new Error(`Error occured while fetching user data: ${errorMessages}`); + } + + return tweets; +}; + +export default getUserTweets; diff --git a/packages/backend/src/apps/twitter2/common/oauth-client/index.ts b/packages/backend/src/apps/twitter2/common/oauth-client.ts similarity index 62% rename from packages/backend/src/apps/twitter2/common/oauth-client/index.ts rename to packages/backend/src/apps/twitter2/common/oauth-client.ts index 7ddc7398..d4a59b2e 100644 --- a/packages/backend/src/apps/twitter2/common/oauth-client/index.ts +++ b/packages/backend/src/apps/twitter2/common/oauth-client.ts @@ -1,10 +1,11 @@ +import { IGlobalVariableForConnection } from '@automatisch/types'; import crypto from 'crypto'; import OAuth from 'oauth-1.0a'; -const oauthClient = ($: any) => { +const oauthClient = ($: IGlobalVariableForConnection) => { const consumerData = { - key: $.auth.consumerKey as string, - secret: $.auth.consumerSecret as string, + key: $.auth.data.consumerKey as string, + secret: $.auth.data.consumerSecret as string, }; return new OAuth({ diff --git a/packages/backend/src/apps/twitter2/triggers/my-tweets/index.ts b/packages/backend/src/apps/twitter2/triggers/my-tweets/index.ts new file mode 100644 index 00000000..55cc9dbc --- /dev/null +++ b/packages/backend/src/apps/twitter2/triggers/my-tweets/index.ts @@ -0,0 +1,37 @@ +import { IGlobalVariableForConnection } from '@automatisch/types'; +import getCurrentUser from '../../common/get-current-user'; +import getUserByUsername from '../../common/get-user-by-username'; +import getUserTweets from '../../common/get-user-tweets'; + +export default { + name: 'My Tweets', + key: 'myTweets', + pollInterval: 15, + description: 'Will be triggered when you tweet something new.', + substeps: [ + { + key: 'chooseConnection', + name: 'Choose connection', + }, + { + key: 'testStep', + name: 'Test trigger', + }, + ], + + async run($: IGlobalVariableForConnection) { + return this.getTweets($, await $.db.flow.lastInternalId()); + }, + + async testRun($: IGlobalVariableForConnection) { + return this.getTweets($); + }, + + async getTweets($: IGlobalVariableForConnection, lastInternalId?: string) { + const { username } = await getCurrentUser($); + const user = await getUserByUsername($, username); + + const tweets = await getUserTweets($, user.id, lastInternalId); + return tweets; + }, +}; diff --git a/packages/backend/src/helpers/global-variable/connection.ts b/packages/backend/src/helpers/global-variable/connection.ts index 45751283..6913592d 100644 --- a/packages/backend/src/helpers/global-variable/connection.ts +++ b/packages/backend/src/helpers/global-variable/connection.ts @@ -1,5 +1,6 @@ import createHttpClient from '../http-client'; import Connection from '../../models/connection'; +import Flow from '../../models/flow'; import { IJSONObject, IApp, @@ -8,7 +9,8 @@ import { const prepareGlobalVariableForConnection = ( connection: Connection, - appData: IApp + appData: IApp, + flow?: Flow ): IGlobalVariableForConnection => { return { auth: { @@ -24,6 +26,9 @@ const prepareGlobalVariableForConnection = ( }, app: appData, http: createHttpClient({ baseURL: appData.baseUrl }), + db: { + flow: flow, + }, }; }; diff --git a/packages/backend/src/models/execution-step.ts b/packages/backend/src/models/execution-step.ts index 2f4c45ee..2d01e7fb 100644 --- a/packages/backend/src/models/execution-step.ts +++ b/packages/backend/src/models/execution-step.ts @@ -3,14 +3,15 @@ import Base from './base'; import Execution from './execution'; import Step from './step'; import Telemetry from '../helpers/telemetry'; +import { IJSONObject } from '@automatisch/types'; class ExecutionStep extends Base { id!: string; executionId!: string; stepId!: string; - dataIn!: Record; - dataOut!: Record; - errorDetails: Record; + dataIn!: IJSONObject; + dataOut!: IJSONObject; + errorDetails: IJSONObject; status = 'failure'; step: Step; @@ -23,7 +24,7 @@ class ExecutionStep extends Base { id: { type: 'string', format: 'uuid' }, executionId: { type: 'string', format: 'uuid' }, stepId: { type: 'string' }, - dataIn: { type: 'object' }, + dataIn: { type: ['object', 'null'] }, dataOut: { type: ['object', 'null'] }, status: { type: 'string', enum: ['success', 'failure'] }, errorDetails: { type: ['object', 'null'] }, diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index 388c477c..db536431 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -10,7 +10,7 @@ class Flow extends Base { name!: string; userId!: string; active: boolean; - steps?: [Step]; + steps: Step[]; published_at: string; static tableName = 'flows'; diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts index 61579f40..dcd9d58d 100644 --- a/packages/backend/src/models/step.ts +++ b/packages/backend/src/models/step.ts @@ -20,7 +20,7 @@ class Step extends Base { parameters: Record; connection?: Connection; flow: Flow; - executionSteps?: [ExecutionStep]; + executionSteps: ExecutionStep[]; static tableName = 'steps'; diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index f753b889..35600ba1 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -10,9 +10,9 @@ class User extends Base { id!: string; email!: string; password!: string; - connections?: [Connection]; - flows?: [Flow]; - steps?: [Step]; + connections?: Connection[]; + flows?: Flow[]; + steps?: Step[]; static tableName = 'users'; diff --git a/packages/backend/src/services/processor.ts b/packages/backend/src/services/processor.ts index 21d58db6..6d2efeca 100644 --- a/packages/backend/src/services/processor.ts +++ b/packages/backend/src/services/processor.ts @@ -112,8 +112,8 @@ class Processor { await execution.$relatedQuery('executionSteps').insertAndFetch({ stepId: id, status: 'failure', - dataIn: computedParameters, - dataOut: null, + dataIn: null, + dataOut: computedParameters, errorDetails: fetchedActionData.error, }); diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index b9cbacae..b9cb17ba 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -47,21 +47,21 @@ export interface IExecution { export interface IStep { id: string; - name: string; + name?: string; flowId: string; - key: string; - appKey: string; + key?: string; + appKey?: string; iconUrl: string; type: 'action' | 'trigger'; - connectionId: string; + connectionId?: string; status: string; position: number; parameters: Record; - connection: Partial; + connection?: Partial; flow: IFlow; executionSteps: IExecutionStep[]; // FIXME: remove this property once execution steps are properly exposed via queries - output: IJSONObject; + output?: IJSONObject; appData?: IApp; } @@ -202,6 +202,9 @@ export type IGlobalVariableForConnection = { }; app: IApp; http: IHttpClient; + db: { + flow: IFlow; + }; }; declare module 'axios' { diff --git a/packages/web/src/components/ChooseAppAndEventSubstep/index.tsx b/packages/web/src/components/ChooseAppAndEventSubstep/index.tsx index e54d1d3b..15f557ec 100644 --- a/packages/web/src/components/ChooseAppAndEventSubstep/index.tsx +++ b/packages/web/src/components/ChooseAppAndEventSubstep/index.tsx @@ -15,7 +15,7 @@ import FlowSubstepTitle from 'components/FlowSubstepTitle'; import type { IApp, IStep, ISubstep } from '@automatisch/types'; type ChooseAppAndEventSubstepProps = { - substep: ISubstep, + substep: ISubstep; expanded?: boolean; onExpand: () => void; onCollapse: () => void; @@ -24,14 +24,17 @@ type ChooseAppAndEventSubstepProps = { step: IStep; }; -const optionGenerator = (app: IApp): { label: string; value: string; } => ({ +const optionGenerator = (app: IApp): { label: string; value: string } => ({ label: app.name as string, value: app.key as string, }); -const getOption = (options: Record[], appKey: IApp["key"]) => options.find(option => option.value === appKey as string) || null; +const getOption = (options: Record[], appKey?: IApp['key']) => + options.find((option) => option.value === (appKey as string)) || null; -function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.ReactElement { +function ChooseAppAndEventSubstep( + props: ChooseAppAndEventSubstepProps +): React.ReactElement { const { substep, expanded = false, @@ -47,59 +50,74 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R const isTrigger = step.type === 'trigger'; - const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }}); + const { data } = useQuery(GET_APPS, { + variables: { onlyWithTriggers: isTrigger }, + }); const apps: IApp[] = data?.getApps; const app = apps?.find((currentApp: IApp) => currentApp.key === step.appKey); - const appOptions = React.useMemo(() => apps?.map((app) => optionGenerator(app)), [apps]); + const appOptions = React.useMemo( + () => apps?.map((app) => optionGenerator(app)), + [apps] + ); const actionsOrTriggers = isTrigger ? app?.triggers : app?.actions; - const actionOptions = React.useMemo(() => actionsOrTriggers?.map((trigger) => optionGenerator(trigger)) ?? [], [app?.key]); - const selectedActionOrTrigger = actionsOrTriggers?.find((actionOrTrigger) => actionOrTrigger.key === step?.key) || null; + const actionOptions = React.useMemo( + () => actionsOrTriggers?.map((trigger) => optionGenerator(trigger)) ?? [], + [app?.key] + ); + const selectedActionOrTrigger = + actionsOrTriggers?.find( + (actionOrTrigger) => actionOrTrigger.key === step?.key + ) || null; - const { - name, - } = substep; + const { name } = substep; const valid: boolean = !!step.key && !!step.appKey; // placeholders - const onEventChange = React.useCallback((event: React.SyntheticEvent, selectedOption: unknown) => { - if (typeof selectedOption === 'object') { - // TODO: try to simplify type casting below. - const typedSelectedOption = selectedOption as { value: string }; - const option: { value: string } = typedSelectedOption; - const eventKey = option?.value as string; + const onEventChange = React.useCallback( + (event: React.SyntheticEvent, selectedOption: unknown) => { + if (typeof selectedOption === 'object') { + // TODO: try to simplify type casting below. + const typedSelectedOption = selectedOption as { value: string }; + const option: { value: string } = typedSelectedOption; + const eventKey = option?.value as string; - if (step.key !== eventKey) { - onChange({ - step: { - ...step, - key: eventKey, - }, - }); + if (step.key !== eventKey) { + onChange({ + step: { + ...step, + key: eventKey, + }, + }); + } } - } - }, [step, onChange]); + }, + [step, onChange] + ); - const onAppChange = React.useCallback((event: React.SyntheticEvent, selectedOption: unknown) => { - if (typeof selectedOption === 'object') { - // TODO: try to simplify type casting below. - const typedSelectedOption = selectedOption as { value: string }; - const option: { value: string } = typedSelectedOption; - const appKey = option?.value as string; + const onAppChange = React.useCallback( + (event: React.SyntheticEvent, selectedOption: unknown) => { + if (typeof selectedOption === 'object') { + // TODO: try to simplify type casting below. + const typedSelectedOption = selectedOption as { value: string }; + const option: { value: string } = typedSelectedOption; + const appKey = option?.value as string; - if (step.appKey !== appKey) { - onChange({ - step: { - ...step, - key: '', - appKey, - parameters: {}, - }, - }); + if (step.appKey !== appKey) { + onChange({ + step: { + ...step, + key: '', + appKey, + parameters: {}, + }, + }); + } } - } - }, [step, onChange]); + }, + [step, onChange] + ); const onToggle = expanded ? onCollapse : onExpand; @@ -112,14 +130,26 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R valid={valid} /> - + } + renderInput={(params) => ( + + )} value={getOption(appOptions, step.appKey)} onChange={onAppChange} /> @@ -137,7 +167,12 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R disableClearable disabled={editorContext.readOnly} options={actionOptions} - renderInput={(params) => } + renderInput={(params) => ( + + )} value={getOption(actionOptions, step.key)} onChange={onEventChange} /> @@ -147,7 +182,9 @@ function ChooseAppAndEventSubstep(props: ChooseAppAndEventSubstepProps): React.R {isTrigger && selectedActionOrTrigger?.pollInterval && ( { return array.slice(0, length); -} +}; const Suggestions = (props: SuggestionsProps) => { - const { - data, - onSuggestionClick = () => null, - } = props; + const { data, onSuggestionClick = () => null } = props; const [current, setCurrent] = React.useState(0); const [listLength, setListLength] = React.useState(SHORT_LIST_LENGTH); @@ -40,41 +37,43 @@ const Suggestions = (props: SuggestionsProps) => { const collapseList = () => { setListLength(SHORT_LIST_LENGTH); - } + }; React.useEffect(() => { setListLength(SHORT_LIST_LENGTH); - }, [current]) + }, [current]); return ( - - Variables - + + + Variables + + {data.map((option: IStep, index: number) => ( <> setCurrent((currentIndex) => currentIndex === index ? null : index)} - sx={{ py: 0.5, }} + onClick={() => + setCurrent((currentIndex) => + currentIndex === index ? null : index + ) + } + sx={{ py: 0.5 }} > - + - {!!option.output?.length && ( - current === index ? : - )} + {!!option.output?.length && + (current === index ? : )} - - {getPartialArray(option.output as any || [], listLength) - .map((suboption: any, index: number) => ( + + {getPartialArray((option.output as any) || [], listLength).map( + (suboption: any, index: number) => ( { primaryTypographyProps={{ variant: 'subtitle1', title: 'Property name', - sx: { fontWeight: 700 } + sx: { fontWeight: 700 }, }} secondary={suboption.value || ''} secondaryTypographyProps={{ @@ -95,24 +94,18 @@ const Suggestions = (props: SuggestionsProps) => { }} /> - )) - } + ) + )} - {option.output?.length > listLength && ( - )} {listLength === Infinity && ( - )} @@ -122,6 +115,6 @@ const Suggestions = (props: SuggestionsProps) => { ); -} +}; export default Suggestions; diff --git a/packages/web/src/components/PowerInput/data.ts b/packages/web/src/components/PowerInput/data.ts index 02198916..a62606e3 100644 --- a/packages/web/src/components/PowerInput/data.ts +++ b/packages/web/src/components/PowerInput/data.ts @@ -1,6 +1,7 @@ import type { IStep } from '@automatisch/types'; -const joinBy = (delimiter = '.', ...args: string[]) => args.filter(Boolean).join(delimiter); +const joinBy = (delimiter = '.', ...args: string[]) => + args.filter(Boolean).join(delimiter); const process = (data: any, parentKey?: any, index?: number): any[] => { if (typeof data !== 'object') { @@ -8,14 +9,19 @@ const process = (data: any, parentKey?: any, index?: number): any[] => { { name: `${parentKey}.${index}`, value: data, - } - ] + }, + ]; } const entries = Object.entries(data); return entries.flatMap(([name, value]) => { - const fullName = joinBy('.', parentKey, (index as number)?.toString(), name); + const fullName = joinBy( + '.', + parentKey, + (index as number)?.toString(), + name + ); if (Array.isArray(value)) { return value.flatMap((item, index) => process(item, fullName, index)); @@ -25,10 +31,12 @@ const process = (data: any, parentKey?: any, index?: number): any[] => { return process(value, fullName); } - return [{ - name: fullName, - value, - }]; + return [ + { + name: fullName, + value, + }, + ]; }); }; @@ -39,12 +47,17 @@ export const processStepWithExecutions = (steps: IStep[]): any[] => { .filter((step: IStep) => { const hasExecutionSteps = !!step.executionSteps?.length; - return hasExecutionSteps + return hasExecutionSteps; }) .map((step: IStep, index: number) => ({ id: step.id, // TODO: replace with step.name once introduced - name: `${index + 1}. ${step.appKey?.charAt(0)?.toUpperCase() + step.appKey?.slice(1)}`, - output: process(step.executionSteps?.[0]?.dataOut || {}, `step.${step.id}`), + name: `${index + 1}. ${ + (step.appKey || '').charAt(0)?.toUpperCase() + step.appKey?.slice(1) + }`, + output: process( + step.executionSteps?.[0]?.dataOut || {}, + `step.${step.id}` + ), })); };