Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
09635e2
chore: prepare next development iteration
jenkinsEdificePublic Feb 13, 2026
1a466d8
chore: prepare next development iteration
jenkinsEdificePublic Oct 3, 2025
8aa3ff3
fix(admin): #COCO-3779 add reset communication rules feature
vbillard91 Sep 5, 2025
9f6dd0a
fix-(admin): #COCO-3779 remove incoming comunication from external gr…
vbillard91 Sep 10, 2025
853b313
chore: update ngx-ode version to use develop-b2school
Romu-C Oct 13, 2025
57bf5c3
chore: fix watch mode for legacy apps
jcbe-ode Oct 16, 2025
5531ea8
fix(directory): #COCO-4881, eliminer doublons fonctions (#924)
pb-jo Nov 7, 2025
416aa0d
perf-WB-3167: enriching reaction upsert api
mariusode Jul 8, 2024
f049fbe
chore: update dependencies
jenkinsEdificePublic Dec 22, 2025
734c94a
fix(admin): ajout d'une clé de trad pour actualités/FALC
pb-jo Feb 2, 2026
168ac1e
chore(portal): #ENABLING-608 remove charet in package.json
Romu-C Feb 4, 2026
5011df4
chore(conversation): #ENABLING-613 remove charet in package.json
Romu-C Feb 4, 2026
70be006
chore: prepare next development iteration
jenkinsEdificePublic Feb 16, 2026
03c6906
chore: update dependencies
jenkinsEdificePublic Feb 18, 2026
77cd3c0
tests: add fail fast checkpoints when fatal errors occurred
juniorode Feb 20, 2026
c213e67
fix(conversation): #5300 update profil color
Romu-C Feb 27, 2026
aa2eb63
chore(directory): #COCO-3929, add limits on health and hobbies (#967)
pb-jo Mar 3, 2026
f2433b7
fix(conversation): #COCO-5328 optimize query that count / list messag…
vbillard91 Mar 4, 2026
0734e35
fix(broker): specify timeout when making a request to NATS
juniorode Mar 4, 2026
7c4294d
fix(timeline): #COCO-5341 add some log to track notification and floo…
vbillard91 Mar 5, 2026
a3acfcd
fix(feeder): #COCO-2069, import CSV explicit error message (#970)
pb-jo Mar 5, 2026
c0e5069
fix(app-registry): #COCO-5298, fix mass actions for ADML (#966)
pb-jo Mar 5, 2026
45b0166
fix(conversation): #COCO-5302 manage concurency on send message to av…
vbillard91 Feb 25, 2026
428c9a4
rollback
vbillard91 Feb 26, 2026
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
12 changes: 12 additions & 0 deletions admin/src/main/resources/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"actualites.infos.list": "Actualités : lister les actualités",
"actualites.threads.list": "Actualités : lister les fils d'actualités",
"actualites.view": "Actualités : accéder au service actualités",
"actualites.genai.falc": "Utiliser la simplification de texte (FALC)",
"add.child": "Ajouter un enfant",
"add.class": "Ajouter une classe",
"add.group": "Ajouter un groupe",
Expand Down Expand Up @@ -679,6 +680,17 @@
"management.structure.informations.attach.parent.error.title": "Erreur d'ajout",
"management.structure.informations.attach.parent.success.content": "Cette structure a bien été attachée à une structure parente",
"management.structure.informations.attach.parent.success.title": "Structure parente attachée",
"management.structure.informations.communications.title": "Droits de communications",
"management.structure.informations.communications.description": "Ré-initialisation des droits de communications de l'établissement",
"management.structure.informations.communications.reset.action": "Ré-initialiser",
"management.structure.informations.communications.warning.title": "Réinitialisation",
"management.structure.informations.communications.warning.content": "La réinitialisation des droits de communication est une action irréversible. Elle supprimera les modifications apportées aux règles de communication entre les différents groupe (sauf groupes manuels) de votre établissement pour remettre la configuration par défaut.",
"management.structure.informations.communications.warning.continue": "Continuer",
"management.structure.informations.communications.confirm.content": "Êtes-vous sur de vouloir réinitialiser les règles de communications",
"management.structure.informations.communications.notify.success.content" : "Les règles de communications ont bien été réinitialisées",
"management.structure.informations.communications.notify.success.title": "Réinitialisation",
"management.structure.informations.communications.notify.error.content" : "Les règles de communications n'ont pas pu être réinitialisées",
"management.structure.informations.communications.notify.error.title": "Erreur de réinitialisation",
"management.structure.informations.detach.parent.error.content": "Cette structure n'a pas pu être détachée de son parent",
"management.structure.informations.detach.parent.error.title": "Erreur lors de la suppression",
"management.structure.informations.detach.parent.success.content": "Cette structure a bien été détachée de son parent",
Expand Down
8 changes: 4 additions & 4 deletions admin/src/main/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
"font-awesome": "4.7.0",
"jquery": "^3.4.1",
"ngx-infinite-scroll": "14.0.1",
"ngx-ode-core": "dev",
"ngx-ode-sijil": "dev",
"ngx-ode-ui": "dev",
"ngx-ode-core": "develop-b2school",
"ngx-ode-sijil": "develop-b2school",
"ngx-ode-ui": "develop-b2school",
"ngx-trumbowyg": "^6.0.7",
"noty": "2.4.1",
"reflect-metadata": "0.1.10",
Expand Down Expand Up @@ -72,4 +72,4 @@
"typescript": "~4.6.4",
"webpack": "^5.70.0"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ export class CommunicationRulesService {
.filter(group => !!group)
.filter(group => group.id === groupId);
}

