-
Notifications
You must be signed in to change notification settings - Fork 69
feat: Concurrent executions with indexedDB #1235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
26e5724
e168a9a
6fc0304
117405f
a8a6614
047f00d
b91218f
58f6d06
c80a929
9d3f7e3
4e522fe
1c0f14c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Microchesst marked this conversation as resolved.
Show resolved
Hide resolved
Microchesst marked this conversation as resolved.
Show resolved
Hide resolved
Microchesst marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import AuthProvider from 'route/auth/AuthProvider'; | |
| import * as React from 'react'; | ||
| import { Provider } from 'react-redux'; | ||
| import store from 'store/store'; | ||
| import ExecutionHistoryLoader from 'preview/components/execution/ExecutionHistoryLoader'; | ||
|
|
||
| const mdTheme: Theme = createTheme({ | ||
| palette: { | ||
|
|
@@ -17,6 +18,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) { | |
| <ThemeProvider theme={mdTheme}> | ||
| <AuthProvider> | ||
| <CssBaseline /> | ||
| <ExecutionHistoryLoader /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this react component here? |
||
| {children} | ||
| </AuthProvider> | ||
| </ThemeProvider> | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all of the methods are returning |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,302 @@ | ||
| import { | ||
| DB_CONFIG, | ||
| ExecutionHistoryEntry, | ||
| } from '../model/backend/gitlab/types/executionHistory'; | ||
|
|
||
| /** | ||
| * Interface for IndexedDB operations | ||
| */ | ||
| export interface IIndexedDBService { | ||
|
||
| init(): Promise<void>; | ||
| addExecutionHistory(entry: ExecutionHistoryEntry): Promise<string>; | ||
| updateExecutionHistory(entry: ExecutionHistoryEntry): Promise<void>; | ||
| getExecutionHistoryById(id: string): Promise<ExecutionHistoryEntry | null>; | ||
| getExecutionHistoryByDTName(dtName: string): Promise<ExecutionHistoryEntry[]>; | ||
| getAllExecutionHistory(): Promise<ExecutionHistoryEntry[]>; | ||
| deleteExecutionHistory(id: string): Promise<void>; | ||
| deleteExecutionHistoryByDTName(dtName: string): Promise<void>; | ||
| } | ||
|
|
||
| /** | ||
| * For interacting with IndexedDB | ||
| */ | ||
| class IndexedDBService implements IIndexedDBService { | ||
| private db: IDBDatabase | null = null; | ||
|
||
|
|
||
| private dbName: string; | ||
|
|
||
| private dbVersion: number; | ||
|
|
||
| constructor() { | ||
| this.dbName = DB_CONFIG.name; | ||
| this.dbVersion = DB_CONFIG.version; | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the database | ||
| * @returns Promise that resolves when the database is initialized | ||
| */ | ||
| public async init(): Promise<void> { | ||
| if (this.db) { | ||
| return Promise.resolve(); | ||
| } | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| const request = indexedDB.open(this.dbName, this.dbVersion); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to open IndexedDB')); | ||
| }; | ||
|
|
||
| request.onsuccess = (event) => { | ||
| this.db = (event.target as IDBOpenDBRequest).result; | ||
| resolve(); | ||
| }; | ||
|
|
||
| request.onupgradeneeded = (event) => { | ||
| const db = (event.target as IDBOpenDBRequest).result; | ||
|
|
||
| // Create object stores and indexes | ||
| if (!db.objectStoreNames.contains('executionHistory')) { | ||
| const store = db.createObjectStore('executionHistory', { | ||
| keyPath: DB_CONFIG.stores.executionHistory.keyPath, | ||
| }); | ||
|
|
||
| // Create indexes | ||
| DB_CONFIG.stores.executionHistory.indexes.forEach((index) => { | ||
| store.createIndex(index.name, index.keyPath); | ||
| }); | ||
| } | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Add a new execution history entry | ||
| * @param entry The execution history entry to add | ||
| * @returns Promise that resolves with the ID of the added entry | ||
| */ | ||
| public async addExecutionHistory( | ||
| entry: ExecutionHistoryEntry, | ||
| ): Promise<string> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction( | ||
| ['executionHistory'], | ||
| 'readwrite', | ||
| ); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const request = store.add(entry); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to add execution history')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(entry.id); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Update an existing execution history entry | ||
| * @param entry The execution history entry to update | ||
| * @returns Promise that resolves when the entry is updated | ||
| */ | ||
| public async updateExecutionHistory( | ||
| entry: ExecutionHistoryEntry, | ||
| ): Promise<void> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction( | ||
| ['executionHistory'], | ||
| 'readwrite', | ||
| ); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const request = store.put(entry); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to update execution history')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Get an execution history entry by ID | ||
| * @param id The ID of the execution history entry | ||
| * @returns Promise that resolves with the execution history entry | ||
| */ | ||
| public async getExecutionHistoryById( | ||
| id: string, | ||
| ): Promise<ExecutionHistoryEntry | null> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction(['executionHistory'], 'readonly'); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const request = store.get(id); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to get execution history')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(request.result || null); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Get all execution history entries for a Digital Twin | ||
| * @param dtName The name of the Digital Twin | ||
| * @returns Promise that resolves with an array of execution history entries | ||
| */ | ||
| public async getExecutionHistoryByDTName( | ||
| dtName: string, | ||
| ): Promise<ExecutionHistoryEntry[]> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction(['executionHistory'], 'readonly'); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const index = store.index('dtName'); | ||
| const request = index.getAll(dtName); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to get execution history by DT name')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(request.result || []); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Get all execution history entries | ||
| * @returns Promise that resolves with an array of all execution history entries | ||
| */ | ||
| public async getAllExecutionHistory(): Promise<ExecutionHistoryEntry[]> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction(['executionHistory'], 'readonly'); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const request = store.getAll(); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to get all execution history')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(request.result || []); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Delete an execution history entry | ||
| * @param id The ID of the execution history entry to delete | ||
| * @returns Promise that resolves when the entry is deleted | ||
| */ | ||
| public async deleteExecutionHistory(id: string): Promise<void> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction( | ||
| ['executionHistory'], | ||
| 'readwrite', | ||
| ); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const request = store.delete(id); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to delete execution history')); | ||
| }; | ||
|
|
||
| request.onsuccess = () => { | ||
| resolve(); | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Delete all execution history entries for a Digital Twin | ||
| * @param dtName The name of the Digital Twin | ||
| * @returns Promise that resolves when all entries are deleted | ||
| */ | ||
| public async deleteExecutionHistoryByDTName(dtName: string): Promise<void> { | ||
| await this.init(); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| if (!this.db) { | ||
| reject(new Error('Database not initialized')); | ||
| return; | ||
| } | ||
|
|
||
| const transaction = this.db.transaction( | ||
| ['executionHistory'], | ||
| 'readwrite', | ||
| ); | ||
| const store = transaction.objectStore('executionHistory'); | ||
| const index = store.index('dtName'); | ||
| const request = index.openCursor(IDBKeyRange.only(dtName)); | ||
|
|
||
| request.onerror = () => { | ||
| reject(new Error('Failed to delete execution history by DT name')); | ||
| }; | ||
|
|
||
| request.onsuccess = (event) => { | ||
| const cursor = (event.target as IDBRequest).result; | ||
| if (cursor) { | ||
| cursor.delete(); | ||
| cursor.continue(); | ||
| } else { | ||
| resolve(); | ||
| } | ||
| }; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Create a singleton instance | ||
| const indexedDBService = new IndexedDBService(); | ||
|
|
||
| // Export the singleton instance as default | ||
| export default indexedDBService; | ||
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { Dispatch, SetStateAction } from 'react'; | ||
| import { ThunkDispatch, Action } from '@reduxjs/toolkit'; | ||
| import { RootState } from 'store/store'; | ||
|
||
| import DigitalTwin from 'preview/util/digitalTwin'; | ||
|
||
|
|
||
| export interface PipelineStatusParams { | ||
| setButtonText: Dispatch<SetStateAction<string>>; | ||
| digitalTwin: DigitalTwin; | ||
| setLogButtonDisabled: Dispatch<SetStateAction<boolean>>; | ||
| dispatch: ReturnType<typeof import('react-redux').useDispatch>; | ||
| executionId?: string; | ||
| } | ||
|
|
||
| export type PipelineHandlerDispatch = ThunkDispatch< | ||
| RootState, | ||
| unknown, | ||
| Action<string> | ||
| >; | ||
|
|
||
| export interface JobLog { | ||
|
||
| jobName: string; | ||
| log: string; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.