Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dc9f067
Implement IndexedDB for flow definitions and add revision handling logic
akanshaaa19 Jul 3, 2025
0b8f976
Refactor axios call in fetchLatestRevision to use axios.get and add I…
akanshaaa19 Jul 3, 2025
9b06d85
Refactor fetchLatestRevision to use fetch API and improve error handl…
akanshaaa19 Jul 14, 2025
a92d4b8
Implement IndexedDB for flow definitions and add revision handling logic
akanshaaa19 Jul 3, 2025
3b818b9
Refactor axios call in fetchLatestRevision to use axios.get and add I…
akanshaaa19 Jul 3, 2025
14f4b43
Refactor fetchLatestRevision to use fetch API and improve error handl…
akanshaaa19 Jul 14, 2025
4c6734b
Add deleteFlowDefinition function and integrate it into FlowEditor on…
akanshaaa19 Aug 5, 2025
c0e60d7
Merge branch 'enhancements/save-revision-before-publish' of github.co…
akanshaaa19 Aug 6, 2025
ad9317c
Remove unused import of deleteFlowDefinition in FlowEditor component
akanshaaa19 Aug 6, 2025
ce7138f
Merge branch 'master' into enhancements/save-revision-before-publish
akanshaaa19 Aug 6, 2025
2020d7b
Add publishFlowWithSuccess mock and update tests for flow publishing
akanshaaa19 Aug 6, 2025
581aebf
Merge branch 'enhancements/save-revision-before-publish' of github.co…
akanshaaa19 Aug 6, 2025
8417fb8
Refactor error handling and logging in FlowEditor helper and tests
akanshaaa19 Aug 6, 2025
7e3fe1b
Change loadfiles function from async to synchronous
akanshaaa19 Aug 6, 2025
cfdd620
Skip inactive flow test
akanshaaa19 Aug 6, 2025
cdb2729
Merge branch 'master' of github.com:glific/glific-frontend into enhan…
akanshaaa19 Oct 13, 2025
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
86 changes: 85 additions & 1 deletion src/components/floweditor/FlowEditor.helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,93 @@

import Tooltip from 'components/UI/Tooltip/Tooltip';
import styles from './FlowEditor.module.css';
import axios from 'axios';

const glificBase = FLOW_EDITOR_API;

const DB_NAME = 'FlowDefinitionDB';
const VERSION = 1;
const STORE_NAME = 'flowDefinitions';
let dbInstance: IDBDatabase | null = null;

async function initDB(): Promise<IDBDatabase> {
if (dbInstance) {
return dbInstance;

Check warning on line 18 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L18

Added line #L18 was not covered by tests
}

return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, VERSION);

request.onerror = () => {
reject(new Error('Failed to open IndexedDB'));

Check warning on line 25 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L25

Added line #L25 was not covered by tests
};

request.onsuccess = () => {
dbInstance = request.result;
resolve(dbInstance);

Check warning on line 30 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L29-L30

Added lines #L29 - L30 were not covered by tests
};

request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;

Check warning on line 34 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L34

Added line #L34 was not covered by tests

if (!db.objectStoreNames.contains(STORE_NAME)) {
const store = db.createObjectStore(STORE_NAME, { keyPath: 'uuid' });
store.createIndex('timestamp', 'timeStamp', { unique: false });

Check warning on line 38 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L37-L38

Added lines #L37 - L38 were not covered by tests
}
};
});
}

export const getFlowDefinition = async (uuid: string): Promise<any | null> => {
const db = dbInstance || (await initDB());
if (!db) {
console.warn('Database not initialized. Call initDB() first.');
return null;

Check warning on line 48 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L47-L48

Added lines #L47 - L48 were not covered by tests
}

return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.get(uuid);

Check warning on line 54 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L51-L54

Added lines #L51 - L54 were not covered by tests

request.onsuccess = () => {
const result = request.result;

Check warning on line 57 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L56-L57

Added lines #L56 - L57 were not covered by tests
resolve(result ? result : null);
};

request.onerror = () => {
reject(new Error('Failed to get flow definition'));

Check warning on line 62 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L61-L62

Added lines #L61 - L62 were not covered by tests
};
});
};

export const fetchLatestRevision = async (uuid: string) => {
let latestRevision;
const response = await axios.get(`${glificBase}revisions/${uuid}`);
if (response.data.results.length > 0) {
latestRevision = response.data.results.reduce((latest: any, current: any) => {

Check warning on line 71 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L71

Added line #L71 was not covered by tests
return new Date(latest.created_on) > new Date(current.created_on) ? latest : current;
});
}

return latestRevision;
};

export const postLatestRevision = async (uuid: any, definition: any) => {
const url = `${glificBase}revisions/${uuid}`;

Check warning on line 80 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L80

Added line #L80 was not covered by tests

try {
const response = await axios.post(url, definition);

Check warning on line 83 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L82-L83

Added lines #L82 - L83 were not covered by tests
if (response.status === 200) {
return true;

Check warning on line 85 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L85

Added line #L85 was not covered by tests
}
return false;

Check warning on line 87 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L87

Added line #L87 was not covered by tests
} catch (error) {
console.error('Error posting latest revision:', error);
return false;

Check warning on line 90 in src/components/floweditor/FlowEditor.helper.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L89-L90

Added lines #L89 - L90 were not covered by tests
}
};