public resetCommunication(structureId: string): Observable<any> {
return this.http.post(`/communication/rules/${structureId}/reset`, null);
}
}

export interface BidirectionalCommunicationRules {
Expand Down
4 changes: 3 additions & 1 deletion admin/src/main/ts/src/app/management/management.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { StructureUserPositionsComponent } from './structure-user-positions/structure-user-positions.component';
import { SharedModule } from '../_shared/shared.module';
import { ConfigResolver } from '../core/resolvers/config.resolver';
import {CommunicationRulesService} from "../communication/communication-rules.service";

@NgModule({
imports: [
Expand Down Expand Up @@ -117,7 +118,8 @@ import { ConfigResolver } from '../core/resolvers/config.resolver';
SubjectsService,
CalendarService,
ImportEDTReportsService,
SubjectsGuardService
SubjectsGuardService,
CommunicationRulesService
]
})
export class ManagementModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,38 @@ <h3>
</p>
</ode-lightbox-confirm>
</div>


<div class="container has-shadow settings" *ngIf="isADMC == true">
<h2>
<s5l>management.structure.informations.communications.title</s5l>
</h2>
<s5l>management.structure.informations.communications.description</s5l>
<form #settingsForm="ngForm" (ngSubmit)="showResetCommunicationWarningLightBox = true;">
<div class="action-communication">
<button class="is-danger">
<s5l>management.structure.informations.communications.reset.action</s5l>
</button>
</div>
</form>
<ode-lightbox-confirm
[show]="showResetCommunicationWarningLightBox"
lightboxTitle="management.structure.informations.communications.warning.title"
(onCancel)="closeLightbox()"
(onConfirm)="openConfirmResetConfirmation()"
confirmText="management.structure.informations.communications.warning.continue">
<p>
<s5l>management.structure.informations.communications.warning.content</s5l>
</p>
</ode-lightbox-confirm>
<ode-lightbox-confirm
[show]="showResetCommunicationConfirmLightBox"
lightboxTitle="management.structure.informations.communications.warning.title"
(onCancel)="closeLightbox()"
(onConfirm)="resetCommunicationRules()">
<p>
<s5l>management.structure.informations.communications.confirm.content</s5l>
</p>
</ode-lightbox-confirm>
</div>

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
justify-content: flex-end;
}

.action-communication {
display: flex;
margin-top: 1em;
}

input[type="checkbox"],
button.cancel,
ode-message-sticker,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { BundlesService } from 'ngx-ode-sijil';
import { Context } from 'src/app/core/store/mappings/context';
import { Config } from 'src/app/core/resolvers/Config';
import { HttpClient } from '@angular/common/http';
import {CommunicationRulesService} from "../../communication/communication-rules.service";

class UserMetric {
active: number = 0;
Expand Down Expand Up @@ -68,6 +69,8 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
public showMfaWarningLightbox = false;
public isADMC: boolean = false;
public showSettingsLightbox = false;
public showResetCommunicationWarningLightBox = false;
public showResetCommunicationConfirmLightBox = false;

private config: Config;

Expand All @@ -78,7 +81,8 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
private infoService: StructureInformationsService,
private notify: NotifyService,
private bundles: BundlesService,
private http: HttpClient) {
private http: HttpClient,
private communicationService: CommunicationRulesService) {
super(injector);
}

