Skip to content
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

Add option to upload and ingest ingestData from snapshot (SOFIE-3553) #1307

Open
wants to merge 2 commits into
base: release52
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 46 additions & 20 deletions meteor/client/ui/Settings/SnapshotsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const SnapshotsViewContent = withTranslation()(
}
}

onUploadFile(e: React.ChangeEvent<HTMLInputElement>, restoreDebugData: boolean) {
onUploadFile(e: React.ChangeEvent<HTMLInputElement>, restoreVariant?: 'debug' | 'ingest') {
const { t } = this.props

const file = e.target.files?.[0]
Expand All @@ -101,7 +101,8 @@ const SnapshotsViewContent = withTranslation()(
body: uploadFileContents,
headers: {
'content-type': 'application/json',
'restore-debug-data': restoreDebugData ? '1' : '0',
'restore-debug-data': restoreVariant === 'debug' ? '1' : '0',
'ingest-snapshot-data': restoreVariant === 'ingest' ? '1' : '0',
},
})
.then(() => {
Expand Down Expand Up @@ -137,6 +138,7 @@ const SnapshotsViewContent = withTranslation()(

reader.readAsText(file)
}

restoreStoredSnapshot = (snapshotId: SnapshotId) => {
const snapshot = Snapshots.findOne(snapshotId)
if (snapshot) {
Expand Down Expand Up @@ -313,24 +315,48 @@ const SnapshotsViewContent = withTranslation()(
</div>
<h2 className="mhn">{t('Restore from Snapshot File')}</h2>
<div className="mdi">
<UploadButton
accept="application/json,.json"
className="btn btn-secondary"
onChange={(e) => this.onUploadFile(e, false)}
key={this.state.uploadFileKey}
>
<FontAwesomeIcon icon={faUpload} />
<span>{t('Upload Snapshot')}</span>
</UploadButton>
<UploadButton
accept="application/json,.json"
className="btn btn-secondary mls"
onChange={(e) => this.onUploadFile(e, true)}
key={this.state.uploadFileKey2}
>
<FontAwesomeIcon icon={faUpload} />
<span>{t('Upload Snapshot (for debugging)')}</span>
</UploadButton>
<p className="mhn">
<UploadButton
accept="application/json,.json"
className="btn btn-secondary"
onChange={(e) => this.onUploadFile(e)}
key={this.state.uploadFileKey}
>
<FontAwesomeIcon icon={faUpload} />
<span>{t('Upload Snapshot')}</span>
</UploadButton>
<span className="text-s vsubtle mls">{t('Upload a snapshot file')}</span>
</p>
<p className="mhn">
<UploadButton
accept="application/json,.json"
className="btn btn-secondary"
onChange={(e) => this.onUploadFile(e, 'debug')}
key={this.state.uploadFileKey2}
>
<FontAwesomeIcon icon={faUpload} />
<span>{t('Upload Snapshot (for debugging)')}</span>
</UploadButton>
<span className="text-s vsubtle mls">
{t(
'Upload a snapshot file (restores additional info not directly related to a Playlist / Rundown, such as Packages, PackageWorkStatuses etc'
)}
</span>
</p>
<p className="mhn">
<UploadButton
accept="application/json,.json"
className="btn btn-secondary"
onChange={(e) => this.onUploadFile(e, 'ingest')}
key={this.state.uploadFileKey2}
>
<FontAwesomeIcon icon={faUpload} />
<span>{t('Ingest from Snapshot')}</span>
</UploadButton>
<span className="text-s vsubtle mls">
{t('Reads the ingest (NRCS) data, and pipes it throught the blueprints')}
</span>
</p>
</div>
<h2 className="mhn">{t('Restore from Stored Snapshots')}</h2>
<div>
Expand Down
86 changes: 65 additions & 21 deletions meteor/server/api/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@

return readSnapshot
}

async function restoreFromSnapshot(
/** The snapshot data to restore */
snapshot: AnySnapshot,
Expand All @@ -497,22 +498,7 @@
): Promise<void> {
// Determine what kind of snapshot

if (!_.isObject(snapshot)) throw new Meteor.Error(500, `Restore input data is not an object`)
// First, some special (debugging) cases:
// @ts-expect-error is's not really a snapshot here:
if (snapshot.externalId && snapshot.segments && snapshot.type === 'mos') {
// Special: Not a snapshot, but a datadump of a MOS rundown
const studioId: StudioId = Meteor.settings.manualSnapshotIngestStudioId || 'studio0'
const studioExists = await checkStudioExists(studioId)
if (studioExists) {
await importIngestRundown(studioId, snapshot as unknown as IngestRundown)
return
}
throw new Meteor.Error(500, `No Studio found`)
}

// Then, continue as if it's a normal snapshot:

if (!snapshot.snapshot) throw new Meteor.Error(500, `Restore input data is not a snapshot (${_.keys(snapshot)})`)

if (snapshot.snapshot.type === SnapshotType.RUNDOWNPLAYLIST) {
Expand All @@ -525,11 +511,7 @@
)
}

// TODO: Improve this. This matches the 'old' behaviour
const studios = await Studios.findFetchAsync({})
const snapshotStudioExists = studios.find((studio) => studio._id === playlistSnapshot.playlist.studioId)
const studioId = snapshotStudioExists ? playlistSnapshot.playlist.studioId : studios[0]?._id
if (!studioId) throw new Meteor.Error(500, `No Studio found`)
const studioId = await getStudioIdFromPlaylistSnapshot(playlistSnapshot)

Check warning on line 514 in meteor/server/api/snapshot.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/snapshot.ts#L514

Added line #L514 was not covered by tests

// A snapshot of a rundownPlaylist
return restoreFromRundownPlaylistSnapshot(snapshot as RundownPlaylistSnapshot, studioId, restoreDebugData)
Expand All @@ -541,6 +523,60 @@
}
}

async function getStudioIdFromPlaylistSnapshot(playlistSnapshot: RundownPlaylistSnapshot): Promise<StudioId> {
// TODO: Improve this. This matches the 'old' behaviour
const studios = await Studios.findFetchAsync({})
const snapshotStudioExists = studios.find((studio) => studio._id === playlistSnapshot.playlist.studioId)
const studioId = snapshotStudioExists ? playlistSnapshot.playlist.studioId : studios[0]?._id
if (!studioId) throw new Meteor.Error(500, `No Studio found`)
return studioId
}

Check warning on line 533 in meteor/server/api/snapshot.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/snapshot.ts#L526-L533

Added lines #L526 - L533 were not covered by tests
/** Read the ingest data from a snapshot and pipe it into blueprints */
async function ingestFromSnapshot(
/** The snapshot data to restore */
snapshot: AnySnapshot
): Promise<void> {
// Determine what kind of snapshot
if (!snapshot.snapshot) throw new Meteor.Error(500, `Restore input data is not a snapshot (${_.keys(snapshot)})`)
if (snapshot.snapshot.type === SnapshotType.RUNDOWNPLAYLIST) {
const playlistSnapshot = snapshot as RundownPlaylistSnapshot

const studioId = await getStudioIdFromPlaylistSnapshot(playlistSnapshot)

// Read the ingestData from the snapshot
const ingestData = playlistSnapshot.ingestData

const rundownData = ingestData.filter((e) => e.type === 'rundown')
const segmentData = ingestData.filter((e) => e.type === 'segment')
const partData = ingestData.filter((e) => e.type === 'part')

if (rundownData.length === 0) throw new Meteor.Error(402, `No rundowns found in ingestData`)

for (const seg of segmentData) {
seg.data.parts = partData
.filter((e) => e.segmentId === seg.segmentId)
.map((e) => e.data)
.sort((a, b) => b.rank - a.rank)
}

for (let i = 0; i < rundownData.length; i++) {
const rundown = rundownData[i]

const segmentsInRundown = segmentData.filter((e) => e.rundownId === rundown.rundownId)

const ingestRundown: IngestRundown = rundown.data
ingestRundown.segments = segmentsInRundown.map((s) => s.data).sort((a, b) => b.rank - a.rank)

await importIngestRundown(studioId, ingestRundown)
}
} else {
throw new Meteor.Error(
402,
`Unable to ingest a snapshot of type "${snapshot.snapshot.type}", did you mean to restore it?`
)
}
}

Check warning on line 578 in meteor/server/api/snapshot.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/snapshot.ts#L535-L578

Added lines #L535 - L578 were not covered by tests

async function restoreFromRundownPlaylistSnapshot(
snapshot: RundownPlaylistSnapshot,
studioId: StudioId,
Expand Down Expand Up @@ -816,8 +852,16 @@
if (!snapshot) throw new Meteor.Error(400, 'Restore Snapshot: Missing request body')

const restoreDebugData = ctx.headers['restore-debug-data'] === '1'
const ingestSnapshotData = ctx.headers['ingest-snapshot-data'] === '1'

if (typeof snapshot !== 'object' || snapshot === null)
throw new Meteor.Error(500, `Restore input data is not an object`)

Check warning on line 858 in meteor/server/api/snapshot.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/snapshot.ts#L855-L858

Added lines #L855 - L858 were not covered by tests

await restoreFromSnapshot(snapshot, restoreDebugData)
if (ingestSnapshotData) {
await ingestFromSnapshot(snapshot)
} else {
await restoreFromSnapshot(snapshot, restoreDebugData)
}

Check warning on line 864 in meteor/server/api/snapshot.ts

View check run for this annotation

Codecov / codecov/patch

meteor/server/api/snapshot.ts#L860-L864

Added lines #L860 - L864 were not covered by tests

ctx.response.status = 200
ctx.response.body = content
Expand Down
Loading