Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
108 changes: 107 additions & 1 deletion src/components/floweditor/FlowEditor.helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,115 @@

import Tooltip from 'components/UI/Tooltip/Tooltip';
import styles from './FlowEditor.module.css';
import { getAuthSession } from 'services/AuthService';

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) => {
try {
let latestRevision = null;
const token = getAuthSession('access_token');

const response = await fetch(`${glificBase}revisions/${uuid}?version=13.2`, {
headers: {
authorization: token,
},
});
Comment on lines +96 to +100
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Include Bearer prefix on Authorization header

The flow-editor API expects standard bearer auth. Here we send the raw token (authorization: token), which the backend treats as missing credentials, returning 401. This regresses publishing after idle periods—the exact bug reported in PR comments. Prefix the header with Bearer (and bail early if token is falsy) so the request reuses our auth session correctly.

-    const response = await fetch(`${glificBase}revisions/${uuid}?version=13.2`, {
-      headers: {
-        authorization: token,
-      },
-    });
+    if (!token) {
+      throw new Error('Missing access token for revision fetch');
+    }
+
+    const response = await fetch(`${glificBase}revisions/${uuid}?version=13.2`, {
+      headers: {
+        authorization: `Bearer ${token}`,
+      },
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await fetch(`${glificBase}revisions/${uuid}?version=13.2`, {
headers: {
authorization: token,
},
});
if (!token) {
throw new Error('Missing access token for revision fetch');
}
const response = await fetch(`${glificBase}revisions/${uuid}?version=13.2`, {
headers: {
authorization: `Bearer ${token}`,
},
});
🤖 Prompt for AI Agents
In src/components/floweditor/FlowEditor.helper.tsx around lines 96 to 100, the
fetch call sends the raw token in the authorization header which the API expects
as a Bearer token; update the code to early-return or throw if token is falsy,
and set the header to "Authorization": `Bearer ${token}` (i.e., prefix the token
with "Bearer " and use the proper header key casing) so the backend recognizes
the credentials and avoids 401s after idle periods.

const data = await response.json();

if (data.results.length > 0) {
latestRevision = data.results.reduce((latest: any, current: any) => {

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
return new Date(latest.created_on) > new Date(current.created_on) ? latest : current;
});
}

return latestRevision;

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
} catch (error) {
console.error('Error fetching latest revision:', error);
return null;
}
};

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L93-L94

Added lines #L93 - L94 were not covered by tests

try {
const response = await fetch(url, {

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L96-L97

Added lines #L96 - L97 were not covered by tests
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: token,
},
body: JSON.stringify(definition),
});

if (response.ok) {
return true;

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.helper.tsx#L111-L112

Added lines #L111 - L112 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 +215,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
44 changes: 40 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,33 @@
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);

if (latestRevision && flowDefinition) {
const latestRevisionTime = dayjs(latestRevision.created_on);
const flowDefinitionTime = dayjs(flowDefinition.timestamp);

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L248-L249

Added lines #L248 - L249 were not covered by tests

if (flowDefinitionTime.isAfter(latestRevisionTime)) {
const timeDifferenceSeconds = flowDefinitionTime.diff(latestRevisionTime, 'seconds');
revisionSaved =

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#L252-L253

Added lines #L252 - L253 were not covered by tests
timeDifferenceSeconds > 300 ? await postLatestRevision(uuid, flowDefinition.definition) : true;
} else {
revisionSaved = true;

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L256

Added line #L256 was not covered by tests
}
}

return revisionSaved;

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L260

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/components/floweditor/FlowEditor.tsx#L266

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

const handleCancelFlow = () => {
Expand Down
Loading