Expand Down Expand Up @@ -267,6 +271,8 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
closeLightbox(): void
{
this.showSettingsLightbox = false;
this.showResetCommunicationConfirmLightBox = false;
this.showResetCommunicationWarningLightBox = false;
this.changeDetector.markForCheck();
}

Expand All @@ -277,4 +283,18 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn

return this.config['allow-adml-structure-name-change'];
}
openConfirmResetConfirmation(): void {
this.closeLightbox();
this.showResetCommunicationConfirmLightBox = true;
}

resetCommunicationRules(): void {
this.closeLightbox();
this.communicationService.resetCommunication(this.structure._id).subscribe(
{
next: (data) => this.notify.success("management.structure.informations.communications.notify.success.content", "management.structure.informations.communications.notify.success.title"),
error: (error) => this.notify.notify("management.structure.informations.communications.notify.error.content", "management.structure.informations.communications.notify.error.title", error, "error")
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
import static fr.wseduc.webutils.Utils.defaultValidationParamsNull;
import static org.entcore.common.neo4j.Neo4jResult.validEmptyHandler;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collector;

import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.neo4j.Neo4jResult;
Expand Down Expand Up @@ -235,84 +234,143 @@ public void massAuthorize(String widgetId, String structureId, List<String> prof
if(structureId == null || structureId .trim().isEmpty() ||
widgetId == null || widgetId.trim().isEmpty() || profiles == null || profiles.isEmpty()){
handler.handle(new Either.Left<String, JsonObject>("invalid.parameters"));
return;
}

String query =
final boolean includeAdml = profiles.contains("AdminLocal");
final List<String> filteredProfiles = includeAdml ? new ArrayList<>(profiles) : profiles;
if (includeAdml) filteredProfiles.remove("AdminLocal");

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId);

String queryAdml =
"MATCH (w:Widget {id: {widgetId}}), " +
"(parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(fg:FunctionGroup) " +
"WHERE fg.filter = 'AdminLocal' AND NOT(fg-[:AUTHORIZED]->w) " +
"CREATE UNIQUE fg-[:AUTHORIZED]->w";

String queryProfile =
"MATCH (w:Widget {id: {widgetId}}), " +
"(parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(g:ProfileGroup)-[:HAS_PROFILE]->(p:Profile) " +
"WHERE p.name IN {profiles} AND NOT(g-[:AUTHORIZED]->w) " +
"CREATE UNIQUE g-[:AUTHORIZED]->w";

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId)
.put("profiles", new JsonArray(profiles));

neo.execute(query, params, validEmptyHandler(handler));
executeMassDispatch(includeAdml, filteredProfiles, params, queryAdml, queryProfile, handler);
}

@Override
public void massUnauthorize(String widgetId, String structureId, List<String> profiles, final Handler<Either<String, JsonObject>> handler){
if(structureId == null || structureId .trim().isEmpty() ||
widgetId == null || widgetId.trim().isEmpty() || profiles == null || profiles.isEmpty()){
handler.handle(new Either.Left<String, JsonObject>("invalid.parameters"));
return;
}

String query =
final boolean includeAdml = profiles.contains("AdminLocal");
final List<String> filteredProfiles = includeAdml ? new ArrayList<>(profiles) : profiles;
if (includeAdml) filteredProfiles.remove("AdminLocal");

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId);

String queryAdml =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(fg:FunctionGroup), " +
"fg-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE fg.filter = 'AdminLocal' " +
"DELETE rel";

String queryProfile =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(g:ProfileGroup)-[:HAS_PROFILE]->(p:Profile), " +
"g-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE p.name IN {profiles} " +
"DELETE rel";

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId)
.put("profiles", new JsonArray(profiles));

neo.execute(query, params, validEmptyHandler(handler));
executeMassDispatch(includeAdml, filteredProfiles, params, queryAdml, queryProfile, handler);
}

@Override
public void massSetMandatory(String widgetId, String structureId, List<String> profiles, final Handler<Either<String, JsonObject>> handler){
if(structureId == null || structureId .trim().isEmpty() ||
widgetId == null || widgetId.trim().isEmpty() || profiles == null || profiles.isEmpty()){
handler.handle(new Either.Left<String, JsonObject>("invalid.parameters"));
return;
}

String query =
final boolean includeAdml = profiles.contains("AdminLocal");
final List<String> filteredProfiles = includeAdml ? new ArrayList<>(profiles) : profiles;
if (includeAdml) filteredProfiles.remove("AdminLocal");

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId);

