-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: add import image features to move all images to OpenPlanner sto…
…rage (#135) * Add import images feature when opening the sessions * Review
- Loading branch information
1 parent
95db078
commit 8bcf0e9
Showing
7 changed files
with
245 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { doc, updateDoc } from 'firebase/firestore' | ||
import { Speaker } from '../../../types' | ||
import { collections } from '../../../services/firebase' | ||
|
||
export const updateSpeaker = async (eventId: string, speaker: Partial<Speaker>): Promise<void> => { | ||
const ref = doc(collections.speakers(eventId), speaker.id) | ||
|
||
return await updateDoc(ref, { | ||
...speaker, | ||
}) | ||
} |
175 changes: 175 additions & 0 deletions
175
src/events/actions/speakers/useMoveAllImagesToOpenPlannerStorage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { Event, Session, Speaker } from '../../../types' | ||
import { baseStorageUrl } from '../../../services/firebase' | ||
import { uploadImage } from '../../../utils/images/uploadImage' | ||
import { updateSpeaker } from './updateSpeaker' | ||
import { useCallback, useEffect, useState } from 'react' | ||
|
||
export const useMoveAllImagesToOpenPlannerStorage = (event: Event, sessions: Session[]) => { | ||
const [state, setState] = useState<{ | ||
shouldUpdateImageStorage: boolean | ||
isLoading: boolean | ||
error: string[] | ||
isError: boolean | ||
progress: number | ||
total: number | ||
isTotalCalculated: boolean | ||
}>({ | ||
shouldUpdateImageStorage: false, | ||
isLoading: false, | ||
error: [], | ||
isError: false, | ||
progress: 0, | ||
total: 0, | ||
isTotalCalculated: false, | ||
}) | ||
|
||
useEffect(() => { | ||
if (!state.isTotalCalculated && sessions.length > 0) { | ||
const total = getTotalImagesToChange(sessions) | ||
setState((newState) => ({ | ||
...newState, | ||
total: total, | ||
shouldUpdateImageStorage: total > 0, | ||
isTotalCalculated: true, | ||
})) | ||
} | ||
}, [sessions]) | ||
|
||
const moveAllImagesToOpenPlannerStorage = useCallback(async () => { | ||
setState((newState) => ({ | ||
...newState, | ||
isLoading: true, | ||
error: [], | ||
isError: false, | ||
progress: 0, | ||
})) | ||
const errors: string[] = [] | ||
for (const session of sessions) { | ||
if (session.speakersData?.length) { | ||
for (const speaker of session.speakersData) { | ||
let i = 0 | ||
const shouldUpdatePhotoUrl = speaker.photoUrl && !speaker.photoUrl.startsWith(baseStorageUrl) | ||
const shouldUpdateCompanyLogoUrl = | ||
speaker.companyLogoUrl && !speaker.companyLogoUrl.startsWith(baseStorageUrl) | ||
|
||
if (shouldUpdatePhotoUrl) { | ||
const result = await downloadAndUpdateSpeakerImage(event, speaker, 'photoUrl') | ||
if (result.success) { | ||
i++ | ||
} else { | ||
errors.push(result.error) | ||
} | ||
} | ||
|
||
if (shouldUpdateCompanyLogoUrl) { | ||
const result = await downloadAndUpdateSpeakerImage(event, speaker, 'companyLogoUrl') | ||
if (result.success) { | ||
i++ | ||
} else { | ||
errors.push(result.error) | ||
} | ||
} | ||
|
||
setState((newState) => ({ | ||
...newState, | ||
progress: newState.progress + i, | ||
error: errors, | ||
isError: errors.length > 0, | ||
})) | ||
} | ||
} | ||
} | ||
|
||
setState((newState) => ({ | ||
...newState, | ||
isLoading: false, | ||
})) | ||
}, [sessions]) | ||
|
||
return { | ||
...state, | ||
moveAllImagesToOpenPlannerStorage, | ||
} | ||
} | ||
|
||
const getTotalImagesToChange = (sessions: Session[]) => { | ||
return sessions.reduce((acc, session) => { | ||
if (session.speakersData?.length) { | ||
return session.speakersData.reduce((acc, speaker) => { | ||
const shouldUpdatePhotoUrl = speaker.photoUrl && !speaker.photoUrl.startsWith(baseStorageUrl) | ||
const shouldUpdateCompanyLogoUrl = | ||
speaker.companyLogoUrl && !speaker.companyLogoUrl.startsWith(baseStorageUrl) | ||
|
||
if (shouldUpdatePhotoUrl && !shouldUpdateCompanyLogoUrl) { | ||
return acc + 1 | ||
} | ||
if (shouldUpdateCompanyLogoUrl && !shouldUpdatePhotoUrl) { | ||
return acc + 1 | ||
} | ||
if (shouldUpdatePhotoUrl && shouldUpdateCompanyLogoUrl) { | ||
return acc + 2 | ||
} | ||
return acc | ||
}, acc) | ||
} | ||
|
||
return acc | ||
}, 0) | ||
} | ||
|
||
const downloadAndUpdateSpeakerImage = async ( | ||
event: Event, | ||
speaker: Speaker, | ||
fieldName: 'photoUrl' | 'companyLogoUrl' | ||
): Promise< | ||
| { | ||
success: true | ||
error: null | ||
} | ||
| { | ||
success: false | ||
error: string | ||
} | ||
> => { | ||
try { | ||
const imageToDownload = speaker[fieldName] | ||
if (!imageToDownload) { | ||
return { | ||
success: true, | ||
error: null, | ||
} | ||
} | ||
const imageFetchResult = await fetch(imageToDownload) | ||
if (!imageFetchResult.ok) { | ||
console.warn( | ||
'Error downloading image', | ||
imageFetchResult.statusText, | ||
imageFetchResult.status, | ||
imageToDownload | ||
) | ||
return { | ||
success: false, | ||
error: `Error downloading image for speaker ${speaker.name}, error: ${imageFetchResult.statusText} (${imageFetchResult.status})`, | ||
} | ||
} | ||
|
||
const imageBlob = await imageFetchResult.blob() | ||
const newImageUrl = await uploadImage(event, imageBlob) | ||
|
||
await updateSpeaker(event.id, { | ||
id: speaker.id, | ||
[fieldName]: newImageUrl, | ||
}) | ||
|
||
return { | ||
success: true, | ||
error: null, | ||
} | ||
} catch (error) { | ||
console.error('error downloading or uploading image', error) | ||
return { | ||
success: false, | ||
error: `Error downloading or uploading image for speaker ${speaker.name}, error: ${error}`, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Alert, Button, Typography } from '@mui/material' | ||
import * as React from 'react' | ||
import { useMoveAllImagesToOpenPlannerStorage } from '../../../actions/speakers/useMoveAllImagesToOpenPlannerStorage' | ||
import { Event, Session } from '../../../../types' | ||
|
||
export const MoveImagesAlert = ({ event, sessionsData }: { event: Event; sessionsData: Session[] }) => { | ||
const moveAllImagesToOpenPlannerStorageState = useMoveAllImagesToOpenPlannerStorage(event, sessionsData) | ||
|
||
return ( | ||
<> | ||
{moveAllImagesToOpenPlannerStorageState.shouldUpdateImageStorage && ( | ||
<Alert variant="filled" severity="info" sx={{ marginBottom: 2 }}> | ||
{moveAllImagesToOpenPlannerStorageState.total} session/speaker images are NOT stored in OpenPlanner. | ||
This may cause issues due to the image not being accessible in some code (ShortVid.io) or being | ||
moved later and making it unavailable in the long term. | ||
<br /> | ||
You can migrate the images to OpenPlanner at once now: | ||
<Button | ||
variant="contained" | ||
color="secondary" | ||
disabled={moveAllImagesToOpenPlannerStorageState.isLoading} | ||
onClick={() => { | ||
moveAllImagesToOpenPlannerStorageState.moveAllImagesToOpenPlannerStorage() | ||
}}> | ||
Migrate images{' '} | ||
{moveAllImagesToOpenPlannerStorageState.isLoading | ||
? `${moveAllImagesToOpenPlannerStorageState.progress}/${moveAllImagesToOpenPlannerStorageState.total}` | ||
: ''} | ||
</Button> | ||
{moveAllImagesToOpenPlannerStorageState.error.length > 0 && ( | ||
<> | ||
<Typography>Error(s):</Typography> | ||
<ul> | ||
{moveAllImagesToOpenPlannerStorageState.error.map((error) => ( | ||
<li key={error}>{error}</li> | ||
))} | ||
</ul> | ||
<Typography> | ||
Common errors could be linked to CORS issues, please check the console for more details. | ||
</Typography> | ||
</> | ||
)} | ||
</Alert> | ||
)} | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters