Add missing ui parts (#257)
* Add jobs resource. Add simple test view for sync list * Add schema to onboarding and create source * Show logs for sync history * Add jobList polling. Small ui fixes * Add log time * Fix style config * code style Co-authored-by: cgardens <giardina.charles@gmail.com>
This commit is contained in:
@@ -402,8 +402,11 @@ ij_xml_space_inside_empty_tag = false
|
||||
ij_xml_text_wrap = normal
|
||||
ij_xml_use_custom_settings = false
|
||||
|
||||
[{*.ats,*.ts}]
|
||||
ij_continuation_indent_size = 4
|
||||
[{*.ats,*.ts,*.tsx}]
|
||||
max_line_length = off
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
ij_continuation_indent_size = 2
|
||||
ij_typescript_align_imports = false
|
||||
ij_typescript_align_multiline_array_initializer_expression = false
|
||||
ij_typescript_align_multiline_binary_operation = false
|
||||
|
||||
34
dataline-webapp/package-lock.json
generated
34
dataline-webapp/package-lock.json
generated
@@ -5550,6 +5550,11 @@
|
||||
"resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-3.1.0.tgz",
|
||||
"integrity": "sha1-H80D29UEudvuK5B4yFpfHH08wtM="
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.8.35",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.35.tgz",
|
||||
"integrity": "sha512-isAbIEenO4ilm6f8cpqvgjZCsuerDAz2Kb7ri201AiNn58aqXuaLJEnCtfIMdCvERZHNGRY5lDMTr/jdAnKSWQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
@@ -7906,12 +7911,11 @@
|
||||
}
|
||||
},
|
||||
"framesync": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/framesync/-/framesync-4.0.4.tgz",
|
||||
"integrity": "sha512-mdP0WvVHe0/qA62KG2LFUAOiWLng5GLpscRlwzBxu2VXOp6B8hNs5C5XlFigsMgrfDrr2YbqTsgdWZTc4RXRMQ==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/framesync/-/framesync-4.1.0.tgz",
|
||||
"integrity": "sha512-MmgZ4wCoeVxNbx2xp5hN/zPDCbLSKiDt4BbbslK7j/pM2lg5S0vhTNv1v8BCVb99JPIo6hXBFdwzU7Q4qcAaoQ==",
|
||||
"requires": {
|
||||
"hey-listen": "^1.0.8",
|
||||
"tslib": "^1.10.0"
|
||||
"hey-listen": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"fresh": {
|
||||
@@ -12621,9 +12625,9 @@
|
||||
}
|
||||
},
|
||||
"popmotion": {
|
||||
"version": "8.7.3",
|
||||
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-8.7.3.tgz",
|
||||
"integrity": "sha512-OcpS/V9sCJjrKiVfp3JB5kp5SBqefZ4RvM9GBLYgv0YbULxv9S5METP9ueVJxSClR3yrfFEY2pLWTWKLn/EUfg==",
|
||||
"version": "8.7.5",
|
||||
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-8.7.5.tgz",
|
||||
"integrity": "sha512-p85l/qrOuLTQZ+aGfyB8cqOzDRWgiSFN941jSrj9CsWeJzUn+jiGSWJ50sr59gWAZ8TKIvqdDowqFlScc0NEyw==",
|
||||
"requires": {
|
||||
"@popmotion/easing": "^1.0.1",
|
||||
"@popmotion/popcorn": "^0.4.4",
|
||||
@@ -12684,14 +12688,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.17.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz",
|
||||
"integrity": "sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw=="
|
||||
"version": "10.17.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.32.tgz",
|
||||
"integrity": "sha512-EUq+cjH/3KCzQHikGnNbWAGe548IFLSm93Vl8xA7EuYEEATiyOVDyEVuGkowL7c9V69FF/RiZSAOCFPApMs/ig=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17037,8 +17036,7 @@
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw=="
|
||||
},
|
||||
"uncontrollable": {
|
||||
"version": "5.1.0",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^5.14.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.12.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.8",
|
||||
"dayjs": "^1.8.35",
|
||||
"formik": "2.1.5",
|
||||
"query-string": "^6.13.1",
|
||||
"react": "^16.12.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import styled from "styled-components";
|
||||
import * as yup from "yup";
|
||||
import { Field, FieldProps, Form, Formik } from "formik";
|
||||
@@ -7,11 +7,15 @@ import { Field, FieldProps, Form, Formik } from "formik";
|
||||
import LabeledDropDown from "../LabeledDropDown";
|
||||
import FrequencyConfig from "../../data/FrequencyConfig.json";
|
||||
import BottomBlock from "./components/BottomBlock";
|
||||
import Label from "../Label";
|
||||
import TreeView, { INode } from "../TreeView/TreeView";
|
||||
|
||||
type IProps = {
|
||||
className?: string;
|
||||
schema: INode[];
|
||||
errorMessage?: React.ReactNode;
|
||||
onSubmit: (values: { frequency: string }) => void;
|
||||
onSubmit: (values: { frequency: string }, checkedState: string[]) => void;
|
||||
initialCheckedSchema: Array<string>;
|
||||
};
|
||||
|
||||
const SmallLabeledDropDown = styled(LabeledDropDown)`
|
||||
@@ -22,6 +26,14 @@ const FormContainer = styled(Form)`
|
||||
padding: 22px 27px 23px 24px;
|
||||
`;
|
||||
|
||||
const TreeViewContainer = styled.div`
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.greyColor0};
|
||||
margin-bottom: 29px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const connectionValidationSchema = yup.object().shape({
|
||||
frequency: yup.string().required("form.empty.error")
|
||||
});
|
||||
@@ -29,7 +41,9 @@ const connectionValidationSchema = yup.object().shape({
|
||||
const FrequencyForm: React.FC<IProps> = ({
|
||||
onSubmit,
|
||||
className,
|
||||
errorMessage
|
||||
errorMessage,
|
||||
schema,
|
||||
initialCheckedSchema
|
||||
}) => {
|
||||
const formatMessage = useIntl().formatMessage;
|
||||
const dropdownData = React.useMemo(
|
||||
@@ -51,6 +65,9 @@ const FrequencyForm: React.FC<IProps> = ({
|
||||
[formatMessage]
|
||||
);
|
||||
|
||||
const [checkedState, setCheckedState] = useState(initialCheckedSchema);
|
||||
const onCheckAction = (data: Array<string>) => setCheckedState(data);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
@@ -60,13 +77,23 @@ const FrequencyForm: React.FC<IProps> = ({
|
||||
validateOnChange={true}
|
||||
validationSchema={connectionValidationSchema}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
await onSubmit(values);
|
||||
await onSubmit(values, checkedState);
|
||||
setSubmitting(false);
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, setFieldValue, isValid, dirty }) => (
|
||||
<FormContainer className={className}>
|
||||
<Field name="serviceType">
|
||||
<Label message={<FormattedMessage id="form.dataSync.message" />}>
|
||||
<FormattedMessage id="form.dataSync" />
|
||||
</Label>
|
||||
<TreeViewContainer>
|
||||
<TreeView
|
||||
nodes={schema}
|
||||
onCheck={onCheckAction}
|
||||
checked={checkedState}
|
||||
/>
|
||||
</TreeViewContainer>
|
||||
<Field name="frequency">
|
||||
{({ field }: FieldProps<string>) => (
|
||||
<SmallLabeledDropDown
|
||||
{...field}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const Row = styled.div`
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
|
||||
@@ -16,9 +16,10 @@ const Badge = styled.div<IProps>`
|
||||
box-shadow: 0 1px 2px ${({ theme }) => theme.shadowColor};
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
padding-top: 1px;
|
||||
padding-top: 4px;
|
||||
color: ${({ theme }) => theme.whiteColor};
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { faChevronRight, faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import "react-checkbox-tree/lib/react-checkbox-tree.css";
|
||||
|
||||
type INode = {
|
||||
export type INode = {
|
||||
value: string;
|
||||
label: string;
|
||||
children?: Array<INode>;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
const config: {
|
||||
ui: { helpLink: string; docsLink: string; workspaceId: string };
|
||||
apiUrl: string;
|
||||
ui: { helpLink: string; docsLink: string; workspaceId: string };
|
||||
apiUrl: string;
|
||||
} = {
|
||||
ui: {
|
||||
helpLink: "https://dataline.io/",
|
||||
docsLink: "https://docs.dataline.io",
|
||||
workspaceId: "5ae6b09b-fdec-41af-aaf7-7d94cfc33ef6"
|
||||
},
|
||||
apiUrl: process.env.REACT_APP_API_URL || `${window.location.protocol}//${window.location.hostname}:8001/api/v1/`
|
||||
ui: {
|
||||
helpLink: "https://dataline.io/",
|
||||
docsLink: "https://docs.dataline.io",
|
||||
workspaceId: "5ae6b09b-fdec-41af-aaf7-7d94cfc33ef6"
|
||||
},
|
||||
apiUrl:
|
||||
process.env.REACT_APP_API_URL ||
|
||||
`${window.location.protocol}//${window.location.hostname}:8001/api/v1/`
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
43
dataline-webapp/src/core/helpers.tsx
Normal file
43
dataline-webapp/src/core/helpers.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { SyncSchema } from "./resources/Schema";
|
||||
|
||||
export const constructInitialSchemaState = (syncSchema: SyncSchema) => {
|
||||
const initialChecked: Array<string> = [];
|
||||
syncSchema.tables.map(item =>
|
||||
item.columns.forEach(column =>
|
||||
column.selected
|
||||
? initialChecked.push(`${item.name}_${column.name}`)
|
||||
: null
|
||||
)
|
||||
);
|
||||
|
||||
const formSyncSchema = syncSchema.tables.map((item: any) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
children: item.columns.map((column: any) => ({
|
||||
value: `${item.name}_${column.name}`,
|
||||
label: column.name
|
||||
}))
|
||||
}));
|
||||
|
||||
return {
|
||||
formSyncSchema,
|
||||
initialChecked
|
||||
};
|
||||
};
|
||||
|
||||
export const constructNewSchema = (
|
||||
syncSchema: SyncSchema,
|
||||
checkedState: string[]
|
||||
) => {
|
||||
const newSyncSchema = {
|
||||
tables: syncSchema.tables.map(item => ({
|
||||
...item,
|
||||
columns: item.columns.map(column => ({
|
||||
...column,
|
||||
selected: checkedState.includes(`${item.name}_${column.name}`)
|
||||
}))
|
||||
}))
|
||||
};
|
||||
|
||||
return newSyncSchema;
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export type ScheduleProperties = {
|
||||
|
||||
export type SyncSchemaColumn = {
|
||||
name: string;
|
||||
selected: string;
|
||||
selected: boolean;
|
||||
type: string;
|
||||
};
|
||||
|
||||
@@ -142,4 +142,20 @@ export default class ConnectionResource extends BaseResource
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static syncShape<T extends typeof Resource>(this: T) {
|
||||
return {
|
||||
...super.detailShape(),
|
||||
getFetchKey: (params: any) =>
|
||||
"POST " + this.url(params) + "/sync" + JSON.stringify(params),
|
||||
fetch: async (
|
||||
params: Readonly<Record<string, string | number>>
|
||||
): Promise<any> => {
|
||||
await this.fetch("post", `${this.url(params)}/sync`, params);
|
||||
return {
|
||||
connectionId: params.connectionId
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
52
dataline-webapp/src/core/resources/Job.ts
Normal file
52
dataline-webapp/src/core/resources/Job.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Resource, FetchOptions } from "rest-hooks";
|
||||
import BaseResource from "./BaseResource";
|
||||
import JobLogsResource from "./JobLogs";
|
||||
|
||||
export interface Job {
|
||||
id: number;
|
||||
configType: string;
|
||||
configId: string;
|
||||
createdAt: number;
|
||||
startedAt: number;
|
||||
updatedAt: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default class JobResource extends BaseResource implements Job {
|
||||
readonly id: number = 0;
|
||||
readonly configType: string = "";
|
||||
readonly configId: string = "";
|
||||
readonly createdAt: number = 0;
|
||||
readonly startedAt: number = 0;
|
||||
readonly updatedAt: number = 0;
|
||||
readonly status: string = "";
|
||||
|
||||
pk() {
|
||||
return this.id?.toString();
|
||||
}
|
||||
|
||||
static urlRoot = "jobs";
|
||||
|
||||
static getFetchOptions(): FetchOptions {
|
||||
return {
|
||||
pollFrequency: 2500 // every 2,5 seconds
|
||||
};
|
||||
}
|
||||
|
||||
static listShape<T extends typeof Resource>(this: T) {
|
||||
return {
|
||||
...super.listShape(),
|
||||
schema: { jobs: [this.asSchema()] }
|
||||
};
|
||||
}
|
||||
|
||||
static detailShape<T extends typeof Resource>(this: T) {
|
||||
return {
|
||||
...super.detailShape(),
|
||||
schema: {
|
||||
job: this.asSchema(),
|
||||
logs: JobLogsResource.asSchema()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
17
dataline-webapp/src/core/resources/JobLogs.ts
Normal file
17
dataline-webapp/src/core/resources/JobLogs.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import BaseResource from "./BaseResource";
|
||||
|
||||
export interface JobLogs {
|
||||
stdout: string[];
|
||||
stderr: string[];
|
||||
}
|
||||
|
||||
export default class JobLogsResource extends BaseResource implements JobLogs {
|
||||
readonly stdout: string[] = [];
|
||||
readonly stderr: string[] = [];
|
||||
|
||||
pk() {
|
||||
return "";
|
||||
}
|
||||
|
||||
static urlRoot = "jobs";
|
||||
}
|
||||
53
dataline-webapp/src/core/resources/Schema.ts
Normal file
53
dataline-webapp/src/core/resources/Schema.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Resource } from "rest-hooks";
|
||||
import BaseResource from "./BaseResource";
|
||||
|
||||
export type SyncSchemaColumn = {
|
||||
name: string;
|
||||
selected: boolean;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type SyncSchema = {
|
||||
tables: {
|
||||
name: string;
|
||||
columns: SyncSchemaColumn[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export interface Schema {
|
||||
id: string;
|
||||
schema: SyncSchema;
|
||||
}
|
||||
|
||||
export default class SchemaResource extends BaseResource implements Schema {
|
||||
readonly schema: SyncSchema = { tables: [] };
|
||||
readonly id: string = "";
|
||||
|
||||
pk() {
|
||||
return this.id?.toString();
|
||||
}
|
||||
|
||||
static urlRoot = "source_implementations";
|
||||
|
||||
static schemaShape<T extends typeof Resource>(this: T) {
|
||||
return {
|
||||
...super.detailShape(),
|
||||
getFetchKey: (params: { sourceImplementationId: string }) =>
|
||||
`POST /source_implementations/discover_schema` + JSON.stringify(params),
|
||||
fetch: async (
|
||||
params: Readonly<Record<string, string | number>>
|
||||
): Promise<any> => {
|
||||
const result = await this.fetch(
|
||||
"post",
|
||||
`${this.url(params)}/discover_schema`,
|
||||
params
|
||||
);
|
||||
return {
|
||||
schema: result?.schema,
|
||||
id: params.sourceImplementationId
|
||||
};
|
||||
},
|
||||
schema: this.asSchema()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,7 @@
|
||||
{
|
||||
"text": "manual",
|
||||
"value": "manual",
|
||||
"config": {
|
||||
"units": 0,
|
||||
"timeUnit": "minutes"
|
||||
}
|
||||
"config": null
|
||||
},
|
||||
{
|
||||
"text": "5 min",
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
"form.frequency": "Sync frequency",
|
||||
"form.frequency.placeholder": "Select a frequency",
|
||||
"form.frequency.message": "Set how often Dataline attempts to replicate data.",
|
||||
"form.dataSync": "Select the data you want to sync",
|
||||
"form.dataSync.message": "You’ll be able to change this later on.",
|
||||
"form.cancel": "Cancel",
|
||||
"form.delete": "Delete",
|
||||
"form.saveChanges": "Save changes",
|
||||
@@ -99,6 +101,15 @@
|
||||
"sources.dataDelete": "No data will be deleted from your source.",
|
||||
"sources.deleteSource": "Delete this source",
|
||||
"sources.deleteConfirm": "Confirm source deletion",
|
||||
"sources.failed": "Failed",
|
||||
"sources.completed": "Completed",
|
||||
"sources.pending": "Pending",
|
||||
"sources.running": "Running",
|
||||
"sources.cancelled": "Cancelled",
|
||||
"sources.emptyLogs": "Empty data",
|
||||
"sources.hour": "{hour}h ",
|
||||
"sources.minute": "{minute}m ",
|
||||
"sources.second": "{second}s",
|
||||
"sources.deleteModalText": "This can not be un-done without a full re-sync. Note that:\n - All past logs and configurations will be deleted\n - Updates of new data will stop\n - No existing data in the destination will be altered",
|
||||
|
||||
"destination.destinationSettings": "Destination Settings"
|
||||
|
||||
@@ -18,6 +18,7 @@ import FrequencyConfig from "../../data/FrequencyConfig.json";
|
||||
import { Routes } from "../routes";
|
||||
import useRouter from "../../components/hooks/useRouterHook";
|
||||
import { Source } from "../../core/resources/Source";
|
||||
import { SyncSchema } from "../../core/resources/Schema";
|
||||
|
||||
const Content = styled.div`
|
||||
width: 100%;
|
||||
@@ -171,6 +172,7 @@ const OnboardingPage: React.FC = () => {
|
||||
|
||||
const onSubmitConnectionStep = async (values: {
|
||||
frequency: string;
|
||||
syncSchema: SyncSchema;
|
||||
source?: Source;
|
||||
}) => {
|
||||
const frequencyData = FrequencyConfig.find(
|
||||
@@ -190,7 +192,8 @@ const OnboardingPage: React.FC = () => {
|
||||
destinations[0].destinationImplementationId,
|
||||
syncMode: "full_refresh",
|
||||
schedule: frequencyData?.config,
|
||||
status: "active"
|
||||
status: "active",
|
||||
syncSchema: values.syncSchema
|
||||
},
|
||||
[
|
||||
[
|
||||
@@ -240,6 +243,7 @@ const OnboardingPage: React.FC = () => {
|
||||
currentSourceId={sources[0].sourceId}
|
||||
currentDestinationId={destinations[0].destinationId}
|
||||
errorStatus={errorStatusRequest}
|
||||
sourceImplementationId={sources[0].sourceImplementationId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { useResource } from "rest-hooks";
|
||||
|
||||
import FrequencyForm from "../../../components/FrequencyForm";
|
||||
import SchemaResource, { SyncSchema } from "../../../core/resources/Schema";
|
||||
import {
|
||||
constructInitialSchemaState,
|
||||
constructNewSchema
|
||||
} from "../../../core/helpers";
|
||||
|
||||
type IProps = {
|
||||
onSubmit: (values: { frequency: string; syncSchema: SyncSchema }) => void;
|
||||
errorMessage?: React.ReactNode;
|
||||
sourceImplementationId: string;
|
||||
};
|
||||
|
||||
const ConnectionStep: React.FC<IProps> = ({
|
||||
onSubmit,
|
||||
errorMessage,
|
||||
sourceImplementationId
|
||||
}) => {
|
||||
const { schema } = useResource(SchemaResource.schemaShape(), {
|
||||
sourceImplementationId
|
||||
});
|
||||
|
||||
const { formSyncSchema, initialChecked } = constructInitialSchemaState(
|
||||
schema
|
||||
);
|
||||
|
||||
const onSubmitForm = async (
|
||||
values: { frequency: string },
|
||||
checkedState: string[]
|
||||
) => {
|
||||
const newSchema = constructNewSchema(schema, checkedState);
|
||||
await onSubmit({ ...values, syncSchema: newSchema });
|
||||
};
|
||||
|
||||
return (
|
||||
<FrequencyForm
|
||||
onSubmit={onSubmitForm}
|
||||
errorMessage={errorMessage}
|
||||
schema={formSyncSchema}
|
||||
initialCheckedSchema={initialChecked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionStep;
|
||||
@@ -1,25 +1,39 @@
|
||||
import React from "react";
|
||||
import React, { Suspense } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useResource } from "rest-hooks";
|
||||
import styled from "styled-components";
|
||||
|
||||
import ContentCard from "../../../components/ContentCard";
|
||||
import ConnectionBlock from "../../../components/ConnectionBlock";
|
||||
import FrequencyForm from "../../../components/FrequencyForm";
|
||||
import ConnectionForm from "./ConnectionForm";
|
||||
import SourceResource, { Source } from "../../../core/resources/Source";
|
||||
import DestinationResource from "../../../core/resources/Destination";
|
||||
import Spinner from "../../../components/Spinner";
|
||||
import { SyncSchema } from "../../../core/resources/Schema";
|
||||
|
||||
type IProps = {
|
||||
onSubmit: (values: { frequency: string; source: Source }) => void;
|
||||
onSubmit: (values: {
|
||||
frequency: string;
|
||||
syncSchema: SyncSchema;
|
||||
source: Source;
|
||||
}) => void;
|
||||
currentSourceId: string;
|
||||
currentDestinationId: string;
|
||||
sourceImplementationId: string;
|
||||
errorStatus?: number;
|
||||
};
|
||||
|
||||
const SpinnerBlock = styled.div`
|
||||
margin: 40px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const ConnectionStep: React.FC<IProps> = ({
|
||||
onSubmit,
|
||||
currentSourceId,
|
||||
currentDestinationId,
|
||||
errorStatus
|
||||
errorStatus,
|
||||
sourceImplementationId
|
||||
}) => {
|
||||
const currentSource = useResource(SourceResource.detailShape(), {
|
||||
sourceId: currentSourceId
|
||||
@@ -28,7 +42,10 @@ const ConnectionStep: React.FC<IProps> = ({
|
||||
destinationId: currentDestinationId
|
||||
});
|
||||
|
||||
const onSubmitStep = async (values: { frequency: string }) => {
|
||||
const onSubmitStep = async (values: {
|
||||
frequency: string;
|
||||
syncSchema: SyncSchema;
|
||||
}) => {
|
||||
await onSubmit({
|
||||
...values,
|
||||
source: {
|
||||
@@ -51,7 +68,19 @@ const ConnectionStep: React.FC<IProps> = ({
|
||||
itemTo={{ name: currentDestination.name }}
|
||||
/>
|
||||
<ContentCard title={<FormattedMessage id="onboarding.setConnection" />}>
|
||||
<FrequencyForm onSubmit={onSubmitStep} errorMessage={errorMessage} />
|
||||
<Suspense
|
||||
fallback={
|
||||
<SpinnerBlock>
|
||||
<Spinner />
|
||||
</SpinnerBlock>
|
||||
}
|
||||
>
|
||||
<ConnectionForm
|
||||
onSubmit={onSubmitStep}
|
||||
errorMessage={errorMessage}
|
||||
sourceImplementationId={sourceImplementationId}
|
||||
/>
|
||||
</Suspense>
|
||||
</ContentCard>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,7 @@ const Content = styled.div<{ enabled?: boolean }>`
|
||||
|
||||
const FrequencyCell: React.FC<IProps> = ({ value, enabled }) => {
|
||||
const cellText = FrequencyConfig.find(
|
||||
item =>
|
||||
item.config.units === value?.units &&
|
||||
item.config.timeUnit === value?.timeUnit
|
||||
item => JSON.stringify(item.config) === JSON.stringify(value)
|
||||
);
|
||||
return <Content enabled={enabled}>{cellText?.text || ""}</Content>;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import SourceImplementationResource, {
|
||||
} from "../../../../core/resources/SourceImplementation";
|
||||
import FrequencyConfig from "../../../../data/FrequencyConfig.json";
|
||||
import ConnectionResource from "../../../../core/resources/Connection";
|
||||
import { SyncSchema } from "../../../../core/resources/Schema";
|
||||
|
||||
const Content = styled.div`
|
||||
max-width: 638px;
|
||||
@@ -104,7 +105,10 @@ const CreateSourcePage: React.FC = () => {
|
||||
setErrorStatusRequest(e.status);
|
||||
}
|
||||
};
|
||||
const onSubmitConnectionStep = async (values: { frequency: string }) => {
|
||||
const onSubmitConnectionStep = async (values: {
|
||||
frequency: string;
|
||||
syncSchema: SyncSchema;
|
||||
}) => {
|
||||
const frequencyData = FrequencyConfig.find(
|
||||
item => item.value === values.frequency
|
||||
);
|
||||
@@ -126,7 +130,8 @@ const CreateSourcePage: React.FC = () => {
|
||||
currentDestination.destinationImplementationId,
|
||||
syncMode: "full_refresh",
|
||||
schedule: frequencyData?.config,
|
||||
status: "active"
|
||||
status: "active",
|
||||
syncSchema: values.syncSchema
|
||||
},
|
||||
[
|
||||
[
|
||||
@@ -164,6 +169,9 @@ const CreateSourcePage: React.FC = () => {
|
||||
onSubmit={onSubmitConnectionStep}
|
||||
destination={destination}
|
||||
sourceId={currentSourceImplementation?.sourceId || ""}
|
||||
sourceImplementationId={
|
||||
currentSourceImplementation?.sourceImplementationId || ""
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import { useResource } from "rest-hooks";
|
||||
|
||||
import FrequencyForm from "../../../../../components/FrequencyForm";
|
||||
import SchemaResource, {
|
||||
SyncSchema
|
||||
} from "../../../../../core/resources/Schema";
|
||||
import {
|
||||
constructInitialSchemaState,
|
||||
constructNewSchema
|
||||
} from "../../../../../core/helpers";
|
||||
|
||||
type IProps = {
|
||||
onSubmit: (values: { frequency: string; syncSchema: SyncSchema }) => void;
|
||||
sourceImplementationId: string;
|
||||
};
|
||||
|
||||
const ConnectionStep: React.FC<IProps> = ({
|
||||
onSubmit,
|
||||
sourceImplementationId
|
||||
}) => {
|
||||
const { schema } = useResource(SchemaResource.schemaShape(), {
|
||||
sourceImplementationId
|
||||
});
|
||||
|
||||
const { formSyncSchema, initialChecked } = constructInitialSchemaState(
|
||||
schema
|
||||
);
|
||||
|
||||
const onSubmitForm = async (
|
||||
values: { frequency: string },
|
||||
checkedState: string[]
|
||||
) => {
|
||||
const newSchema = constructNewSchema(schema, checkedState);
|
||||
await onSubmit({ ...values, syncSchema: newSchema });
|
||||
};
|
||||
|
||||
return (
|
||||
<FrequencyForm
|
||||
onSubmit={onSubmitForm}
|
||||
schema={formSyncSchema}
|
||||
initialCheckedSchema={initialChecked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionStep;
|
||||
@@ -1,27 +1,38 @@
|
||||
import React from "react";
|
||||
import React, { Suspense } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useResource } from "rest-hooks";
|
||||
import styled from "styled-components";
|
||||
|
||||
import ConnectionBlock from "../../../../../components/ConnectionBlock";
|
||||
import ContentCard from "../../../../../components/ContentCard";
|
||||
import FrequencyForm from "../../../../../components/FrequencyForm";
|
||||
import { Destination } from "../../../../../core/resources/Destination";
|
||||
import SourceResource from "../../../../../core/resources/Source";
|
||||
import Spinner from "../../../../../components/Spinner";
|
||||
import { SyncSchema } from "../../../../../core/resources/Schema";
|
||||
import ConnectionForm from "./ConnectionForm";
|
||||
|
||||
type IProps = {
|
||||
onSubmit: (values: { frequency: string }) => void;
|
||||
onSubmit: (values: { frequency: string; syncSchema: SyncSchema }) => void;
|
||||
destination: Destination;
|
||||
sourceId: string;
|
||||
sourceImplementationId: string;
|
||||
};
|
||||
|
||||
const SpinnerBlock = styled.div`
|
||||
margin: 40px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const CreateSourcePage: React.FC<IProps> = ({
|
||||
onSubmit,
|
||||
destination,
|
||||
sourceId
|
||||
sourceId,
|
||||
sourceImplementationId
|
||||
}) => {
|
||||
const source = useResource(SourceResource.detailShape(), {
|
||||
sourceId
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectionBlock
|
||||
@@ -29,7 +40,18 @@ const CreateSourcePage: React.FC<IProps> = ({
|
||||
itemTo={{ name: destination.name }}
|
||||
/>
|
||||
<ContentCard title={<FormattedMessage id="onboarding.setConnection" />}>
|
||||
<FrequencyForm onSubmit={onSubmit} />
|
||||
<Suspense
|
||||
fallback={
|
||||
<SpinnerBlock>
|
||||
<Spinner />
|
||||
</SpinnerBlock>
|
||||
}
|
||||
>
|
||||
<ConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
sourceImplementationId={sourceImplementationId}
|
||||
/>
|
||||
</Suspense>
|
||||
</ContentCard>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { Suspense, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useFetcher, useResource } from "rest-hooks";
|
||||
import styled from "styled-components";
|
||||
|
||||
import PageTitle from "../../../../components/PageTitle";
|
||||
import Breadcrumbs from "../../../../components/Breadcrumbs";
|
||||
@@ -11,6 +12,19 @@ import StatusView from "./components/StatusView";
|
||||
import SettingsView from "./components/SettingsView";
|
||||
import SchemaView from "./components/SchemaView";
|
||||
import ConnectionResource from "../../../../core/resources/Connection";
|
||||
import LoadingPage from "../../../../components/LoadingPage";
|
||||
|
||||
const Content = styled.div`
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 67px);
|
||||
margin-top: -17px;
|
||||
padding-top: 17px;
|
||||
`;
|
||||
|
||||
const Page = styled.div`
|
||||
overflow-y: hidden;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const SourceItemPage: React.FC = () => {
|
||||
const { query, push, history } = useRouter();
|
||||
@@ -47,7 +61,7 @@ const SourceItemPage: React.FC = () => {
|
||||
name: <FormattedMessage id="sidebar.sources" />,
|
||||
onClick: onClickBack
|
||||
},
|
||||
{ name: connection.name }
|
||||
{ name: connection.source?.name }
|
||||
];
|
||||
|
||||
const onChangeStatus = async () => {
|
||||
@@ -82,7 +96,7 @@ const SourceItemPage: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Page>
|
||||
<PageTitle
|
||||
withLine
|
||||
title={<Breadcrumbs data={breadcrumbsData} />}
|
||||
@@ -95,8 +109,10 @@ const SourceItemPage: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{renderStep()}
|
||||
</>
|
||||
<Content>
|
||||
<Suspense fallback={<LoadingPage />}>{renderStep()}</Suspense>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import React, { Suspense, useState } from "react";
|
||||
import pose from "react-pose";
|
||||
import {
|
||||
FormattedMessage,
|
||||
FormattedDateParts,
|
||||
FormattedTimeParts
|
||||
} from "react-intl";
|
||||
import styled from "styled-components";
|
||||
import dayjs from "dayjs";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { Job } from "../../../../../core/resources/Job";
|
||||
import { Row, Cell } from "../../../../../components/SimpleTableComponents";
|
||||
import StatusIcon from "../../../../../components/StatusIcon";
|
||||
import Spinner from "../../../../../components/Spinner";
|
||||
import JobLogs from "./JobLogs";
|
||||
|
||||
type IProps = {
|
||||
job: Job;
|
||||
};
|
||||
|
||||
const Item = styled.div<{ isFailed: boolean }>`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.greyColor20};
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, isFailed }) =>
|
||||
isFailed ? theme.dangerTransparentColor : theme.greyColor0};
|
||||
}
|
||||
`;
|
||||
|
||||
const MainInfo = styled(Row)<{
|
||||
isOpen?: boolean;
|
||||
isFailed?: boolean;
|
||||
}>`
|
||||
cursor: pointer;
|
||||
height: 59px;
|
||||
padding: 10px 44px 10px 40px;
|
||||
border-bottom: 1px solid
|
||||
${({ theme, isOpen, isFailed }) =>
|
||||
!isOpen
|
||||
? "none"
|
||||
: isFailed
|
||||
? theme.dangerTransparentColor
|
||||
: theme.greyColor20};
|
||||
`;
|
||||
|
||||
const Title = styled.div<{ isFailed: boolean }>`
|
||||
position: relative;
|
||||
color: ${({ theme, isFailed }) =>
|
||||
isFailed ? theme.dangerColor : theme.darkPrimaryColor};
|
||||
`;
|
||||
|
||||
const ErrorSign = styled(StatusIcon)`
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
`;
|
||||
|
||||
const LoadLogs = styled.div`
|
||||
background: ${({ theme }) => theme.whiteColor};
|
||||
text-align: center;
|
||||
padding: 6px 0;
|
||||
min-height: 58px;
|
||||
`;
|
||||
|
||||
const CompletedTime = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
color: ${({ theme }) => theme.greyColor40};
|
||||
`;
|
||||
|
||||
const Arrow = styled.div<{
|
||||
isOpen?: boolean;
|
||||
isFailed?: boolean;
|
||||
}>`
|
||||
transform: ${({ isOpen }) => !isOpen && "rotate(-90deg)"};
|
||||
transition: 0.3s;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
color: ${({ theme, isFailed }) =>
|
||||
isFailed ? theme.dangerColor : theme.darkPrimaryColor};
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
top: calc(50% - 11px);
|
||||
opacity: 0;
|
||||
|
||||
div:hover > div > &,
|
||||
div:hover > div > div > &,
|
||||
div:hover > & {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const itemConfig = {
|
||||
open: {
|
||||
height: "auto",
|
||||
opacity: 1,
|
||||
transition: "tween"
|
||||
},
|
||||
closed: {
|
||||
height: "1px",
|
||||
opacity: 0,
|
||||
transition: "tween"
|
||||
}
|
||||
};
|
||||
|
||||
const ContentWrapper = pose.div(itemConfig);
|
||||
|
||||
const JobItem: React.FC<IProps> = ({ job }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const onExpand = () => setIsOpen(!isOpen);
|
||||
|
||||
const date1 = dayjs(job.createdAt * 1000);
|
||||
const date2 = dayjs(job.updatedAt * 1000);
|
||||
const hours = Math.abs(date2.diff(date1, "hour"));
|
||||
const minutes = Math.abs(date2.diff(date1, "minute")) - hours * 60;
|
||||
const seconds =
|
||||
Math.abs(date2.diff(date1, "second")) - minutes * 60 - hours * 3600;
|
||||
|
||||
const isFailed = job.status === "failed";
|
||||
return (
|
||||
<Item isFailed={isFailed}>
|
||||
<MainInfo isOpen={isOpen} isFailed={isFailed} onClick={onExpand}>
|
||||
<Cell>
|
||||
<Title isFailed={isFailed}>
|
||||
{isFailed && <ErrorSign />}
|
||||
<FormattedMessage id={`sources.${job.status}`} />
|
||||
</Title>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<FormattedTimeParts
|
||||
value={job.createdAt * 1000}
|
||||
hour="numeric"
|
||||
minute="2-digit"
|
||||
>
|
||||
{parts => (
|
||||
<span>{`${parts[0].value}:${parts[2].value}${parts[4].value} `}</span>
|
||||
)}
|
||||
</FormattedTimeParts>
|
||||
<FormattedDateParts
|
||||
value={job.createdAt * 1000}
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
>
|
||||
{parts => <span>{`${parts[0].value}/${parts[2].value}`}</span>}
|
||||
</FormattedDateParts>
|
||||
<CompletedTime>
|
||||
{hours ? (
|
||||
<FormattedMessage id="sources.hour" values={{ hour: hours }} />
|
||||
) : null}
|
||||
{hours || minutes ? (
|
||||
<FormattedMessage
|
||||
id="sources.minute"
|
||||
values={{ minute: minutes }}
|
||||
/>
|
||||
) : null}
|
||||
<FormattedMessage
|
||||
id="sources.second"
|
||||
values={{ second: seconds }}
|
||||
/>
|
||||
</CompletedTime>
|
||||
<Arrow isOpen={isOpen} isFailed={isFailed}>
|
||||
<FontAwesomeIcon icon={faAngleDown} />
|
||||
</Arrow>
|
||||
</Cell>
|
||||
</MainInfo>
|
||||
<ContentWrapper pose={!isOpen ? "closed" : "open"} withParent={false}>
|
||||
<div>
|
||||
<Suspense
|
||||
fallback={
|
||||
<LoadLogs>
|
||||
<Spinner />
|
||||
</LoadLogs>
|
||||
}
|
||||
>
|
||||
{isOpen && <JobLogs id={job.id} />}
|
||||
</Suspense>
|
||||
</div>
|
||||
</ContentWrapper>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobItem;
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import { useResource } from "rest-hooks";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import styled from "styled-components";
|
||||
|
||||
import JobResource from "../../../../../core/resources/Job";
|
||||
|
||||
type IProps = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
const Logs = styled.div`
|
||||
padding: 20px 42px;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
color: ${({ theme }) => theme.darkPrimaryColor};
|
||||
`;
|
||||
|
||||
const JobLogs: React.FC<IProps> = ({ id }) => {
|
||||
const job = useResource(JobResource.detailShape(), { id });
|
||||
|
||||
if (!job.logs.stderr.length) {
|
||||
return (
|
||||
<Logs>
|
||||
<FormattedMessage id="sources.emptyLogs" />
|
||||
</Logs>
|
||||
);
|
||||
}
|
||||
|
||||
// now logs always empty. TODO: Test ui with data
|
||||
return (
|
||||
<Logs>
|
||||
{job.logs.stderr.map((item, key) => (
|
||||
<div key={`log-${id}-${key}`}>{item}</div>
|
||||
))}
|
||||
</Logs>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobLogs;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import JobItem from "./JobItem";
|
||||
import { Job } from "../../../../../core/resources/Job";
|
||||
|
||||
type IProps = {
|
||||
jobs: Job[];
|
||||
};
|
||||
|
||||
const Content = styled.div``;
|
||||
|
||||
const JobsList: React.FC<IProps> = ({ jobs }) => {
|
||||
return (
|
||||
<Content>
|
||||
{jobs.map(item => (
|
||||
<JobItem key={item.id} job={item} />
|
||||
))}
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobsList;
|
||||
@@ -10,6 +10,10 @@ import ConnectionResource, {
|
||||
SyncSchema
|
||||
} from "../../../../../core/resources/Connection";
|
||||
import EmptySyncHistory from "./EmptySyncHistory";
|
||||
import {
|
||||
constructInitialSchemaState,
|
||||
constructNewSchema
|
||||
} from "../../../../../core/helpers";
|
||||
|
||||
type IProps = {
|
||||
connectionId: string;
|
||||
@@ -37,29 +41,14 @@ const SchemaView: React.FC<IProps> = ({
|
||||
connectionStatus
|
||||
}) => {
|
||||
const updateConnection = useFetcher(ConnectionResource.updateShape());
|
||||
const initialChecked: Array<string> = [];
|
||||
syncSchema.tables.map(item =>
|
||||
item.columns.forEach(column =>
|
||||
column.selected ? initialChecked.push(column.name) : null
|
||||
)
|
||||
const { formSyncSchema, initialChecked } = useMemo(
|
||||
() => constructInitialSchemaState(syncSchema),
|
||||
[syncSchema]
|
||||
);
|
||||
|
||||
const [disabledButtons, setDisabledButtons] = useState(true);
|
||||
const [checkedState, setCheckedState] = useState(initialChecked);
|
||||
|
||||
const formSyncSchema = useMemo(
|
||||
() =>
|
||||
syncSchema.tables.map((item: any) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
children: item.columns.map((column: any) => ({
|
||||
value: column.name,
|
||||
label: column.name
|
||||
}))
|
||||
})),
|
||||
[syncSchema.tables]
|
||||
);
|
||||
|
||||
const onCheckAction = (data: Array<string>) => {
|
||||
setDisabledButtons(JSON.stringify(data) === JSON.stringify(initialChecked));
|
||||
setCheckedState(data);
|
||||
@@ -71,15 +60,7 @@ const SchemaView: React.FC<IProps> = ({
|
||||
};
|
||||
const onSubmit = async () => {
|
||||
setDisabledButtons(true);
|
||||
const newSyncSchema = {
|
||||
tables: syncSchema.tables.map(item => ({
|
||||
...item,
|
||||
columns: item.columns.map(column => ({
|
||||
...column,
|
||||
selected: checkedState.includes(column.name)
|
||||
}))
|
||||
}))
|
||||
};
|
||||
const newSyncSchema = constructNewSchema(syncSchema, checkedState);
|
||||
|
||||
await updateConnection(
|
||||
{},
|
||||
|
||||
@@ -41,9 +41,7 @@ const SettingsView: React.FC<IProps> = ({ sourceData }) => {
|
||||
);
|
||||
|
||||
const schedule = FrequencyConfig.find(
|
||||
item =>
|
||||
item.config.units === sourceData.schedule?.units &&
|
||||
item.config.timeUnit === sourceData.schedule?.timeUnit
|
||||
item => JSON.stringify(item.config) === JSON.stringify(sourceData.schedule)
|
||||
);
|
||||
|
||||
const onSubmit = async (values: {
|
||||
@@ -81,6 +79,7 @@ const SettingsView: React.FC<IProps> = ({ sourceData }) => {
|
||||
{},
|
||||
{
|
||||
...sourceData,
|
||||
schedule: frequencyData?.config,
|
||||
source: {
|
||||
...sourceData.source,
|
||||
name: values.name,
|
||||
|
||||
@@ -67,9 +67,7 @@ const StatusMainInfo: React.FC<IProps> = ({ sourceData, onEnabledChange }) => {
|
||||
});
|
||||
|
||||
const cellText = FrequencyConfig.find(
|
||||
item =>
|
||||
item.config.units === sourceData.schedule?.units &&
|
||||
item.config.timeUnit === sourceData.schedule?.timeUnit
|
||||
item => JSON.stringify(item.config) === JSON.stringify(sourceData.schedule)
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,12 +3,17 @@ import { FormattedMessage } from "react-intl";
|
||||
import styled from "styled-components";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faRedoAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useFetcher, useSubscription, useResource } from "rest-hooks";
|
||||
|
||||
import ContentCard from "../../../../../components/ContentCard";
|
||||
import Button from "../../../../../components/Button";
|
||||
import StatusMainInfo from "./StatusMainInfo";
|
||||
import EmptySyncHistory from "./EmptySyncHistory";
|
||||
import { Connection } from "../../../../../core/resources/Connection";
|
||||
import ConnectionResource, {
|
||||
Connection
|
||||
} from "../../../../../core/resources/Connection";
|
||||
import JobResource from "../../../../../core/resources/Job";
|
||||
import JobsList from "./JobsList";
|
||||
|
||||
type IProps = {
|
||||
sourceData: Connection;
|
||||
@@ -20,6 +25,10 @@ const Content = styled.div`
|
||||
margin: 18px auto;
|
||||
`;
|
||||
|
||||
const StyledContentCard = styled(ContentCard)`
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -38,25 +47,41 @@ const SyncButton = styled(Button)`
|
||||
`;
|
||||
|
||||
const StatusView: React.FC<IProps> = ({ sourceData, onEnabledChange }) => {
|
||||
const { jobs } = useResource(JobResource.listShape(), {
|
||||
configId: sourceData.connectionId,
|
||||
configType: "sync"
|
||||
});
|
||||
useSubscription(JobResource.listShape(), {
|
||||
configId: sourceData.connectionId,
|
||||
configType: "sync"
|
||||
});
|
||||
|
||||
const SyncConnection = useFetcher(ConnectionResource.syncShape());
|
||||
|
||||
const onSync = () =>
|
||||
SyncConnection({
|
||||
connectionId: sourceData.connectionId
|
||||
});
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<StatusMainInfo
|
||||
sourceData={sourceData}
|
||||
onEnabledChange={onEnabledChange}
|
||||
/>
|
||||
<ContentCard
|
||||
<StyledContentCard
|
||||
title={
|
||||
<Title>
|
||||
<FormattedMessage id={"sources.syncHistory"} />
|
||||
<SyncButton>
|
||||
<SyncButton onClick={onSync}>
|
||||
<TryArrow icon={faRedoAlt} />
|
||||
<FormattedMessage id={"sources.syncNow"} />
|
||||
</SyncButton>
|
||||
</Title>
|
||||
}
|
||||
>
|
||||
<EmptySyncHistory />
|
||||
</ContentCard>
|
||||
{jobs.length ? <JobsList jobs={jobs} /> : <EmptySyncHistory />}
|
||||
</StyledContentCard>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user