export const setConfig = (uuid: any, isTemplate: boolean, skipValidation: boolean) => {
const services = JSON.parse(localStorage.getItem('organizationServices') || '{}');

Expand Down Expand Up @@ -109,7 +193,7 @@
return config;
};

export const loadfiles = (startFlowEditor: any) => {
export const loadfiles = async (startFlowEditor: any) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Function signature change needs verification

The loadfiles function has been changed from synchronous to asynchronous, but the internal implementation remains synchronous. This could break existing callers.

Verify that all callers of loadfiles have been updated to handle the async nature:


🏁 Script executed:

#!/bin/bash
# Search for all usages of loadfiles function
rg -A 3 -B 3 "loadfiles\(" --type ts --type tsx

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Search for all usages of loadfiles across TS/TSX files
rg -A 3 -B 3 "loadfiles\(" -g "*.ts" -g "*.tsx" || echo "No occurrences found."

Length of output: 535


Ensure callers handle loadfiles’ async return

The loadfiles function now returns a Promise, but its caller in FlowEditor.tsx still treats it as synchronous:

• File: src/components/floweditor/FlowEditor.tsx
Around the useEffect:

useEffect(() => {
  if (flowId) {
-     const files = loadfiles(() => {
+     (async () => {
+       const files = await loadfiles(() => {
        getFreeFlow({ variables: { id: flowId } });
      });
+       // …use files here…
+     })();
  }
}, [flowId]);

Please update this (and any other) call site to either await the async function or chain with .then(...), and adjust the useEffect body accordingly so you’re working with the resolved value instead of a Promise.

🤖 Prompt for AI Agents
In src/components/floweditor/FlowEditor.tsx near the useEffect hook, the call to
the async function loadfiles from FlowEditor.helper.tsx is currently treated
synchronously. Update the useEffect callback to either use an async function
with await when calling loadfiles or use loadfiles(...).then(...) to handle the
Promise resolution properly. This ensures the code works with the resolved value
instead of the Promise itself.

const files: Array<HTMLScriptElement | HTMLLinkElement> = [];
const filesToLoad: any = Manifest.files;

Expand Down
16 changes: 16 additions & 0 deletions src/components/floweditor/FlowEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,23 @@ const mockedAxios = axios as any;
vi.mock('../simulator/Simulator', () => ({
default: ({ message }: { message: string }) => <div data-testid="simulator">{message}</div>, // Mocking the component's behavior
}));
mockedAxios.get.mockImplementation(() =>
Promise.resolve({
data: {
results: [],
},
})
);

beforeAll(() => {
globalThis.indexedDB = {
open: vi.fn(() => ({
onerror: vi.fn(),
onsuccess: vi.fn(),
result: {},
})),
} as unknown as IDBFactory;
});
const mocks = [
messageReceivedSubscription({ organizationId: null }),
messageSendSubscription({ organizationId: null }),
Expand Down
42 changes: 38 additions & 4 deletions src/components/floweditor/FlowEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@
import Track from 'services/TrackService';
import { exportFlowMethod } from 'common/utils';
import styles from './FlowEditor.module.css';
import { checkElementInRegistry, getKeywords, loadfiles, setConfig } from './FlowEditor.helper';
import {
checkElementInRegistry,
fetchLatestRevision,
getFlowDefinition,
getKeywords,
loadfiles,
postLatestRevision,
setConfig,
} from './FlowEditor.helper';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { BackdropLoader, FlowTranslation } from 'containers/Flow/FlowTranslation';
import dayjs from 'dayjs';

declare function showFlowEditor(node: any, config: any): void;

Expand Down Expand Up @@ -208,8 +217,10 @@
Track('Flow opened');

return () => {
Object.keys(files).forEach((node: any) => {
Object.keys(files).forEach((node) => {
// @ts-ignore
if (files[node] && document.body.contains(files[node])) {
// @ts-ignore
document.body.removeChild(files[node]);
}
});
Expand All @@ -227,8 +238,31 @@
return () => {};
}, [flowId]);

const handlePublishFlow = () => {
publishFlow({ variables: { uuid: params.uuid } });
const checkLatestRevision = async () => {
let revisionSaved = false;
if (uuid) {
const latestRevision = await fetchLatestRevision(uuid);

const flowDefinition = await getFlowDefinition(uuid);

const timeDifferenceSeconds = Math.abs(

Check warning on line 248 in src/components/floweditor/FlowEditor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L248

Added line #L248 was not covered by tests
dayjs(latestRevision.created_on).diff(dayjs(flowDefinition.timeStamp), 'seconds')
);

if (timeDifferenceSeconds > 300) {
revisionSaved = await postLatestRevision(uuid, flowDefinition.definition);

Check warning on line 253 in src/components/floweditor/FlowEditor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L253

Added line #L253 was not covered by tests
} else {
revisionSaved = true;

Check warning on line 255 in src/components/floweditor/FlowEditor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L255

Added line #L255 was not covered by tests
}
}
console.log(revisionSaved);
return revisionSaved;

Check warning on line 259 in src/components/floweditor/FlowEditor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L258-L259

Added lines #L258 - L259 were not covered by tests
};

const handlePublishFlow = async () => {
if (await checkLatestRevision()) {
publishFlow({ variables: { uuid: params.uuid } });

Check warning on line 264 in src/components/floweditor/FlowEditor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L264

Added line #L264 was not covered by tests
}
};

const handleCancelFlow = () => {
Expand Down
Loading