Skip to content

Commit 7572153

Browse files
ClementBouvierNfreddidierRTE
authored andcommitted
Added support for loading custom global CSS (#7708)
Signed-off-by: ClementBouvierN <[email protected]>
1 parent da23b7f commit 7572153

File tree

8 files changed

+76
-43
lines changed

8 files changed

+76
-43
lines changed

config/docker/docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ services:
155155
- "./ui-config:/usr/share/nginx/html/config"
156156
- "./nginx.conf:/etc/nginx/conf.d/default.conf"
157157
- "./externalJs:/usr/share/nginx/html/externalJs"
158+
- "./externalCss:/usr/share/nginx/html/externalCss"
158159
- "../../src/test/externalWebAppExample:/usr/share/nginx/html/external/appExample"
159160
cards-external-diffusion:
160161
container_name: cards-external-diffusion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* Copyright (c) 2025, RTE (http://www.rte-france.com)
2+
* See AUTHORS.txt
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
* This file is part of the OperatorFabric project.
8+
*/
9+
10+
.custom-external-font-color {
11+
color: #777;
12+
}

config/docker/ui-config/web-ui.json

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"useDescriptionFieldForEntityList": false
154154
},
155155
"customJsToLoad": ["http://localhost:2002/externalJs/handlebarsExample.js","http://localhost:2002/externalJs/loadTags.js"],
156+
"customCssToLoad": ["http://localhost:2002/externalCss/externalStyle.css"],
156157
"dashboard": {
157158
"processStateRedirects": [
158159
{

src/docs/asciidoc/deployment/configuration/web-ui_configuration.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ a|authentication mode, available options:
193193

194194
|customJsToLoad||no|List of URLs of javascript files to be loaded at startup
195195

196+
|customCssToLoad||no|List of URLs of css files to be loaded at startup
197+
196198
|===
197199

198200
IMPORTANT:

src/docs/asciidoc/reference_doc/template_description.adoc

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2018-2024 RTE (http://www.rte-france.com)
1+
// Copyright (c) 2018-2025 RTE (http://www.rte-france.com)
22
// See AUTHORS.txt
33
// This document is subject to the terms of the Creative Commons Attribution 4.0 International license.
44
// If a copy of the license was not distributed with this
@@ -794,7 +794,8 @@ The library https://www.chartjs.org/[charts.js] is integrated in OperatorFabric,
794794

795795
The version of chartjs integrated in OperatorFabric is v3.7.1.
796796

797-
== Custom javascript files
797+
== Custom javascript or css files
798798

799-
It is possible to configure Opfab to load custom javascript files at startup. This allows to share common functions and business logic between templates.
800-
The list of URLs of javascript files to be loaded can be configured using `customJsToLoad` parameter in `web-ui.json` file.
799+
It is possible to configure Opfab to load custom javascript or css files at startup. This allows to share common functions, business logic and styling between templates.
800+
The list of URLs of javascript files to be loaded can be configured using `customJsToLoad` parameter in `web-ui.json` file.
801+
The list of URLs of css files to be loaded can be configured using `customCssToLoad` parameter in `web-ui.json` file.

src/test/cypress/cypress/integration/CardDetail.spec.js

+34-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2021-2024, RTE (http://www.rte-france.com)
1+
/* Copyright (c) 2021-2025, RTE (http://www.rte-france.com)
22
* See AUTHORS.txt
33
* This Source Code Form is subject to the terms of the Mozilla Public
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,16 +10,16 @@
1010
/* This test file focuses on some state-type specific behaviour in card details header. As the Cypress test suite grows,
1111
it might make sense to merge it with other tests.
1212
* */
13-
import { OpfabGeneralCommands } from '../support/opfabGeneralCommands';
14-
import { FeedCommands } from '../support/feedCommands';
15-
import { ScriptCommands } from "../support/scriptCommands";
13+
import {OpfabGeneralCommands} from '../support/opfabGeneralCommands';
14+
import {FeedCommands} from '../support/feedCommands';
15+
import {ScriptCommands} from '../support/scriptCommands';
1616

17-
describe('Card detail', function() {
17+
describe('Card detail', function () {
1818
const opfab = new OpfabGeneralCommands();
1919
const feed = new FeedCommands();
2020
const script = new ScriptCommands();
2121

22-
before('Set up configuration', function() {
22+
before('Set up configuration', function () {
2323
// This can stay in a `before` block rather than `beforeEach` as long as the test does not change configuration
2424
script.resetUIConfigurationFiles();
2525
script.deleteAllSettings();
@@ -29,9 +29,8 @@ describe('Card detail', function() {
2929
script.sendCard('cypress/cardDetail/cardDetail.json');
3030
});
3131

32-
describe('Check card detail', function() {
33-
34-
it(`Check card detail`, function() {
32+
describe('Check card detail', function () {
33+
it(`Check card detail`, function () {
3534
opfab.loginWithUser('operator1_fr');
3635
feed.openFirstCard();
3736

@@ -86,36 +85,38 @@ describe('Card detail', function() {
8685
cy.get('#handlebars-if').contains(/^ok$/);
8786
cy.get('#handlebars-each').contains(/^123$/);
8887

88+
// external css
89+
cy.get('#external-text').should('have.css', 'color', 'rgb(119, 119, 119)');
8990
// Check card detail footer contains card reception date and time and not contains 'Addressed to' (because operator1_fr is member of only one entity)
9091
cy.get('.opfab-card-received-footer').contains(
91-
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
92+
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
9293
);
9394
cy.get('#opfab-card-details-address-to').should('not.exist');
9495

9596
cy.get('#severityColor').contains('#1074ad');
9697
});
9798

98-
it(`Check card footer for operator4_fr (member of several entities)`, function() {
99+
it(`Check card footer for operator4_fr (member of several entities)`, function () {
99100
opfab.loginWithUser('operator4_fr');
100101
feed.openFirstCard();
101102

102103
// Check card detail footer contains card reception date and time and contains 'Addressed to' with user entities to which the card was sent
103104
cy.get('.opfab-card-received-footer').contains(
104-
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
105+
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
105106
);
106107
cy.get('#opfab-card-details-address-to').contains('Control Center FR North');
107108
cy.get('#opfab-card-details-address-to').contains('Control Center FR South');
108109
});
109110

110-
it(`Check card detail spinner when simulating card processed `, function() {
111+
it(`Check card detail spinner when simulating card processed `, function () {
111112
opfab.loginWithUser('operator1_fr');
112113
feed.openFirstCard();
113114
cy.get('#opfabAPI-display-spinner-button').click();
114115
opfab.checkLoadingSpinnerIsDisplayed();
115116
opfab.checkLoadingSpinnerIsNotDisplayed();
116117
});
117118

118-
it(`Check card detail in archives`, function() {
119+
it(`Check card detail in archives`, function () {
119120
opfab.loginWithUser('operator1_fr');
120121
opfab.navigateToArchives();
121122
// We click the search button
@@ -161,12 +162,12 @@ describe('Card detail', function() {
161162

162163
// Check card detail footer contains card reception date and time and not contains 'Addressed to'
163164
cy.get('.opfab-card-received-footer').contains(
164-
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
165+
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
165166
);
166167
cy.get('#opfab-card-details-address-to').should('not.exist');
167168
});
168169

169-
it(`Check card detail footer for archives for operator4_fr (member of several entities)`, function() {
170+
it(`Check card detail footer for archives for operator4_fr (member of several entities)`, function () {
170171
opfab.loginWithUser('operator4_fr');
171172
opfab.navigateToArchives();
172173

@@ -184,28 +185,28 @@ describe('Card detail', function() {
184185

185186
// Check card detail footer contains card reception date and time and not contains 'Addressed to'
186187
cy.get('.opfab-card-received-footer').contains(
187-
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
188+
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
188189
);
189190
cy.get('#opfab-card-details-address-to').should('not.exist');
190191
});
191192

192-
it(`Check opfab API when response not required `, function() {
193+
it(`Check opfab API when response not required `, function () {
193194
script.sendCard('cypress/cardDetail/cardDetailResponseNotRequired.json');
194195
opfab.loginWithUser('operator1_fr');
195196
feed.openFirstCard();
196197
cy.get('#opfab-currentCard-isUserAllowedToRespond').contains('true');
197198
cy.get('#opfab-currentCard-isUserMemberOfAnEntityRequiredToRespond').contains('false');
198199
});
199200

200-
it(`Check opfab API when response is not possible `, function() {
201+
it(`Check opfab API when response is not possible `, function () {
201202
script.sendCard('cypress/cardDetail/cardDetailResponseNotPossible.json');
202203
opfab.loginWithUser('operator1_fr');
203204
feed.openFirstCard();
204205
cy.get('#opfab-currentCard-isUserAllowedToRespond').contains('false');
205206
cy.get('#opfab-currentCard-isUserMemberOfAnEntityRequiredToRespond').contains('false');
206207
});
207208

208-
it(`Check that a spinner is displayed when the card takes time to load `, function() {
209+
it(`Check that a spinner is displayed when the card takes time to load `, function () {
209210
script.sendCard('cypress/cardDetail/cardDetailResponseNotPossible.json');
210211
cy.delayRequestResponse('/cards-consultation/cards/**');
211212
opfab.loginWithUser('operator1_fr');
@@ -214,7 +215,7 @@ describe('Card detail', function() {
214215
opfab.checkLoadingSpinnerIsNotDisplayed();
215216
});
216217

217-
it(`Check deleted card detail footer in archives`, function() {
218+
it(`Check deleted card detail footer in archives`, function () {
218219
script.sendCard('cypress/userCard/message.json');
219220
opfab.loginWithUser('operator1_fr');
220221
feed.openFirstCard();
@@ -229,18 +230,17 @@ describe('Card detail', function() {
229230
cy.waitDefaultTime();
230231
cy.get('#opfab-archives-cards-list').find('.opfab-archive-sev-information').first().click();
231232

232-
// Check card detail footer contains card reception date
233+
// Check card detail footer contains card reception date
233234
cy.get('.opfab-card-received-footer').contains(
234-
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
235+
/Received : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
235236
);
236-
// Check card detail footer contains card deletion date
237+
// Check card detail footer contains card deletion date
237238
cy.get('.opfab-card-received-footer').contains(
238-
/Deleted or updated : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5][0-9]) ([AP]M))/
239+
/Deleted or updated : \d{2}\/\d{2}\/\d{4} at ((1[0-2]|0?[1-9]):([0-5]\d) ([AP]M))/
239240
);
240241
});
241242

242-
243-
it(`Check showCard link`, function() {
243+
it(`Check showCard link`, function () {
244244
script.sendCard('cypress/userCard/message.json');
245245
script.sendCard('cypress/cardDetail/cardDetail.json');
246246

@@ -253,12 +253,12 @@ describe('Card detail', function() {
253253
cy.get('#showCardLink').click();
254254

255255
cy.hash().should('eq', '#/feed/cards/defaultProcess.process1');
256-
cy.get('#opfab-div-card-template-processed').contains('Hello operator1_fr, you received the following message');
257-
256+
cy.get('#opfab-div-card-template-processed').contains(
257+
'Hello operator1_fr, you received the following message'
258+
);
258259
});
259260

260-
261-
it(`Check show alert message links`, function() {
261+
it(`Check show alert message links`, function () {
262262
script.sendCard('cypress/cardDetail/cardDetail.json');
263263

264264
opfab.loginWithUser('operator1_fr');
@@ -285,10 +285,9 @@ describe('Card detail', function() {
285285
cy.get('#opfab-alert-detail-msg').contains('Alarm message');
286286
cy.get('#opfab-alert-detail-msg').should('have.css', 'background-color', 'rgb(167, 26, 26)');
287287
cy.get('.opfab-alert-close').click();
288-
289288
});
290289

291-
it(`Check getCards API call`, function() {
290+
it(`Check getCards API call`, function () {
292291
script.sendCard('cypress/userCard/message.json');
293292
script.sendCard('cypress/cardDetail/cardDetail.json');
294293

@@ -300,10 +299,9 @@ describe('Card detail', function() {
300299
cy.get('#opfabGetCardsResult').contains('"numberOfElements":1');
301300
cy.get('#opfabGetCardsResult').contains('"_id":"defaultProcess.process1"');
302301
cy.get('#opfabGetCardsResult').contains('"titleTranslated":"Message"');
303-
304302
});
305303

306-
it(`Check isUserAllowedToEdit and editCard API call`, function() {
304+
it(`Check isUserAllowedToEdit and editCard API call`, function () {
307305
script.sendCard('defaultProcess/message.json');
308306
opfab.loginWithUser('operator1_fr');
309307

@@ -314,7 +312,7 @@ describe('Card detail', function() {
314312
cy.get('#opfab-div-card-template-processed').find('#editButton').eq(0).should('contain.text', 'Edit');
315313
cy.get('#opfab-div-card-template-processed').find('#editButton').eq(0).click();
316314

317-
cy.get("of-usercard").should('exist');
315+
cy.get('of-usercard').should('exist');
318316
cy.get('#opfab-usercard-btn-cancel').click();
319317

320318
opfab.logout();
@@ -327,8 +325,6 @@ describe('Card detail', function() {
327325
// check user not allowed to edit does not see the edit button
328326
cy.get('#opfab-div-card-template-processed').should('exist');
329327
cy.get('#editButton').should('not.be.visible');
330-
331328
});
332329
});
333-
334330
});

src/test/resources/bundles/cypress/template/kitchenSink.handlebars

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- Copyright (c) 2021-2024, RTE (http://www.rte-france.com) -->
1+
<!-- Copyright (c) 2021-2025, RTE (http://www.rte-france.com) -->
22
<!-- See AUTHORS.txt -->
33
<!-- This Source Code Form is subject to the terms of the Mozilla Public -->
44
<!-- License, v. 2.0. If a copy of the MPL was not distributed with this -->
@@ -49,6 +49,10 @@
4949
<div> Card id : <a onclick="opfab.navigate.showCardInFeed('{{card.id}}')"><span id="cardId">{{card.id}}</span></a></div>
5050
<div> Card uid : <span id="cardUid">{{card.uid}}</span></div>
5151
<div>Card severity: <span id="severityColor">{{example_severityColor card.severity}}</span></div>
52+
53+
<H3> EXTERNAL CSS </H3>
54+
<div id="external-text" class="custom-external-font-color"> Some test text </div>
55+
5256
<script>
5357
5458

ui/main/src/app/services/applicationLoader/ApplicationLoader.ts

+16
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export class ApplicationLoader {
121121
await this.setActivityArea();
122122
this.initOpfabAPI();
123123
await this.loadCustomScripts();
124+
await this.loadCustomStyles();
124125
this.initServices();
125126
await this.waitForStreamInitDone();
126127
logger.info('Card stream connection established');
@@ -319,4 +320,19 @@ export class ApplicationLoader {
319320
}
320321
}
321322
}
323+
324+
private async loadCustomStyles() {
325+
const customStyles = ConfigService.getConfigValue('customCssToLoad');
326+
if (customStyles) {
327+
for (const style of customStyles) {
328+
await new Promise<void>((resolve) => {
329+
const link = document.createElement('link');
330+
link.rel = 'stylesheet';
331+
link.href = style;
332+
link.onload = () => resolve();
333+
document.head.appendChild(link);
334+
});
335+
}
336+
}
337+
}
322338
}

0 commit comments

Comments
 (0)