Skip to content

Commit

Permalink
Merge branch 'master' into dynamic-cards
Browse files Browse the repository at this point in the history
  • Loading branch information
obgibson committed Jan 18, 2024
2 parents c0b868c + b16c114 commit 09668f4
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 66 deletions.
9 changes: 1 addition & 8 deletions src/components/Form/TimeRange.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useState } from 'react';
import styled from 'styled-components';
import Icon from '../Icon';
import { InputLabel } from './InputLabel';
Expand Down Expand Up @@ -40,15 +40,8 @@ const TimeRange: React.FC<TimeRangeProps> = ({ sectionLabel, onSubmit, initialVa
const [selectedPreset, setPreset] = useState<string>('none');
const { timezone } = useContext(TimezoneContext);

const startValue = initialValues && initialValues[0];
const endValue = initialValues && initialValues[1];

useOnKeyPress('Escape', () => setShow(false));

useEffect(() => {
setValues({ start: startValue || null, end: endValue || null });
}, [startValue, endValue]);

const HandleSubmit = () => {
onSubmit({
start: values.start,
Expand Down
17 changes: 9 additions & 8 deletions src/components/MFCard/CardIframe.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactEventHandler, forwardRef, useEffect, useState } from 'react';
import React, { ReactEventHandler, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { MESSAGE_NAME } from '../Plugins/PluginManager';
import { apiHttp } from '../../constants';
Expand All @@ -8,6 +8,7 @@ const CHECK_HEIGHT_INTERVAL = 1000;

type Props = {
path: string;
onLoad: (iframe: HTMLIFrameElement) => void;
};

const FALLBACK_HEIGHT = 750; // arbitrary height that should show enough
Expand All @@ -16,14 +17,11 @@ const FALLBACK_HEIGHT = 750; // arbitrary height that should show enough
// Render single card in iframe.
//

const CardIframe = forwardRef(function (
{ path }: Props,
ref: React.RefObject<HTMLIFrameElement> | ((instance: HTMLIFrameElement | null) => void) | null | undefined,
) {
// const ref = useRef<HTMLIFrameElement>(null);
const CardIframe = ({ path, onLoad }: Props) => {
const [elementHeight, setElementHeight] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const ref = useRef<HTMLIFrameElement>(null);

// Check iframe height every second in case it changes somehow.
useEffect(() => {
Expand Down Expand Up @@ -77,6 +75,9 @@ const CardIframe = forwardRef(function (

const handleIframeLoad = () => {
setLoading(false);
if (ref?.current) {
onLoad(ref?.current);
}
};

const handleIframeError: ReactEventHandler<HTMLIFrameElement> = (e) => {
Expand All @@ -85,7 +86,7 @@ const CardIframe = forwardRef(function (
};

return (
<div>
<div style={{ width: '100%' }}>
{error && <div>Something went wrong</div>}
{loading && (
<SpinnerContainer>
Expand All @@ -105,7 +106,7 @@ const CardIframe = forwardRef(function (
/>
</div>
);
});
};

const StyledCardIframe = styled.iframe`
width: 100%;
Expand Down
45 changes: 26 additions & 19 deletions src/components/MFCard/DynamicCardIframe.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { apiHttp } from '../../constants';
import React, { useCallback, useRef } from 'react';
import { DYNAMIC_CARDS_REFRESH_INTERVAL, apiHttp } from '../../constants';
import { taskCardPath } from '../../components/MFCard/useTaskCards';
import { Task } from '../../types';
import CardIframe from './CardIframe';
Expand All @@ -9,57 +9,64 @@ type Props = {
hash: string;
};

const INTERVAL = 1000;

//
// Wrap Card iframe with functions to enable dynamism
//

const DynamicCardIframe: React.FC<Props> = ({ task, hash }) => {
let ref: React.Ref<HTMLIFrameElement> | undefined;
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>();

// Get new data from the server and update the card
const getNewData = useCallback(
(token: string, updateFn: (payload: object) => void) => {
(token: string, updateFn: (payload: object) => void, iframe: HTMLIFrameElement) => {
// Generate path
const taskCardsPath = (task: Task): string => {
return `/flows/${task.flow_id}/runs/${task.run_number}/steps/${task.step_name}/tasks/${task.task_id}/cards/${hash}/data`;
};

fetch(`${apiHttp(taskCardsPath(task))}?invalidate=true`)
.then((result) => result.json())
.then((result) => {
if (token === result.payload?.reload_token) {
updateFn(result.payload);
if (!result.is_complete) {
if (token === result.data?.reload_token) {
// Call the callback provided by the card to add new data
updateFn(result.data?.data);

if (!(result.data?.reload_token === 'final')) {
timeoutRef.current = setTimeout(() => {
getNewData(token, updateFn);
}, INTERVAL);
getNewData(token, updateFn, iframe);
}, DYNAMIC_CARDS_REFRESH_INTERVAL);
}
} else {
iframe?.contentWindow?.location.reload();
}
})
.catch((err) => {
console.error(err);
console.error('Error fetching dynamic card data: ', err);
timeoutRef.current = setTimeout(() => {
getNewData(token, updateFn, iframe);
}, DYNAMIC_CARDS_REFRESH_INTERVAL);
});
},
[task, hash],
);

useEffect(() => {
const token = (ref as React.RefObject<HTMLIFrameElement>)?.current?.contentWindow?.METAFLOW_RELOAD_TOKEN;
const updateFn = (ref as React.RefObject<HTMLIFrameElement>)?.current?.contentWindow?.metaflow_card_update;
const handleIframeLoad = (iframe: HTMLIFrameElement) => {
const token = iframe?.contentWindow?.METAFLOW_RELOAD_TOKEN;
const updateFn = iframe?.contentWindow?.metaflow_card_update;

// If the card supplies a reload token and an update function, start the refresh loop
if (updateFn && token) {
timeoutRef.current = setTimeout(() => {
getNewData(token, updateFn);
}, INTERVAL);
getNewData(token, updateFn, iframe);
}, DYNAMIC_CARDS_REFRESH_INTERVAL);
}

return () => {
clearTimeout(timeoutRef.current);
};
}, [getNewData, ref]);
};

return <CardIframe path={`${taskCardPath(task, hash)}?embed=true`} ref={ref} />;
return <CardIframe path={`${taskCardPath(task, hash)}?embed=true&invalidate=true`} onLoad={handleIframeLoad} />;
};

export default DynamicCardIframe;
12 changes: 8 additions & 4 deletions src/components/Timeline/useTaskData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export function rowDataReducer(state: RowDataModel, action: RowDataAction): RowD
}

const newEndTime = !row.finished_at || endTime > row.finished_at ? endTime : row.finished_at;

return {
...obj,
[key]: {
Expand All @@ -164,16 +165,19 @@ export function rowDataReducer(state: RowDataModel, action: RowDataAction): RowD
};
}
// New step entry

const data = grouped[key].reduce<Record<number, Task[]>>((dataobj, item) => {
return { ...dataobj, [item.task_id]: makeTasksForStep(dataobj, item) };
}, {});

return {
...obj,
[key]: {
isOpen: true,
status: getStepStatus(grouped),
status: getStepStatus(data),
finished_at: endTime,
duration: startTime ? endTime - startTime : 0,
data: grouped[key].reduce<Record<number, Task[]>>((dataobj, item) => {
return { ...dataobj, [item.task_id]: makeTasksForStep(dataobj, item) };
}, {}),
data,
},
};
}, state);
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ export const DEFAULT_TIME_FILTER_DAYS: number =
(process.env.REACT_APP_MF_DEFAULT_TIME_FILTER_DAYS &&
parseInt(process.env.REACT_APP_MF_DEFAULT_TIME_FILTER_DAYS, DEFAULT_NUM_DAYS)) ||
30;

// The time between polls for new data for cards
export const DYNAMIC_CARDS_REFRESH_INTERVAL =
process.env.REACT_APP_DYNAMIC_CARDS_REFRESH_INTERVAL !== undefined
? Number(process.env.REACT_APP_DYNAMIC_CARDS_REFRESH_INTERVAL)
: 2000;
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ describe('useResource hook', () => {
// Without subscribeToEvents hook should just fetch data initially
//
it('useResource - Basic fetch', () => {
console.log(server.url);
mount(<ResourceListComponent />);
cy.waitUntil(() => connected, {}).then(() => {
// Check that content was fetched
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Debug/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import ContentHeader from '../../components/Content/ContentHeader';
import { ItemRow } from '../../components/Structure';
import StatusIndicator from '../../components/Status';
import { BigButton } from '../../components/Button';
import FEATURE_FLAGS, { FeatureFlags } from '../../utils/FEATURE';
import useLogger from '../../hooks/useLogger';
import { CheckboxField } from '../../components/Form/Checkbox';

const DebugPage: React.FC = () => {
const { t } = useTranslation();
Expand Down Expand Up @@ -53,7 +53,7 @@ const FeatureFlagItem: React.FC<{ flag: string; active: boolean }> = ({ flag, ac
return (
<DebugSectionContainer>
<DebugSectionTitle>
<StatusIndicator status={active ? 'completed' : 'failed'} />
<CheckboxField label={''} checked={active} disabled />
{flag}
</DebugSectionTitle>

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const HomeContentArea: React.FC<Props> = ({
<GenericError icon="searchNotFound" message={t('error.no-results')} />
</ItemRow>
)}

{/* TODO */}
<BigLoader visible={showLoader && resultAmount > 0}>
<Spinner md />
</BigLoader>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/Home.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { defaultHomeParameters } from './useHomeParameters';
export function makeActiveRequestParameters(params: Record<string, string>): Record<string, string> {
const { timerange_start, timerange_end, ...rest } = params;
let newParams = rest;
// We want to remove groupping from request in some cases
// We want to remove grouping from request in some cases
// 1) When grouping is flow_id and only 1 flow_id filter is active, we want to show all runs of this group
// 2) When grouping is user and only 1 user filter is active, we want to show all runs of this group
if (newParams._group) {
Expand Down
23 changes: 13 additions & 10 deletions src/pages/Home/Sidebar/SidebarTimerangeSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useMemo } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { SidebarSectionWrapper } from '.';
Expand Down Expand Up @@ -26,18 +26,21 @@ const SidebarTimerangeSelection: React.FC<Props> = ({ params, updateField }) =>
const hasSelectedTimeRange = startTime || endTime;
const { t } = useTranslation();

const shouldShowWarning = !hasSelectedTimeRange && !params.flow_id && !params._tags && !params.suer;
const shouldShowWarning = !hasSelectedTimeRange && !params.flow_id && !params._tags && !params.user;

const handleSubmit = ({ start, end }: { start: number | null; end: number | null }) => {
updateField('timerange_start', start ? start.toString() : '');
updateField('timerange_end', end ? end.toString() : '');
};

const initialValues: [number | null, number | null] = useMemo(
() => [startTime ? parseInt(startTime) : null, endTime ? parseInt(endTime) : null],
[startTime, endTime],
);

return (
<SidebarSectionWrapper>
<TimeRange
initialValues={[startTime ? parseInt(startTime) : null, endTime ? parseInt(endTime) : null]}
onSubmit={({ start, end }) => {
updateField('timerange_start', start ? start.toString() : '');
updateField('timerange_end', end ? end.toString() : '');
}}
sectionLabel="Time frame"
/>
<TimeRange initialValues={initialValues} onSubmit={handleSubmit} sectionLabel="Time frame" />

{hasSelectedTimeRange && (
<ParameterList>
Expand Down
8 changes: 0 additions & 8 deletions src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,6 @@ const Home: React.FC = () => {
} else if (historyAction !== 'POP' || scrollValue === 0) {
HomeStateCache.active = true;
}

if (historyAction !== 'POP' && initialised) {
if (isDefaultParams(rawParams, false, timezone)) {
// We want to add timerange filter if we are rolling with default params
// but not in back event. In back event we should keep state we had
setQp({ timerange_start: getTimeFromPastByDays(DEFAULT_TIME_FILTER_DAYS, timezone).toString() });
}
}
}, [historyAction, initialised, rawParams, setQp, timezone]);

// Update cache page on page change
Expand Down
8 changes: 4 additions & 4 deletions src/pages/Task/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ const Task: React.FC<TaskViewProps> = ({
: emptyDecoratorArray,
);

// Show cards if feature flag is set and the task has not failed
const showCards = task?.status !== 'failed' && FEATURE_FLAGS.CARDS;
// Show cards if feature flag is set
const showCards = FEATURE_FLAGS.CARDS;

const setSection = useCallback((value: string | null) => setQp({ section: value }, 'replaceIn'), [setQp]);
const selectHandler = useCallback((att?: string | null) => setQp({ attempt: att }), [setQp]);
Expand Down Expand Up @@ -513,12 +513,12 @@ const Task: React.FC<TaskViewProps> = ({
<FullPageContainer
onClose={closeHandler}
actionbar={
fullscreen.type === 'logs' ? null : (
fullscreen.type === 'artifact' ? (
<ArtifactActionBar
data={fullscreen.artifactdata}
name={`${fullscreen.name}-${task.ts_epoch}-${getTaskId(task)}-attempt${task.attempt_id}`}
/>
)
) : null
}
title={fullscreen.type === 'logs' ? fullscreen.logtype : fullscreen.name}
component={fullPageComponent}
Expand Down

0 comments on commit 09668f4

Please sign in to comment.