Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat snippet destinations #217

Merged
merged 3 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class GetTemplates {
totalUploads: template.totalUploads,
totalInvalidRecords: template.totalInvalidRecords,
totalRecords: template.totalRecords,
authHeaderName: template.authHeaderName,
}));
}
}
7 changes: 7 additions & 0 deletions apps/api/src/app/template/dtos/template-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ export class TemplateResponseDto {
@IsOptional()
callbackUrl: string;

@ApiProperty({
description: 'Name of the header that gets sent to the application',
})
@IsString()
@IsOptional()
authHeaderName: string;

@ApiProperty({
description: 'Size of data in rows that gets sent to the application',
})
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/app/template/dtos/update-template-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export class UpdateTemplateRequestDto {
@IsOptional()
callbackUrl?: string;

@ApiProperty({
description: 'Name of auth header to be sent to the application',
})
@IsString()
@IsOptional()
authHeaderName?: string;

@ApiProperty({
description: 'Size of data in rows that gets sent to the application',
format: 'number',
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/app/template/template.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export class TemplateController {
callbackUrl: body.callbackUrl,
chunkSize: body.chunkSize,
name: body.name,
authHeaderName: body.authHeaderName,
}),
templateId
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class GetTemplateDetails {
totalUploads: template.totalUploads,
totalInvalidRecords: template.totalInvalidRecords,
totalRecords: template.totalRecords,
authHeaderName: template.authHeaderName,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export class UpdateTemplateCommand extends BaseCommand {
@IsOptional()
callbackUrl?: string;

@IsString()
@IsOptional()
authHeaderName?: string;

@IsNumber({
allowNaN: false,
})
Expand Down
5 changes: 2 additions & 3 deletions apps/queue-manager/src/consumers/process-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,8 @@ export class ProcessFileConsumer extends BaseConsumer {
// Get template information
const templateData = await this.templateRepository.findById(
uploadata._templateId,
'_projectId callbackUrl chunkSize code'
'_projectId callbackUrl chunkSize code authHeaderName'
);
const projectData = await this.projectRepository.findById(templateData._projectId, 'authHeaderName');

return {
_templateId: uploadata._templateId,
Expand All @@ -236,7 +235,7 @@ export class ProcessFileConsumer extends BaseConsumer {
isInvalidRecords: uploadata._validDataFileId ? false : true,
invalidDataFilePath: (uploadata._invalidDataFileId as unknown as FileEntity)?.path,
page: 1,
authHeaderName: projectData.authHeaderName,
authHeaderName: templateData.authHeaderName,
authHeaderValue: uploadata.authHeaderValue,
processInvalidRecords: uploadata.processInvalidRecords,
validDataFilePath: (uploadata._validDataFileId as unknown as FileEntity)?.path,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/.example.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
API_BASE_URL=http://localhost:3000
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
DOCUMENTATION_REACT_PROPS_URL=https://docs.impler.io/widget/react-component#props
98 changes: 52 additions & 46 deletions apps/web/components/imports/Destination.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,66 @@
import { useState } from 'react';
import { Prism } from '@mantine/prism';
import { ITemplate } from '@impler/shared';
import { Code, Divider, Stack, Text } from '@mantine/core';

import { colors } from '@config';
import { REGULAR_EXPRESSIONS, colors } from '@config';
import { Input } from '@ui/input';
import { Button } from '@ui/button';
import { Select } from '@ui/select';
import { APIBlock } from '@ui/api-block';
import { SectionBlock } from '@ui/section-block';
import { useDestination } from '@hooks/useDestination';

export function Destination() {
const [type, setType] = useState<'webhook' | 'rest'>('webhook');
interface DestinationProps {
template: ITemplate;
}

export function Destination({ template }: DestinationProps) {
const { register, errors, onSubmit, isUpdateImportLoading } = useDestination({ template });

return (
<Stack style={{ width: '100%' }}>
<Select
data={[
{ label: 'Webhook Call', value: 'webhook' },
{ label: 'REST API', value: 'rest' },
]}
register={{
value: type,
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => setType(e.target.value as any),
}}
/>
<Divider variant="dotted" />
{type === 'webhook' && (
<Stack spacing="xs">
<Input placeholder="Webhook URL" />
<Input placeholder="Auth Header Name" />
<Button>Save</Button>
</Stack>
)}
{type === 'rest' && (
<Stack spacing="sm">
<APIBlock
method="GET"
title="GET valid data of imported file"
url="https://api.impler.io/v1/upload/{uploadId}/rows/valid"
/>
<APIBlock
method="GET"
title="Get Invalid data of imported file"
url="https://api.impler.io/v1/upload/{uploadId}/rows/invalid"
/>
<SectionBlock title="How to get uploadId?">
<Text style={{ lineHeight: '1.5rem', color: colors.TXTSecondaryDark }}>
You will get uploadId in <Code>onUploadComplete</Code> callback of <Code>@impler/react</Code> package, at
the time of import is completed.
</Text>
<Prism language="tsx">{`import { Import } from '@impler/react';
<>
<Text pb="sm">
To get imported data you can provide Webhook URL or You can call our API to get valid and invalid data.
</Text>
<SectionBlock title="Add Script">
<form onSubmit={onSubmit}>
<Stack spacing="xs">
<Input
type="url"
placeholder="Callback URL"
register={register('callbackUrl', {
pattern: REGULAR_EXPRESSIONS.URL,
})}
error={errors.callbackUrl ? 'Please enter valid URL' : undefined}
/>
<Input placeholder="Auth Header Name" register={register('authHeaderName')} />
<Button loading={isUpdateImportLoading} type="submit">
Save
</Button>
</Stack>
</form>
</SectionBlock>
<Divider my="sm" variant="dotted" />
<Stack spacing="sm">
<APIBlock
method="GET"
title="GET valid data of imported file"
url="https://api.impler.io/v1/upload/{uploadId}/rows/valid"
/>
<APIBlock
method="GET"
title="Get Invalid data of imported file"
url="https://api.impler.io/v1/upload/{uploadId}/rows/invalid"
/>
<SectionBlock title="How to get uploadId?">
<Text style={{ lineHeight: '1.5rem', color: colors.TXTSecondaryDark }}>
You will get uploadId in <Code>onUploadComplete</Code> callback of <Code>@impler/react</Code> package, at
the time of import is completed.
</Text>
<Prism language="tsx">{`import { Import } from '@impler/react';
\n<Button projectId="<PROJECT_ID>" template="<CODE_OR_ID>" onUploadComplete={(upload) => console.log(upload)}>
Import\n</Button>`}</Prism>
</SectionBlock>
</Stack>
)}
</Stack>
</SectionBlock>
</Stack>
</>
);
}
15 changes: 9 additions & 6 deletions apps/web/components/imports/Snippet.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Prism } from '@mantine/prism';
import { Code, Flex, Text } from '@mantine/core';

import { colors } from '@config';
import { CONSTANTS, colors } from '@config';
import { SectionBlock } from '@ui/section-block';

export function Snippet() {
interface SnippetProps {
projectId: string;
templateId: string;
}

export function Snippet({ projectId, templateId }: SnippetProps) {
return (
<Flex gap="sm" direction="column">
<Text>
Expand All @@ -15,9 +20,7 @@ export function Snippet() {
Copy & Paste this snippet to your code before the closing body tag. It will add impler variable in window, so
you can call its init and show method.
</Text>
<Prism language="markup">
{'<script type="text/javascript" src="https://embed.impler.io/embed.umd.min.js" async></script>'}
</Prism>
<Prism language="markup">{`<script type="text/javascript" src="${CONSTANTS.EMBED_URL}" async></script>`}</Prism>
</SectionBlock>

<SectionBlock title="Install the Package">
Expand All @@ -32,7 +35,7 @@ export function Snippet() {
Now add <Code>Import</Code> Button from <Code>@impler/react</Code> which opens the Widget.
</Text>
<Prism language="tsx">{`import { Import } from '@impler/react';
\n<Button projectId="<PROJECT_ID>" template="<CODE_OR_ID>">\nImport\n</Button>`}</Prism>
\n<Button projectId="${projectId}" templateId="${templateId}">\nImport\n</Button>`}</Prism>
<Text style={{ lineHeight: '1.5rem', color: colors.TXTSecondaryDark }}>
You can get to know about props on{' '}
<a href={process.env.DOCUMENTATION_REACT_PROPS_URL} target="_blank" rel="noreferrer">
Expand Down
5 changes: 5 additions & 0 deletions apps/web/config/constants.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const CONSTANTS = {
AUTH_COOKIE_NAME: 'authentication',
AUTHENTICATION_ERROR_CODE: 'AuthenticationError',
PROFILE_STORAGE_NAME: 'profile',
EMBED_URL: 'https://embed.impler.io/embed.umd.min.js',
};

export const VARIABLES = {
Expand Down Expand Up @@ -58,3 +59,7 @@ export const ROUTES = {
SIGNIN_ONBOARDING: '/signin/onboard',
IMPORTS: '/imports',
};

export const REGULAR_EXPRESSIONS = {
URL: /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm,
};
6 changes: 3 additions & 3 deletions apps/web/design-system/api-block/APIBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export function APIBlock({ method, title, url }: APIBlockProps) {

return (
<Stack spacing="sm" className={classes.root}>
<Text weight="bold" size="lg">
{title}
</Text>
<Group spacing="xs">
<Badge variant="filled" color="green" radius="xl" p="xs">
{method}
Expand All @@ -34,9 +37,6 @@ export function APIBlock({ method, title, url }: APIBlockProps) {
}
</CopyButton>
</Group>
<Text weight="bold" size="lg">
{title}
</Text>
</Stack>
);
}
13 changes: 12 additions & 1 deletion apps/web/design-system/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ interface InputProps {
placeholder?: string;
register?: any;
pattern?: string;
type?: 'text' | 'email' | 'number' | 'url';
}

export function Input({ required, disabled, error, placeholder, pattern, dataAutoFocus, register }: InputProps) {
export function Input({
required,
type = 'text',
disabled,
error,
placeholder,
pattern,
dataAutoFocus,
register,
}: InputProps) {
const { classes } = useStyles();

return (
<MantineInput
type={type}
data-autofocus={dataAutoFocus}
required={required}
placeholder={placeholder}
Expand Down
62 changes: 62 additions & 0 deletions apps/web/hooks/useDestination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { API_KEYS } from '@config';
import { IErrorObject, ITemplate } from '@impler/shared';
import { commonApi } from '@libs/api';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';

interface UseDestinationProps {
template: ITemplate;
}

interface UpdateDestinationData {
authHeaderName: string;
callbackUrl: string;
}

export function useDestination({ template }: UseDestinationProps) {
const queryClient = useQueryClient();
const {
register,
reset,
handleSubmit,
formState: { errors },
} = useForm<UpdateDestinationData>();
const { mutate: updateImport, isLoading: isUpdateImportLoading } = useMutation<
ITemplate,
IErrorObject,
UpdateDestinationData,
(string | undefined)[]
>(
[API_KEYS.TEMPLATE_UPDATE, template._id],
(data) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
commonApi<ITemplate>(API_KEYS.TEMPLATE_UPDATE as any, { parameters: [template._id], body: { ...data } }),
{
onSuccess: (data) => {
queryClient.setQueryData<ITemplate[]>([API_KEYS.TEMPLATES_LIST, template?._projectId], (oldData) =>
oldData?.map((item) => (item._id === data._id ? data : item))
);
queryClient.setQueryData<ITemplate>([API_KEYS.TEMPLATE_DETAILS, template._id], data);
},
}
);

useEffect(() => {
reset({
authHeaderName: template.authHeaderName,
callbackUrl: template.callbackUrl,
});
}, [template, reset]);

const onSubmit = (data: UpdateDestinationData) => {
updateImport(data);
};

return {
errors,
register,
isUpdateImportLoading,
onSubmit: handleSubmit(onSubmit),
};
}
4 changes: 2 additions & 2 deletions apps/web/pages/imports/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function ImportDetails({ template }: ImportDetailProps) {
{
value: 'snippet',
title: 'Snippet',
content: <Snippet />,
content: <Snippet templateId={template._id} projectId={template._projectId} />,
},
{
value: 'output',
Expand All @@ -85,7 +85,7 @@ export default function ImportDetails({ template }: ImportDetailProps) {
{
value: 'destination',
title: 'Destination',
content: <Destination />,
content: <Destination template={template} />,
},
]}
defaultValue="schema"
Expand Down
2 changes: 0 additions & 2 deletions libs/dal/src/repositories/project/project.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ export class ProjectEntity {

name: string;

authHeaderName: string;

_userId: string;
}
3 changes: 0 additions & 3 deletions libs/dal/src/repositories/project/project.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ const projectSchema = new Schema(
name: {
type: Schema.Types.String,
},
authHeaderName: {
type: Schema.Types.String,
},
_userId: {
type: Schema.Types.ObjectId,
ref: 'User',
Expand Down
Loading