Skip to content

Commit 71dc9a1

Browse files
tkosterkleinfreund
andauthored
Colour gradient for number card (#209)
* Color gradient for number card * Move dashboard migrations into separate module * Apply suggestions from code review Co-Authored-By: Philipp Rudloff <[email protected]> * Fix asymmetrical GET/POST dashboard API and move the dashboard migration to the server * Fix simulation and example dashboard file Co-authored-by: Philipp Rudloff <[email protected]>
1 parent b5e294c commit 71dc9a1

21 files changed

+512
-51
lines changed

.data/dashboard.blank.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
"blockSize": [250, 200],
44
"bgColor": "papayawhip",
55
"tiles": [],
6-
"title": "\u26a1electric io",
6+
"title": "⚡electric io",
77
"bgImageUrl": "",
88
"bgImageRepeat": "true"
9-
}
9+
},
10+
"version": 1
1011
}

.data/dashboard.example.json

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
{
2626
"title": "Study Temperature",
27-
"type": "lineChart",
27+
"type": "line-chart",
2828
"lineColor": "#FF6384",
2929
"deviceId": "AZ3166",
3030
"property": "temperature",
@@ -34,7 +34,7 @@
3434
},
3535
{
3636
"title": "Jenn's Coolness Factor 😎",
37-
"type": "lineChart",
37+
"type": "line-chart",
3838
"lineColor": "#6b66ff",
3939
"deviceId": "Jenn",
4040
"property": "coolness",
@@ -50,11 +50,16 @@
5050
"property": "humidity",
5151
"position": [296, 465],
5252
"size": [1, 1],
53-
"id": "101ca6cb-ba75-4e5c-9db6-d29ba7ab9f51"
53+
"id": "101ca6cb-ba75-4e5c-9db6-d29ba7ab9f51",
54+
"textColorMode": "single",
55+
"lowValue": 20,
56+
"lowTextColor": "#1282baff",
57+
"highValue": 40,
58+
"highTextColor": "#cb1475ff"
5459
},
5560
{
5661
"title": "Noise 🙉",
57-
"type": "lineChart",
62+
"type": "line-chart",
5863
"lineColor": "#60dd4d",
5964
"deviceId": "Tessel2",
6065
"property": "sound",
@@ -70,6 +75,8 @@
7075
"size": [1, 1],
7176
"id": "88e12934-c628-48cb-8b84-b16c2e311105"
7277
}
73-
]
74-
}
78+
],
79+
"title": "⚡electric io"
80+
},
81+
"version": 1
7582
}

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This page should get you up and running with how to run locally and how to adher
1212
- [Mobile support](#mobile-support)
1313
3. [Pull requests](#pull-requests)
1414
- [Avoiding merge conflicts](#merge-conflicts)
15+
4. [Dashboard settings migrations](#dashboard-settings-migrations)
1516

1617
## Installation
1718

@@ -191,3 +192,19 @@ This will:
191192
You can then `git push` to update the PR if you’ve already submitted one.
192193

193194
✨✨✨ Happy Contributing!! ✨✨✨
195+
196+
## Dashboard settings migrations
197+
198+
If you change the structure of the dashboard settings in a way that would prevent an existing dashboard from loading correctly, you should add a migration.
199+
200+
Dashboard migrations are functions in the `dashboardMigrations` array in `lib/services/dashboard-migrations.service.js`. Each function in this array corresponds to a new dashboard version.
201+
202+
The value of the dashboard `version` property is an integer corresponding to the number of migrations in the array of all dashboard migrations. An upgrade is required if the dashboard version is lower than the number of migrations in this array. When the server starts, it checks the `version` property and upgrades the dashboard if necessary. (A dashboard with no version property is at version `0`.)
203+
204+
For example, say the dashboard is at version `2` and there are four functions in the migrations array. This means the dashboard is two versions behind. The upgrade runs the two last functions in the migrations array and sets the dashboard version to `4`.
205+
206+
### Adding a dashboard migration
207+
208+
To add a dashboard migration, write a function that performs the migration and **append** it to the array of migrations in `dashboard-migrations.service.js`. This function should have one parameter, the dashboard settings object, and modify it so that the dashboard will load correctly in the current version. Always use sensible defaults to avoid showing users broken or surprising behaviour.
209+
210+
Do not forget to update the blank and default dashboard settings files in the `.data` directory if necessary.

lib/routes.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function injectRoutes({ iotHubService }) {
1111
try {
1212
const dashboardSettings = await DashboardService.getDashboardSettings();
1313

14-
response.status(200).json(dashboardSettings);
14+
response.status(200).json(dashboardSettings.dashboard);
1515
} catch (error) {
1616
response.status(500).send({
1717
data: {
@@ -25,7 +25,9 @@ function injectRoutes({ iotHubService }) {
2525
debug("LOG: Saving dashboard settings.");
2626

2727
try {
28-
await DashboardService.saveDashboardSettings(request.body);
28+
const dashboardSettings = await DashboardService.getDashboardSettings();
29+
dashboardSettings.dashboard = request.body;
30+
await DashboardService.saveDashboardSettings(dashboardSettings);
2931

3032
response.status(200).send();
3133
} catch (error) {

lib/routes.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const mockDashboardSettings = {
3333
],
3434
editMode: "unlocked"
3535
},
36-
deviceList: ["AZ3166", "Tessel2", "Jenn"]
36+
version: 1
3737
};
3838

3939
const mockDeviceIds = ["AZ3166", "Tessel2", "Jenn"];
@@ -52,7 +52,7 @@ describe("API endpoints", () => {
5252
const response = await request(app).get("/api/dashboard");
5353

5454
expect(response.status).toBe(200);
55-
expect(response.body).toEqual(mockDashboardSettings);
55+
expect(response.body).toEqual(mockDashboardSettings.dashboard);
5656
});
5757

5858
test("can’t get dashboard settings if the service produces an error", async () => {
@@ -78,7 +78,7 @@ describe("API endpoints", () => {
7878

7979
const response = await request(app)
8080
.post("/api/dashboard")
81-
.send(mockDashboardSettings);
81+
.send(mockDashboardSettings.dashboard);
8282

8383
expect(response.status).toBe(200);
8484
});
@@ -92,7 +92,7 @@ describe("API endpoints", () => {
9292

9393
const response = await request(app)
9494
.post("/api/dashboard")
95-
.send(mockDashboardSettings);
95+
.send(mockDashboardSettings.dashboard);
9696

9797
expect(response.status).toBe(500);
9898
expect(response.body.data.message).toContain("Could not write to");
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = {
2+
upgradeDashboard
3+
};
4+
5+
function upgradeDashboard(settings, migrations = dashboardMigrations) {
6+
const storedVersion = settings.version || 0;
7+
const currentVersion = migrations.length;
8+
9+
for (let i = storedVersion; i < currentVersion; ++i) {
10+
migrations[i](settings.dashboard);
11+
}
12+
13+
settings.version = currentVersion;
14+
return storedVersion < currentVersion;
15+
}
16+
17+
const dashboardMigrations = [migrateDashboardVersion0To1];
18+
19+
/**
20+
* Migrate from dashboard version 0 to version 1:
21+
* - Number tile: added text color gradient properties
22+
*/
23+
function migrateDashboardVersion0To1(dashboard) {
24+
for (const tile of dashboard.tiles) {
25+
if (tile.type === "number") {
26+
tile.textColorMode = "single";
27+
tile.lowValue = 20.0;
28+
tile.lowTextColor = "#1282baff";
29+
tile.highValue = 40.0;
30+
tile.highTextColor = "#cb1475ff";
31+
}
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { upgradeDashboard } = require("./dashboard-migrations.service");
2+
3+
describe("upgradeDashboard", () => {
4+
it("does not invoke migrations on an up-to-date dashboard", () => {
5+
const migration = jest.fn();
6+
const migrations = [migration, migration, migration];
7+
const dashboard = { version: 3 };
8+
9+
upgradeDashboard(dashboard, migrations);
10+
11+
expect(migration).not.toHaveBeenCalled();
12+
});
13+
14+
it("invokes migrations in order", () => {
15+
const migrationsPerformed = [];
16+
const migration1 = jest.fn(() => migrationsPerformed.push(1));
17+
const migration2 = jest.fn(() => migrationsPerformed.push(2));
18+
const migration3 = jest.fn(() => migrationsPerformed.push(3));
19+
const migrations = [migration1, migration2, migration3];
20+
const dashboard = {};
21+
22+
upgradeDashboard(dashboard, migrations);
23+
24+
expect(migrationsPerformed).toEqual([1, 2, 3]);
25+
expect(dashboard.version).toEqual(3);
26+
});
27+
28+
it("skips migrations already performed", () => {
29+
const migrationsPerformed = [];
30+
const migration1 = jest.fn(() => migrationsPerformed.push(1));
31+
const migration2 = jest.fn(() => migrationsPerformed.push(2));
32+
const migration3 = jest.fn(() => migrationsPerformed.push(3));
33+
const migrations = [migration1, migration2, migration3];
34+
const dashboard = { version: 2 };
35+
36+
upgradeDashboard(dashboard, migrations);
37+
38+
expect(migrationsPerformed).toEqual([3]);
39+
expect(dashboard.version).toEqual(3);
40+
});
41+
});

lib/services/dashboard.service.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ module.exports = {
4343
debug(message);
4444
reject(message);
4545
} else {
46-
const fileContents = `{ "dashboard": ${JSON.stringify(
47-
dashboardSettings
48-
)} }`;
46+
const fileContents = JSON.stringify(dashboardSettings);
4947

5048
fs.writeFile(DASHBOARD_FILE_PATH, fileContents, "utf8", error => {
5149
if (error) {

lib/services/dashboard.service.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const mockDashboardSettings = {
3131
],
3232
editMode: "unlocked"
3333
},
34-
deviceList: ["AZ3166", "Tessel2", "Jenn"]
34+
version: 1
3535
};
3636

3737
describe("DashboardService", () => {

lib/simulator/simulatedDevice.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ simulatedDevice.prototype.start = function() {
1111
const [min, max] = prop.minmax;
1212
body[prop.name] = Math.random() * (max - min) + min;
1313
});
14-
const annotations = {
14+
const systemProperties = {
1515
"iothub-connection-device-id": this.id,
1616
"iothub-enqueuedtime": new Date()
1717
};
18-
const message = { annotations, body };
18+
const message = { systemProperties, body };
1919

2020
this.receiver.emitMessage(message);
2121
}, this.telemetry.interval);

0 commit comments

Comments
 (0)