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
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,19 @@ screenly edge-app instance create

## Configuration

| Setting | Description | Required | Default |
| --------- | ------------------------------- | -------- | ------------------ |
| `message` | The message displayed on screen | No | `Hello, Screenly!` |
Settings are defined in `screenly.yml` and mirrored in `screenly_qc.yml` (used for the QC/staging build).

| Setting | Description | Required | Default |
| ------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------- |
| `access_token` | For testing only. Production support for Puzzel ID OAuth is not implemented yet. | No | — |
| `customer_key` | Your Puzzel customer number. | Yes | — |
| `display_errors` | For debugging purposes to display errors on the screen. | No | `false` |
| `override_locale` | Override the default locale with a supported language code (e.g., en_US, fr_FR, de_DE). | No | `en` |
| `override_timezone` | Override the default timezone with a supported timezone identifier (e.g., Europe/London, America/New_York). | No | — |
| `refresh_interval` | How often to refresh dashboard data, in seconds. | No | `15` |
| `sentry_dsn` | Sentry Client Key from Sentry SDK for error capturing. | No | — |
| `user_group_name` | Filter agent statuses to a specific Puzzel user group. Leave blank to show all agents. | No | — |
| `user_id` | The Puzzel user ID associated with your OIDC client's Product Access. Required for API requests to succeed. | Yes | — |

## Screenshots

Expand Down
12 changes: 12 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

204 changes: 204 additions & 0 deletions e2e/screenshots.lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type { Browser } from '@playwright/test'
import {
createMockScreenlyForScreenshots,
FIXED_SCREENSHOT_DATE,
getScreenshotsDir,
setupClockMock,
setupScreenlyJsMock,
} from '@screenly/edge-apps/test/screenshots'
import path from 'path'

const MOCK_CUSTOMER_KEY = '12345'
const MOCK_USER_ID = '67890'

export const MOCK_CREDENTIALS = {
token: 'mock-access-token',
metadata: {},
}

export const MOCK_QUEUES = [
{
id: 1,
description: 'Support',
queueSize: 3,
agentsLoggedOn: 4,
agentsInPause: 1,
agentsReady: 3,
waitTimeMaxSeconds: 95,
waitTimeAverageSeconds: 32,
callsOfferedToday: 58,
callsAnsweredToday: 54,
ciqsOfferedToday: 6,
ciqsAnsweredToday: 5,
sla: 92,
},
{
id: 2,
description: 'Sales',
queueSize: 1,
agentsLoggedOn: 3,
agentsInPause: 0,
agentsReady: 3,
waitTimeMaxSeconds: 40,
waitTimeAverageSeconds: 18,
callsOfferedToday: 21,
callsAnsweredToday: 20,
ciqsOfferedToday: 3,
ciqsAnsweredToday: 3,
sla: 98,
},
{
id: 3,
description: 'Billing',
queueSize: 0,
agentsLoggedOn: 2,
agentsInPause: 1,
agentsReady: 1,
waitTimeMaxSeconds: 0,
waitTimeAverageSeconds: 0,
callsOfferedToday: 9,
callsAnsweredToday: 9,
ciqsOfferedToday: 0,
ciqsAnsweredToday: 0,
sla: 100,
},
]

export const MOCK_AGENTS = [
{
userId: 1,
firstName: 'Ada',
lastName: 'Nilsen',
contactCentreStatus: 'LoggedOn',
userStatus: 'Available',
userGroupName: 'Support',
callsOffered: 12,
callsAnswered: 11,
},
{
userId: 2,
firstName: 'Bo',
lastName: 'Eriksen',
contactCentreStatus: 'Pause',
userStatus: 'Busy',
userGroupName: 'Support',
callsOffered: 9,
callsAnswered: 9,
},
{
userId: 3,
firstName: 'Cleo',
lastName: 'Vik',
contactCentreStatus: 'LoggedOn',
userStatus: 'Available',
userGroupName: 'Sales',
callsOffered: 15,
callsAnswered: 14,
},
{
userId: 4,
firstName: 'Dan',
lastName: 'Aasen',
contactCentreStatus: 'LoggedOff',
userStatus: 'System',
userGroupName: 'Billing',
callsOffered: 4,
callsAnswered: 4,
},
]

export const { screenlyJsContent: dashboardScreenlyJsContent } =
createMockScreenlyForScreenshots(
{ coordinates: [37.3861, -122.0839], location: 'Silicon Valley, USA' },
{
customer_key: MOCK_CUSTOMER_KEY,
user_id: MOCK_USER_ID,
refresh_interval: '15',
display_errors: 'false',
screenly_oauth_tokens_url: 'http://localhost:3000/',
screenly_app_auth_token: 'mock-token',
},
)

export type BrowserContext = Awaited<ReturnType<Browser['newContext']>>

