Skip to content

Commit 29564fc

Browse files
committed
feat: import usage statistics scraper
1 parent 5b4658e commit 29564fc

19 files changed

+3103
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Create and publish usage-statistics image
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths:
7+
- "usage-statistics/**"
8+
- ".github/workflows/usage-statistics.yml"
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: ${{ github.repository }}-usage-statistics
13+
14+
jobs:
15+
build-and-push-image:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
packages: write
20+
attestations: write
21+
id-token: write
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v5
26+
27+
- name: Log in to the Container registry
28+
uses: docker/login-action@v3
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract metadata (tags, labels) for Docker
35+
id: meta
36+
uses: docker/metadata-action@v5
37+
with:
38+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
39+
40+
- name: Build and push Docker image
41+
id: push
42+
uses: docker/build-push-action@v6
43+
with:
44+
context: usage-statistics
45+
push: true
46+
tags: ${{ steps.meta.outputs.tags }}
47+
labels: ${{ steps.meta.outputs.labels }}
48+
49+
- name: Generate artifact attestation
50+
uses: actions/attest-build-provenance@v3
51+
with:
52+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
53+
subject-digest: ${{ steps.push.outputs.digest }}
54+
push-to-registry: true

usage-statistics/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

usage-statistics/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
run.sh
3+
dist
4+
.yarn

usage-statistics/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

usage-statistics/.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

usage-statistics/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM node:22-alpine
2+
RUN apk add --no-cache tzdata
3+
4+
WORKDIR /app
5+
COPY . .
6+
7+
RUN corepack enable
8+
RUN yarn install --immutable
9+
RUN yarn build
10+
11+
CMD ["node", "dist/main.js"]
12+

usage-statistics/eslint.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { generateEslintConfig } from '@sofie-automation/code-standard-preset/eslint/main.mjs'
2+
3+
export default await generateEslintConfig({})

usage-statistics/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "companion-susage-statistics",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"main": "dist/main.js",
6+
"author": "Julian Waller <git@julusian.co.uk>",
7+
"license": "MIT",
8+
"private": true,
9+
"scripts": {
10+
"build": "yarn tsc -p tsconfig.build.json",
11+
"lint:raw": "run eslint",
12+
"lint": "yarn lint:raw .",
13+
"lint-fix": "yarn lint --fix",
14+
"dev": "node --loader ts-node/esm src/main.ts",
15+
"eslint": "./node_modules/.bin/eslint"
16+
},
17+
"prettier": "@sofie-automation/code-standard-preset/.prettierrc.json",
18+
"devDependencies": {
19+
"@sofie-automation/code-standard-preset": "^3.0.0",
20+
"@types/node": "^22.15.31",
21+
"eslint": "^9.29.0",
22+
"prettier": "^3.5.3",
23+
"ts-node": "^10.9.2",
24+
"typescript": "~5.7.3"
25+
},
26+
"dependencies": {
27+
"mysql2": "^3.14.1",
28+
"sequelize": "^6.37.7",
29+
"tslib": "^2.8.1"
30+
},
31+
"packageManager": "yarn@4.9.2"
32+
}

