datajunction-ui
Version:
DataJunction UI
231 lines (221 loc) • 7.49 kB
JSX
import { useContext, useEffect, useRef, useState } from 'react';
import * as React from 'react';
import DJClientContext from '../../providers/djclient';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
import { ConfigField } from './MaterializationConfigField';
import LoadingIcon from '../../icons/LoadingIcon';
export default function AddMaterializationPopover({ node, onSubmit }) {
const djClient = useContext(DJClientContext).DataJunctionAPI;
const [popoverAnchor, setPopoverAnchor] = useState(false);
const [options, setOptions] = useState([]);
const [jobs, setJobs] = useState([]);
const timePartitionColumns = node.columns.filter(col => col.partition);
const ref = useRef(null);
// Fetch materialization options only when the popover is opened (once), not on
// every materialization-tab load — the options are only used inside the form.
const fetchedRef = useRef(false);
useEffect(() => {
if (!popoverAnchor || fetchedRef.current) return;
fetchedRef.current = true;
const fetchData = async () => {
const options = await djClient.materializationInfo();
setOptions(options);
const allowedJobs = options.job_types?.filter(job =>
job.allowed_node_types.includes(node.type),
);
setJobs(allowedJobs);
};
fetchData().catch(console.error);
}, [popoverAnchor, djClient, node.type]);
useEffect(() => {
const handleClickOutside = event => {
if (ref.current && !ref.current.contains(event.target)) {
setPopoverAnchor(false);
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, []);
const materialize = async (values, setStatus) => {
const config = {};
config.spark = values.spark_config;
config.lookback_window = values.lookback_window;
if (!values.job_type) {
values.job_type = 'spark_sql';
}
const { status, json } =
values.job_type === 'druid_cube'
? await djClient.materializeCube(
values.node,
values.job_type,
values.strategy,
values.schedule,
values.lookback_window,
)
: await djClient.materialize(
values.node,
values.job_type,
values.strategy,
values.schedule,
config,
);
if (status === 200 || status === 201) {
setStatus({ success: json.message });
window.location.reload();
} else {
setStatus({
failure: `${json.message}`,
});
}
};
const configureMaterialization = async (
values,
{ setSubmitting, setStatus },
) => {
await materialize(values, setStatus).then(_ => {
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
setSubmitting(false);
});
};
return (
<>
<button
className="edit_button"
aria-label="AddMaterialization"
tabIndex="0"
onClick={() => {
setPopoverAnchor(!popoverAnchor);
}}
>
<span className="add_node">+ Add Materialization</span>
</button>
<div
className="fade modal-backdrop in"
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
></div>
<div
className="centerPopover"
role="dialog"
aria-label="client-code"
style={{
display: popoverAnchor === false ? 'none' : 'block',
width: '50%',
}}
ref={ref}
>
<Formik
initialValues={{
node: node?.name,
job_type: node?.type === 'cube' ? 'druid_cube' : 'spark_sql',
strategy:
timePartitionColumns.length == 1 ? 'incremental_time' : 'full',
schedule: '@daily',
lookback_window: '1 DAY',
spark_config: {
'spark.executor.memory': '16g',
'spark.memory.fraction': '0.3',
},
}}
onSubmit={configureMaterialization}
>
{function Render({ isSubmitting, status, setFieldValue }) {
return (
<Form>
<h2>Configure Materialization for the Latest Node Version</h2>
{displayMessageAfterSubmit(status)}
{node.type === 'cube' ? (
<span data-testid="job-type">
<label htmlFor="job_type">Job Type</label>
<Field as="select" name="job_type">
<>
<option key={'druid_cube'} value={'druid_cube'}>
Druid
</option>
</>
</Field>
<br />
<br />
</span>
) : (
''
)}
<input
hidden={true}
name="node"
value={node?.name}
readOnly={true}
/>
<span data-testid="edit-partition">
<label htmlFor="strategy">Strategy</label>
<Field as="select" name="strategy" id="strategy">
<>
<option key={'full'} value={'full'}>
Full
</option>
<option
key={'incremental_time'}
value={'incremental_time'}
>
Incremental Time
</option>
</>
</Field>
</span>
<br />
<br />
<label htmlFor="schedule">Schedule</label>
<Field
type="text"
name="schedule"
id="schedule"
placeholder="Cron"
default="@daily"
/>
<br />
<br />
<div className="DescriptionInput">
<ErrorMessage name="description" component="span" />
<label htmlFor="lookback_window">Lookback Window</label>
<Field
type="text"
name="lookback_window"
id="lookback_window"
placeholder="1 DAY"
default="1 DAY"
/>
</div>
<br />
<ConfigField
value={{
'spark.executor.memory': '16g',
'spark.memory.fraction': '0.3',
}}
/>
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
marginTop: '20px',
}}
>
<button
className="add_node"
type="submit"
aria-label="SaveEditColumn"
aria-hidden="false"
disabled={isSubmitting}
>
{isSubmitting ? <LoadingIcon /> : 'Save'}
</button>
</div>
</Form>
);
}}
</Formik>
</div>
</>
);
}