export async function takeScreenshot(
browser: Browser,
width: number,
height: number,
filename: string,
screenlyJsContent: string,
setup: (context: BrowserContext) => Promise<void>,
): Promise<void> {
const screenshotsDir = getScreenshotsDir()
const context = await browser.newContext({ viewport: { width, height } })
const page = await context.newPage()

await setupClockMock(page, FIXED_SCREENSHOT_DATE)
await setupScreenlyJsMock(page, screenlyJsContent)
await setup(context)

await page.goto('/')
await page.waitForLoadState('networkidle')

await page.screenshot({
path: path.join(screenshotsDir, filename),
fullPage: false,
})

await context.close()
}

function mockCredentials(context: BrowserContext): Promise<void> {
return context.route(/access_token\//, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(MOCK_CREDENTIALS),
}),
)
}

export async function setupDashboardRoutes(
context: BrowserContext,
): Promise<void> {
await mockCredentials(context)

await context.route(/visualqueues\/stateinformation/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ result: MOCK_QUEUES }),
}),
)

await context.route(/users\/stateinformation/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ result: MOCK_AGENTS }),
}),
)
}

export async function setupEmptyQueuesRoutes(
context: BrowserContext,
): Promise<void> {
await mockCredentials(context)

await context.route(/visualqueues\/stateinformation/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ result: [] }),
}),
)

await context.route(/users\/stateinformation/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ result: [] }),
}),
)
}
56 changes: 30 additions & 26 deletions e2e/screenshots.spec.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import { test } from '@playwright/test'
import { RESOLUTIONS } from '@screenly/edge-apps/test/screenshots'
import {
createMockScreenlyForScreenshots,
getScreenshotsDir,
RESOLUTIONS,
setupClockMock,
setupScreenlyJsMock,
} from '@screenly/edge-apps/test/screenshots'
import path from 'path'

const { screenlyJsContent } = createMockScreenlyForScreenshots()
dashboardScreenlyJsContent,
setupDashboardRoutes,
setupEmptyQueuesRoutes,
takeScreenshot,
} from './screenshots.lib'

for (const { width, height } of RESOLUTIONS) {
test(`screenshot ${width}x${height}`, async ({ browser }) => {
const screenshotsDir = getScreenshotsDir()

const context = await browser.newContext({ viewport: { width, height } })
const page = await context.newPage()

await setupClockMock(page)
await setupScreenlyJsMock(page, screenlyJsContent)

await page.goto('/')
await page.waitForLoadState('networkidle')

await page.screenshot({
path: path.join(screenshotsDir, `${width}x${height}.png`),
fullPage: false,
})
test(`screenshot dashboard ${width}x${height}`, async ({ browser }) => {
await takeScreenshot(
browser,
width,
height,
`dashboard-${width}x${height}.png`,
dashboardScreenlyJsContent,
(context) => setupDashboardRoutes(context),
)
})
}

await context.close()
for (const [width, height] of [
[1920, 1080],
[1080, 1920],
]) {
test(`screenshot empty-queues ${width}x${height}`, async ({ browser }) => {
await takeScreenshot(
browser,
width,
height,
`empty-queues-${width}x${height}.png`,
dashboardScreenlyJsContent,
(context) => setupEmptyQueuesRoutes(context),
)
})
}
16 changes: 6 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
reference-height="1080"
orientation="auto"
>
<div id="app" class="flex h-full w-full flex-col bg-neutral-900 text-white">
<app-header show-date></app-header>
<div
id="app"
class="flex h-full w-full flex-col bg-neutral-900 px-12 py-7 text-white portrait:px-8 portrait:py-7"
>
<app-header class="bg-neutral-800 [zoom:1.4]" show-date></app-header>
<main
id="dashboard"
class="flex flex-1 flex-col gap-6 overflow-hidden p-6 portrait:p-4"
class="flex flex-1 flex-col gap-6 overflow-hidden py-6 portrait:py-4"
style="display: none"
>
<section
Expand All @@ -28,13 +31,6 @@
class="grid flex-1 grid-cols-6 gap-3 overflow-hidden portrait:grid-cols-3"
></section>
</main>
<div
id="error-screen"
class="flex flex-1 items-center justify-center p-8 text-center"
style="display: none"
>
<p id="error-message" class="text-3xl portrait:text-xl"></p>
</div>
</div>
</auto-scaler>
<script type="module" src="/src/main.ts"></script>
Expand Down
4 changes: 4 additions & 0 deletions mock-server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PUZZEL_CLIENT_KEY=your_puzzel_client_key_here
PUZZEL_CLIENT_SECRET=your_puzzel_client_secret_here
PUZZEL_TENANT_ID=your_puzzel_tenant_id_here
PUZZEL_USER_ID=your_puzzel_user_id_here
2 changes: 2 additions & 0 deletions mock-server/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules/
dist/
.env
auth.db
Loading