usage-statistics/src/main.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { initDb } from './models-raw.js'
2+
import { initDb2 } from './models-grafana.js'
3+
import { runUsers } from './users-data.js'
4+
import { runModules } from './modules-data.js'
5+
import { runPlatforms } from './platforms.js'
6+
import { runPlatformStats } from './platform-stats.js'
7+
8+
console.log('hello world')
9+
10+
const db = await initDb()
11+
await initDb2()
12+
13+
await Promise.all([
14+
// Set everything going
15+
runUsers(db),
16+
runModules(db),
17+
runPlatforms(db),
18+
runPlatformStats(db),
19+
])
20+
21+
console.log('all done!')
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { Sequelize, Model, DataTypes, DataType, ModelAttributeColumnOptions } from 'sequelize'
2+
3+
const mysqlUrl = process.env.MYSQL_URL2 || ''
4+
5+
if (!mysqlUrl || mysqlUrl.length === 0) {
6+
throw new Error('MYSQL_URL2 is required')
7+
}
8+
9+
const sequelize = new Sequelize(mysqlUrl)
10+
11+
export interface IModuleInfo {
12+
id: number
13+
type: '1day' | '7day' | '30day'
14+
module: string
15+
user_count: number
16+
ts: Date // recorded
17+
}
18+
19+
export class ModuleInfo extends Model implements IModuleInfo {
20+
public id!: number
21+
public type!: '1day' | '7day' | '30day'
22+
public module!: string
23+
public user_count!: number
24+
public ts!: Date // recorded
25+
}
26+
27+
ModuleInfo.init(
28+
{
29+
id: {
30+
type: DataTypes.INTEGER,
31+
autoIncrement: true,
32+
primaryKey: true,
33+
},
34+
type: {
35+
type: DataTypes.ENUM,
36+
values: ['1day', '7day', '30day'],
37+
},
38+
module: DataTypes.STRING,
39+
user_count: DataTypes.INTEGER,
40+
ts: DataTypes.DATE,
41+
} satisfies { [field in keyof IModuleInfo]: DataType | ModelAttributeColumnOptions },
42+
{
43+
sequelize,
44+
modelName: 'companion_modules',
45+
freezeTableName: true,
46+
createdAt: false,
47+
updatedAt: false,
48+
deletedAt: false,
49+
indexes: [],
50+
}
51+
)
52+
53+
export interface IUsersInfo {
54+
id: number
55+
type: '1day' | '7day' | '30day'
56+
version: string
57+
user_count: number
58+
ts: Date // recorded
59+
}
60+
61+
export class UsersInfo extends Model implements IUsersInfo {
62+
public id!: number
63+
public type!: '1day' | '7day' | '30day'
64+
public version!: string
65+
public user_count!: number
66+
public ts!: Date // recorded
67+
}
68+
69+
UsersInfo.init(
70+
{
71+
id: {
72+
type: DataTypes.INTEGER,
73+
autoIncrement: true,
74+
primaryKey: true,
75+
},
76+
type: {
77+
type: DataTypes.ENUM,
78+
values: ['1day', '7day', '30day'],
79+
},
80+
version: DataTypes.STRING,
81+
user_count: DataTypes.INTEGER,
82+
ts: DataTypes.DATE,
83+
} satisfies { [field in keyof IUsersInfo]: DataType | ModelAttributeColumnOptions },
84+
{
85+
sequelize,
86+
modelName: 'companion_users',
87+
freezeTableName: true,
88+
createdAt: false,
89+
updatedAt: false,
90+
deletedAt: false,
91+
indexes: [],
92+
}
93+
)
94+
95+
export interface IPlatformsInfo {
96+
id: number
97+
type: '1day' | '7day' | '30day'
98+
platform: string
99+
arch: string
100+
user_count: number
101+
ts: Date // recorded
102+
}
103+
export class PlatformsInfo extends Model implements IPlatformsInfo {
104+
public id!: number
105+
public type!: '1day' | '7day' | '30day'
106+
public platform!: string
107+
public arch!: string
108+
public user_count!: number
109+
public ts!: Date // recorded
110+
}
111+
112+
PlatformsInfo.init(
113+
{
114+
id: {
115+
type: DataTypes.INTEGER,
116+
autoIncrement: true,
117+
primaryKey: true,
118+
},
119+
type: {
120+
type: DataTypes.ENUM,
121+
values: ['1day', '7day', '30day'],
122+
},
123+
platform: DataTypes.STRING,
124+
arch: DataTypes.STRING,
125+
user_count: DataTypes.INTEGER,
126+
ts: DataTypes.DATE,
127+
} satisfies { [field in keyof IPlatformsInfo]: DataType | ModelAttributeColumnOptions },
128+
{
129+
sequelize,
130+
modelName: 'companion_platforms',
131+
freezeTableName: true,
132+
createdAt: false,
133+
updatedAt: false,
134+
deletedAt: false,
135+
indexes: [],
136+
}
137+
)
138+
139+
export interface IPlatformStatsInfo {
140+
id: number
141+
type: '1day' | '7day' | '30day'
142+
platform: string
143+
release_group: string
144+
user_count: number
145+
ts: Date // recorded
146+
}
147+
148+
export class PlatformStatsInfo extends Model implements IPlatformStatsInfo {
149+
public id!: number
150+
public type!: '1day' | '7day' | '30day'
151+
public platform!: string
152+
public release_group!: string
153+
public user_count!: number
154+
public ts!: Date // recorded
155+
}
156+
157+
PlatformStatsInfo.init(
158+
{
159+
id: {
160+
type: DataTypes.INTEGER,
161+
autoIncrement: true,
162+
primaryKey: true,
163+
},
164+
type: {
165+
type: DataTypes.ENUM,
166+
values: ['1day', '7day', '30day'],
167+
},
168+
platform: DataTypes.STRING,
169+
release_group: DataTypes.STRING,
170+
user_count: DataTypes.INTEGER,
171+
ts: DataTypes.DATE,
172+
} satisfies { [field in keyof IPlatformStatsInfo]: DataType | ModelAttributeColumnOptions },
173+
{
174+
sequelize,
175+
modelName: 'companion_platform_stats',
176+
freezeTableName: true,
177+
createdAt: false,
178+
updatedAt: false,
179+
deletedAt: false,
180+
indexes: [],
181+
}
182+
)
183+
184+
export async function initDb2(): Promise<Sequelize> {
185+
await sequelize.authenticate()
186+
// await sequelize.sync()
187+
188+
return sequelize
189+
}

0 commit comments

Comments
 (0)