Skip to content
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
51 changes: 50 additions & 1 deletion meteor/server/api/__tests__/peripheralDevice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { EmptyPieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/dataModel/Piece'
import { literal, getRandomId, getRandomString } from '@sofie-automation/corelib/dist/lib'
import { LogLevel } from '@sofie-automation/meteor-lib/dist/lib'
import { protectString, ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
import { protectString, ProtectedString, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
import { getCurrentTime } from '../../lib/lib'
import { waitUntil } from '../../../__mocks__/helpers/jest'
import { setupDefaultStudioEnvironment, DefaultEnvironment } from '../../../__mocks__/helpers/database'
Expand Down Expand Up @@ -45,7 +45,9 @@
RundownPlaylists,
Rundowns,
Segments,
Studios,
} from '../../collections'
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
import { SupressLogMessages } from '../../../__mocks__/suppressLogging'
import { JSONBlobStringify } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
import { PeripheralDeviceCommand } from '@sofie-automation/corelib/dist/dataModel/PeripheralDeviceCommand'
Expand Down Expand Up @@ -165,6 +167,7 @@
name: 'Earth',
})
})

beforeEach(async () => {
QueueStudioJobSpy.mockReset()
QueueStudioJobSpy.mockClear()
Expand Down Expand Up @@ -494,7 +497,7 @@
).rejects.toThrowMeteor(418, `Error thrown, as requested`)
})

/*

Check warning on line 500 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented

Check warning on line 500 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented
test('timelineTriggerTime', () => {
if (DEBUG) setLogLevel(LogLevel.DEBUG)
let timelineTriggerTimeResult: PeripheralDeviceAPI.TimelineTriggerTimeResult = [
Expand Down Expand Up @@ -562,7 +565,7 @@
})

// Note: this test fails, due to a backwards-compatibility hack in #c579c8f0
// test('initialize with bad arguments', () => {

Check warning on line 568 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented

Check warning on line 568 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented
// let options: PeripheralDeviceInitOptions = {
// category: PeripheralDeviceCategory.INGEST,
// type: PeripheralDeviceType.MOS,
Expand All @@ -583,7 +586,7 @@
// }
// })

// test('setStatus with bad arguments', () => {

Check warning on line 589 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented

Check warning on line 589 in meteor/server/api/__tests__/peripheralDevice.test.ts

View workflow job for this annotation

GitHub Actions / Typecheck and Lint Core

Some tests seem to be commented
// try {
// Meteor.call(PeripheralDeviceAPIMethods.setStatus, 'wibbly', device.token, { statusCode: 0 })
// fail('expected to throw')
Expand Down Expand Up @@ -745,4 +748,50 @@
})
})
})

describe('auto-assign devices', () => {
beforeEach(async () => {
env = await setupDefaultStudioEnvironment()
})

test('initialize auto-assign to single studio', async () => {
if (DEBUG) setLogLevel(LogLevel.DEBUG)

// Prepare a new device id, not present in the DB yet
const newDeviceId = getRandomId()
const token = getRandomString()
const options: PeripheralDeviceInitOptions = {
category: PeripheralDeviceCategory.PLAYOUT,
type: PeripheralDeviceType.PLAYOUT,
subType: 'test',
name: 'autoAssignDevice',
connectionId: 'testconn',
configManifest: {
deviceConfigSchema: JSONBlobStringify({}),
subdeviceManifest: {},
},
documentationUrl: 'http://example.com',
}

// Ensure it's not present yet
expect(await PeripheralDevices.findOneAsync(newDeviceId)).toBeFalsy()

// Initialize the device
await MeteorCall.peripheralDevice.initialize(newDeviceId, token, options)

// Ensure the device exists and is assigned to the only studio in the db
const initDevice = (await PeripheralDevices.findOneAsync(newDeviceId)) as PeripheralDevice
expect(initDevice).toBeTruthy()
expect(initDevice.studioAndConfigId).toBeTruthy()
expect(initDevice.studioAndConfigId!.studioId).toBe(env.studio._id)

// Ensure it is created in the "parent devices" mapping (deviceSettings)
const studio = await Studios.findOneAsync(env.studio._id)
const deviceSettings = applyAndValidateOverrides(studio!.peripheralDeviceSettings.deviceSettings).obj
expect(deviceSettings[unprotectString(newDeviceId)]).toBeTruthy()

const playoutDevices = applyAndValidateOverrides(studio!.peripheralDeviceSettings.playoutDevices).obj
expect(playoutDevices[unprotectString(newDeviceId)]).toBeFalsy()
})
})
})
41 changes: 41 additions & 0 deletions meteor/server/api/peripheralDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ export namespace ServerPeripheralDeviceAPI {

documentationUrl: options.documentationUrl,
})

// If there is only one Studio, assign the device to that studio.
// Overall plan at the moment is to have only 1 studio per sofie instance in mid-term future
await assignToStudioIfOnlyOnePresent(deviceId, options)
}
if (options.configManifest?.translations) {
await upsertBundles(
Expand All @@ -175,6 +179,43 @@ export namespace ServerPeripheralDeviceAPI {
}
return deviceId
}

async function assignToStudioIfOnlyOnePresent(deviceId: PeripheralDeviceId, options: PeripheralDeviceInitOptions) {
if ((await Studios.countDocuments()) !== 1) {
return
}

const studio = await Studios.findOneAsync({})

if (!studio) {
return
}

const configId = unprotectString(deviceId)

await Studios.updateAsync(studio._id, {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure how I feel about this creating a config object for itself when attaching..

There are 2 scenarios:

  1. blueprints provide config objects for each device. meaning the user will need to go to the settings to reassign it to the correct one and delete this temporary object
  2. blueprints done provide config objects. meaning this was helpful.

As the recommended approach for blueprints is 1, should we be prioritising that being smoother?
I dont know, Im curious what anyone else thinks

Copy link
Contributor

Choose a reason for hiding this comment

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

I too feel that this is a false friend in our "Configuration as Code" philosophy. I think we should probably re-think our "first run story": we should have the blueprints be the first thing that gets set up in a System, have them pre-populate the deviceSettings for a Studio and then, on first connection, the Peripheral Devices are matched to a known config.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I dont disagree with that, but even if blueprints are supposed to pre-generate the configurations, there will be cases where they havent so having a fallback to attach them with an empty config is still good.

So I am leaning towards leaving that larger rethinking until we have actually removed support for multiple studios; as that should in theory simplify these things even further

Copy link
Author

Choose a reason for hiding this comment

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

@Julusian @jstarpl guys, given that configIds in blueprints are arbitrary (correct me if I'm wrong) is there a way we can programmatically determine here which configId to hook up?

$push: {
[`peripheralDeviceSettings.deviceSettings.overrides`]: {
op: 'set',
path: configId,
Copy link
Member

Choose a reason for hiding this comment

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

While the risk is low, it is possible that this will replace an existing config with this empty one. I think this needs to make sure that configId isnt already defined before adding this override

value: {
name: options.name,
options: {},
},
},
},
})

await PeripheralDevices.updateAsync(deviceId, {
$set: {
studioAndConfigId: {
configId,
studioId: studio._id,
},
},
})
}

export async function unInitialize(
context: MethodContext,
deviceId: PeripheralDeviceId,
Expand Down
Loading