String queryAdml =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(fg:FunctionGroup), " +
"fg-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE fg.filter = 'AdminLocal' " +
"SET rel.mandatory = true";

String queryProfile =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(g:ProfileGroup)-[:HAS_PROFILE]->(p:Profile), " +
"g-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE p.name IN {profiles} " +
"SET rel.mandatory = true";

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId)
.put("profiles", new JsonArray(profiles));

neo.execute(query, params, validEmptyHandler(handler));
executeMassDispatch(includeAdml, filteredProfiles, params, queryAdml, queryProfile, handler);
}

@Override
public void massRemoveMandatory(String widgetId, String structureId, List<String> profiles, final Handler<Either<String, JsonObject>> handler){
if(structureId == null || structureId .trim().isEmpty() ||
widgetId == null || widgetId.trim().isEmpty() || profiles == null || profiles.isEmpty()){
handler.handle(new Either.Left<String, JsonObject>("invalid.parameters"));
return;
}

String query =
final boolean includeAdml = profiles.contains("AdminLocal");
final List<String> filteredProfiles = includeAdml ? new ArrayList<>(profiles) : profiles;
if (includeAdml) filteredProfiles.remove("AdminLocal");

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId);

String queryAdml =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(fg:FunctionGroup), " +
"fg-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE fg.filter = 'AdminLocal' " +
"AND COALESCE(w.locked ,false) = false " +
"REMOVE rel.mandatory";

String queryProfile =
"MATCH (parentStructure:Structure {id: {structureId}})<-[:HAS_ATTACHMENT*0..]-(s:Structure)<-[:DEPENDS]-(g:ProfileGroup)-[:HAS_PROFILE]->(p:Profile), " +
"g-[rel:AUTHORIZED]->(w:Widget {id: {widgetId}}) " +
"WHERE p.name IN {profiles} " +
"AND COALESCE(w.locked ,false) = false " +
"REMOVE rel.mandatory";

JsonObject params = new JsonObject()
.put("widgetId", widgetId)
.put("structureId", structureId)
.put("profiles", new JsonArray(profiles));
executeMassDispatch(includeAdml, filteredProfiles, params, queryAdml, queryProfile, handler);
}

neo.execute(query, params, validEmptyHandler(handler));
/** Dispatch mass operation: ADML only, profiles only, or both sequentially. */
private void executeMassDispatch(boolean includeAdml, List<String> filteredProfiles,
JsonObject params, String queryAdml, String queryProfile,
Handler<Either<String, JsonObject>> handler) {
if (includeAdml && !filteredProfiles.isEmpty()) {
params.put("profiles", new JsonArray(filteredProfiles));
neo.execute(queryAdml, params, validEmptyHandler(r -> {
if (r.isLeft()) handler.handle(r);
else neo.execute(queryProfile, params, validEmptyHandler(handler));
}));
} else if (includeAdml) {
neo.execute(queryAdml, params, validEmptyHandler(handler));
} else {
params.put("profiles", new JsonArray(filteredProfiles));
neo.execute(queryProfile, params, validEmptyHandler(handler));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void deleteReaction(final HttpServerRequest request) {
final String resourceId = request.getParam("resourceId");
verify(module, resourceType, Collections.singleton(resourceId), request)
.onSuccess(user -> reactionService.deleteReaction(module, resourceType, resourceId, user)
.onSuccess(e -> Renders.ok(request))
.onSuccess(e -> Renders.render(request, e))
.onFailure(th -> {
Renders.log.error("Error while deleting reaction for user and resource " + module + "@" + resourceType + "@" + resourceId, th);
Renders.renderError(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ public interface ReactionService {

Future<ReactionDetailsResponse> getReactionDetails(String module, String resourceType, String resourceId, int page, int size);

Future<Void> upsertReaction(String module, String resourceType, String resourceId, UserInfos userInfos, String reactionType);
Future<ReactionsSummaryResponse> upsertReaction(String module, String resourceType, String resourceId, UserInfos userInfos, String reactionType);

Future<Void> deleteReaction(String module, String resourceType, String resourceId, UserInfos user);
Future<ReactionsSummaryResponse> deleteReaction(String module, String resourceType, String resourceId, UserInfos user);

Future<Void> deleteAllReactionsOfUsers(Set<String> userIds);

Expand Down
Loading