diff --git a/admin/src/main/resources/i18n/fr.json b/admin/src/main/resources/i18n/fr.json
index fedae3bb3f..58aad10bad 100644
--- a/admin/src/main/resources/i18n/fr.json
+++ b/admin/src/main/resources/i18n/fr.json
@@ -660,6 +660,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",
diff --git a/admin/src/main/ts/package.json b/admin/src/main/ts/package.json
index 2bea0ba9e3..f8b2d68bd9 100644
--- a/admin/src/main/ts/package.json
+++ b/admin/src/main/ts/package.json
@@ -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",
@@ -72,4 +72,4 @@
"typescript": "~4.6.4",
"webpack": "^5.70.0"
}
-}
\ No newline at end of file
+}
diff --git a/admin/src/main/ts/src/app/communication/communication-rules.service.ts b/admin/src/main/ts/src/app/communication/communication-rules.service.ts
index 42b605830b..0bf9994b1e 100644
--- a/admin/src/main/ts/src/app/communication/communication-rules.service.ts
+++ b/admin/src/main/ts/src/app/communication/communication-rules.service.ts
@@ -185,6 +185,10 @@ export class CommunicationRulesService {
.filter(group => !!group)
.filter(group => group.id === groupId);
}
+
+ public resetCommunication(structureId: string): Observable {
+ return this.http.post(`/communication/rules/${structureId}/reset`, null);
+ }
}
export interface BidirectionalCommunicationRules {
diff --git a/admin/src/main/ts/src/app/management/management.module.ts b/admin/src/main/ts/src/app/management/management.module.ts
index c0ed69e521..d24694446a 100644
--- a/admin/src/main/ts/src/app/management/management.module.ts
+++ b/admin/src/main/ts/src/app/management/management.module.ts
@@ -45,6 +45,7 @@ import { StructureUserPositionComponent } from './structure-user-positions/struc
import { MatDialogModule } from '@angular/material/dialog';
import { StructureUserPositionsComponent } from './structure-user-positions/structure-user-positions.component';
import { SharedModule } from '../_shared/shared.module';
+import {CommunicationRulesService} from "../communication/communication-rules.service";
@NgModule({
imports: [
@@ -115,7 +116,8 @@ import { SharedModule } from '../_shared/shared.module';
SubjectsService,
CalendarService,
ImportEDTReportsService,
- SubjectsGuardService
+ SubjectsGuardService,
+ CommunicationRulesService
]
})
export class ManagementModule {}
diff --git a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.html b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.html
index b95807944d..49d929be9c 100644
--- a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.html
+++ b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.html
@@ -318,3 +318,38 @@
+
+
+
+
+ management.structure.informations.communications.title
+
+
management.structure.informations.communications.description
+
+
+
+ management.structure.informations.communications.warning.content
+
+
+
+
+ management.structure.informations.communications.confirm.content
+
+
+
+
diff --git a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.scss b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.scss
index 23e20b6b15..e418690e64 100644
--- a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.scss
+++ b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.scss
@@ -3,6 +3,11 @@
justify-content: flex-end;
}
+.action-communication {
+ display: flex;
+ margin-top: 1em;
+}
+
input[type="checkbox"],
button.cancel,
ode-message-sticker,
diff --git a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.ts b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.ts
index ed28acfbac..460252a93c 100644
--- a/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.ts
+++ b/admin/src/main/ts/src/app/management/structure-informations/structure-informations.component.ts
@@ -9,6 +9,7 @@ import { Session } from 'src/app/core/store/mappings/session';
import { SessionModel } from 'src/app/core/store/models/session.model';
import { BundlesService } from 'ngx-ode-sijil';
import { Context } from 'src/app/core/store/mappings/context';
+import {CommunicationRulesService} from "../../communication/communication-rules.service";
class UserMetric {
active: number = 0;
@@ -66,6 +67,8 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
public showMfaWarningLightbox = false;
public isADMC: boolean = false;
public showSettingsLightbox = false;
+ public showResetCommunicationWarningLightBox = false;
+ public showResetCommunicationConfirmLightBox = false;
public metrics: StructureMetrics = new StructureMetrics();
public settings: DuplicationSettings = new DuplicationSettings();
@@ -73,8 +76,8 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
constructor(injector: Injector,
private infoService: StructureInformationsService,
private notify: NotifyService,
- private bundles: BundlesService)
- {
+ private bundles: BundlesService,
+ private communicationService: CommunicationRulesService) {
super(injector);
}
@@ -259,6 +262,23 @@ export class StructureInformationsComponent extends OdeComponent implements OnIn
closeLightbox(): void
{
this.showSettingsLightbox = false;
+ this.showResetCommunicationConfirmLightBox = false;
+ this.showResetCommunicationWarningLightBox = false;
this.changeDetector.markForCheck();
}
+
+ 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")
+ });
+ }
+
}
diff --git a/archive/src/main/java/org/entcore/archive/controllers/DuplicationController.java b/archive/src/main/java/org/entcore/archive/controllers/DuplicationController.java
index 353b015350..0f3a2a5999 100644
--- a/archive/src/main/java/org/entcore/archive/controllers/DuplicationController.java
+++ b/archive/src/main/java/org/entcore/archive/controllers/DuplicationController.java
@@ -81,7 +81,7 @@ public void handle(Buffer buff)
}
catch(Exception e)
{
- log.error(e, e.getMessage());
+ log.error(e.getMessage(), e);
badRequest(request);
return;
}
diff --git a/auth/src/main/java/org/entcore/auth/controllers/AuthController.java b/auth/src/main/java/org/entcore/auth/controllers/AuthController.java
index 21913ab9a6..04050f7d88 100644
--- a/auth/src/main/java/org/entcore/auth/controllers/AuthController.java
+++ b/auth/src/main/java/org/entcore/auth/controllers/AuthController.java
@@ -638,14 +638,14 @@ public void context(final HttpServerRequest request) {
pwdResetFormatByLang.put(lang, i18n.translate("password.rules.reset", Renders.getHost(request), lang));
} catch (Exception e) {
pwdResetFormatByLang.put(lang, "");
- log.error("error when translating password.rules.reset in {0} : {1}", lang, e);
+ log.error(String.format("error when translating password.rules.reset in %s : ", lang), e);
}
try {
pwdActivationFormatByLang.put(lang, i18n.translate("password.rules.activation", Renders.getHost(request), lang));
} catch (Exception e) {
pwdActivationFormatByLang.put(lang, "");
- log.error("error when translating password.rules.activation in {0} : {1}", lang, e);
+ log.error(String.format("error when translating password.rules.activation in %s : ", lang), e);
}
}
});
diff --git a/common/src/main/java/org/entcore/common/neo4j/Neo.java b/common/src/main/java/org/entcore/common/neo4j/Neo.java
index 47b9986bbf..e03c4b7d22 100644
--- a/common/src/main/java/org/entcore/common/neo4j/Neo.java
+++ b/common/src/main/java/org/entcore/common/neo4j/Neo.java
@@ -35,7 +35,7 @@ public class Neo {
private Neo4j neo4j;
- public Neo (Vertx vertx, EventBus eb, Logger log) {
+ public Neo (Vertx vertx, EventBus eb, Object log) {
neo4j = Neo4j.getInstance();
}
diff --git a/common/src/main/java/org/entcore/common/neo4j/StatementsBuilder.java b/common/src/main/java/org/entcore/common/neo4j/StatementsBuilder.java
index 69c7d97850..f92b856108 100644
--- a/common/src/main/java/org/entcore/common/neo4j/StatementsBuilder.java
+++ b/common/src/main/java/org/entcore/common/neo4j/StatementsBuilder.java
@@ -43,6 +43,13 @@ public StatementsBuilder add(String query, JsonObject params) {
}
return this;
}
+ public StatementsBuilder add(StatementsBuilder st) {
+ st.build()
+ .stream()
+ .map(JsonObject.class::cast)
+ .forEach(job -> this.add(job.getString("statement"), job.getJsonObject("parameters")));
+ return this;
+ }
public StatementsBuilder add(String query, Map params) {
return add(query, new JsonObject(params));
diff --git a/common/src/main/java/org/entcore/common/service/impl/MongoDbRepositoryEvents.java b/common/src/main/java/org/entcore/common/service/impl/MongoDbRepositoryEvents.java
index 19816a47d7..bd282b1783 100644
--- a/common/src/main/java/org/entcore/common/service/impl/MongoDbRepositoryEvents.java
+++ b/common/src/main/java/org/entcore/common/service/impl/MongoDbRepositoryEvents.java
@@ -196,8 +196,7 @@ else if (users.getJsonObject(i) != null && users.getJsonObject(i).getString("id"
" : " + eventOwner.body().getString("message"));
}
// find updated resources
- final Bson findByKey = Filters.eq("_deleteUsersKey", timestamp);
- final JsonObject query = MongoQueryBuilder.build(findByKey);
+ final JsonObject query = new JsonObject().put("_deleteUsersKey", timestamp);
mongo.find(collection, query, eventFind -> {
final JsonArray results = eventFind.body().getJsonArray("results");
final List list = new ArrayList<>();
diff --git a/communication/src/main/java/org/entcore/communication/controllers/CommunicationController.java b/communication/src/main/java/org/entcore/communication/controllers/CommunicationController.java
index fb8cb4f66c..cfb9ff1c19 100644
--- a/communication/src/main/java/org/entcore/communication/controllers/CommunicationController.java
+++ b/communication/src/main/java/org/entcore/communication/controllers/CommunicationController.java
@@ -40,6 +40,7 @@
import org.entcore.common.communication.CommunicationUtils;
import org.entcore.common.http.filter.AdminFilter;
import org.entcore.common.http.filter.ResourceFilter;
+import org.entcore.common.http.filter.SuperAdminFilter;
import org.entcore.common.user.UserUtils;
import org.entcore.common.validation.StringValidation;
import org.entcore.communication.filters.CommunicationDiscoverVisibleFilter;
@@ -403,6 +404,15 @@ public void handle(JsonObject body) {
});
}
+ @Post("/rules/:structureId/reset")
+ @SecuredAction(value = "", type = ActionType.RESOURCE)
+ @ResourceFilter(SuperAdminFilter.class)
+ @MfaProtected()
+ public void resetRules(final HttpServerRequest request) {
+ String structureId = request.params().get("structureId");
+ communicationService.resetRules(structureId, defaultResponseHandler(request));
+ }
+
/**
* Send the default communication rules contained inside the mod.json file.
* @param request Incoming request.
diff --git a/communication/src/main/java/org/entcore/communication/services/CommunicationService.java b/communication/src/main/java/org/entcore/communication/services/CommunicationService.java
index 00be785fa2..f72395bc64 100644
--- a/communication/src/main/java/org/entcore/communication/services/CommunicationService.java
+++ b/communication/src/main/java/org/entcore/communication/services/CommunicationService.java
@@ -41,7 +41,15 @@ public interface CommunicationService {
List EXPECTED_TYPES = Arrays.asList(
"User", "Group", "ManualGroup", "ProfileGroup", "FunctionalGroup", "FunctionGroup", "HTGroup", "CommunityGroup", "DirectionGroup");
- //enum VisibleType { USERS, GROUPS, BOTH }
+ /**
+ * Reset all communication rules on a structure and apply the default one configured
+ * in console or configuration
+ * @param structureId The target structure to reset
+ * @param eitherHandler handler for the response to the client
+ */
+ void resetRules(String structureId, Handler> eitherHandler);
+
+ //enum VisibleType { USERS, GROUPS, BOTH }
enum Direction {
INCOMING (0x01),
OUTGOING (0x10),
diff --git a/communication/src/main/java/org/entcore/communication/services/impl/DefaultCommunicationService.java b/communication/src/main/java/org/entcore/communication/services/impl/DefaultCommunicationService.java
index 287edea371..594d99b3c4 100644
--- a/communication/src/main/java/org/entcore/communication/services/impl/DefaultCommunicationService.java
+++ b/communication/src/main/java/org/entcore/communication/services/impl/DefaultCommunicationService.java
@@ -34,7 +34,9 @@
import org.entcore.common.conversation.LegacySearchVisibleRequest;
import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.neo4j.StatementsBuilder;
+import org.entcore.common.neo4j.TransactionHelper;
import org.entcore.common.notification.TimelineHelper;
+import org.entcore.common.schema.Source;
import org.entcore.common.user.DefaultFunctions;
import org.entcore.common.user.UserInfos;
import org.entcore.common.user.UserUtils;
@@ -61,12 +63,61 @@ public class DefaultCommunicationService implements CommunicationService {
final JsonArray discoverVisibleExpectedProfile = new JsonArray();
private final String visiblesSearchType;
private final EventBus eventBus;
+ private final JsonObject defaultRules;
public DefaultCommunicationService(final Vertx vertx, final TimelineHelper notifyTimeline, final JsonObject config) {
this.notifyTimeline = notifyTimeline;
this.discoverVisibleExpectedProfile.addAll(config.getJsonArray("discoverVisibleExpectedProfile", new JsonArray()));
this.visiblesSearchType = config.getString("visibles-search-type", "light");
this.eventBus = vertx.eventBus();
+ this.defaultRules = config.getJsonObject("initDefaultCommunicationRules");
+ }
+
+ @Override
+ public void resetRules(String structureId, Handler> eitherHandler) {
+ log.warn("Reset communication rules for structure {}",structureId);
+
+ List statements = Lists.newLinkedList();
+ StatementsBuilder builder = new StatementsBuilder();
+ JsonObject params = new JsonObject();
+
+ params.put("structureId", structureId);
+ //remove communiqueWith to apply default configuration
+ String query = " MATCH(s:Structure {id: {structureId}})<-[:BELONGS]-(c:Class)<-[:DEPENDS]-(pg:Group) " +
+ " WHERE NOT(pg:ManualGroup) and has(pg.communiqueWith) " +
+ " REMOVE pg.communiqueWith ";
+ builder.add(query, params);
+
+ query = " MATCH(s:Structure {id: {structureId}})<-[:DEPENDS]-(pg:Group) " +
+ " WHERE NOT(pg:ManualGroup) AND has(pg.communiqueWith) " +
+ " REMOVE pg.communiqueWith ";
+ builder.add(query, params);
+ //remove link between group
+ query = "MATCH(s:Structure {id: {structureId}})<-[:BELONGS]-(:Class)<-[:DEPENDS]-(g:Group)-[c:COMMUNIQUE]->(g2:Group) "
+ + " WHERE NOT(g:ManualGroup) " +
+ " DELETE c";
+ builder.add(query, params);
+ query = "MATCH(s:Structure {id: {structureId}})<-[:DEPENDS]-(g:Group)-[c:COMMUNIQUE]->(g2:Group) "
+ + " WHERE NOT(g:ManualGroup) " +
+ " DELETE c";
+ builder.add(query, params);
+ //remove incoming communication from an external group
+ query = "MATCH(s:Structure {id: {structureId}, users:'INCOMING'})<-[:DEPENDS]-(g:Group)<-[c:COMMUNIQUE]-(g2:Group) "
+ + " WHERE NOT(g:ManualGroup) " +
+ " DELETE c";
+
+ builder.add(query, params);
+
+ statements.add(builder);
+
+ JsonArray structureIds = new JsonArray(Lists.newArrayList(structureId));
+ //apply default communiqueWith
+ statements.addAll(getStatementsForDefaultRules(structureIds, defaultRules));
+ //apply communique relation
+ statements.add(getApplyDefaultRulesStatements(structureIds));
+
+ StatementsBuilder allStatements = statements.stream().reduce(new StatementsBuilder(), StatementsBuilder::add);
+ neo4j.executeTransaction(allStatements.build(), null, true, validUniqueResultHandler(eitherHandler) );
}
@Override
@@ -282,9 +333,7 @@ public void removeLinkBetweenRelativeAndStudent(String groupId, Direction direct
neo4j.execute(query, params, validUniqueResultHandler(handler));
}
- @Override
- public void initDefaultRules(JsonArray structureIds, JsonObject defaultRules, final Integer transactionId,
- final Boolean commit, final Handler> handler) {
+ private List getStatementsForDefaultRules(JsonArray structureIds, JsonObject defaultRules) {
final StatementsBuilder s1 = new StatementsBuilder();
final StatementsBuilder s2 = new StatementsBuilder();
final StatementsBuilder s3 = new StatementsBuilder();
@@ -301,19 +350,27 @@ public void initDefaultRules(JsonArray structureIds, JsonObject defaultRules, fi
"SET ag.users = 'BOTH' "
);
for (String attr : defaultRules.fieldNames()) {
- initDefaultRules(structureIds, attr, defaultRules.getJsonObject(attr), s1, s2);
+ getStatementsForDefaultRules(structureIds, attr, defaultRules.getJsonObject(attr), s1, s2);
}
- neo4j.executeTransaction(s1.build(), transactionId, false, new Handler>() {
+ return Lists.newArrayList(s1, s2, s3);
+ }
+
+ @Override
+ public void initDefaultRules(JsonArray structureIds, JsonObject defaultRules, final Integer transactionId,
+ final Boolean commit, final Handler> handler) {
+ List statementsBuilderList = getStatementsForDefaultRules(structureIds, defaultRules);
+
+ neo4j.executeTransaction(statementsBuilderList.get(0).build(), transactionId, false, new Handler>() {
@Override
public void handle(Message event) {
if ("ok".equals(event.body().getString("status"))) {
Integer transactionId = event.body().getInteger("transactionId");
- neo4j.executeTransaction(s2.build(), transactionId, false, new Handler>() {
+ neo4j.executeTransaction(statementsBuilderList.get(1).build(), transactionId, false, new Handler>() {
@Override
public void handle(Message event) {
if ("ok".equals(event.body().getString("status"))) {
Integer transactionId = event.body().getInteger("transactionId");
- neo4j.executeTransaction(s3.build(), transactionId, commit.booleanValue(),
+ neo4j.executeTransaction(statementsBuilderList.get(2).build(), transactionId, commit.booleanValue(),
new Handler>() {
@Override
public void handle(Message message) {
@@ -348,8 +405,8 @@ public void initDefaultRules(JsonArray structureIds, JsonObject defaultRules,
initDefaultRules(structureIds, defaultRules, null, true, handler);
}
- private void initDefaultRules(JsonArray structureIds, String attr, JsonObject defaultRules,
- final StatementsBuilder existingGroups, final StatementsBuilder newGroups) {
+ private void getStatementsForDefaultRules(JsonArray structureIds, String attr, JsonObject defaultRules,
+ final StatementsBuilder existingGroups, final StatementsBuilder newGroups) {
final String[] a = attr.split("\\-");
final String c = "Class".equals(a[0]) ? "*2" : "";
String relativeStudent = defaultRules.getString("Relative-Student"); // TODO check type in enum
@@ -452,9 +509,7 @@ private void initDefaultRules(JsonArray structureIds, String attr, JsonObject de
}
}
- @Override
- public void applyDefaultRules(JsonArray structureIds, final Integer transactionId, final Boolean commit,
- Handler> handler) {
+ private StatementsBuilder getApplyDefaultRulesStatements(JsonArray structureIds) {
StatementsBuilder s = new StatementsBuilder();
JsonObject params = new JsonObject().put("structures", structureIds);
String query =
@@ -501,6 +556,13 @@ public void applyDefaultRules(JsonArray structureIds, final Integer transactionI
"WITH DISTINCT v " +
"SET v:Visible ";
s.add(setVisible2, params);
+ return s;
+ }
+
+ @Override
+ public void applyDefaultRules(JsonArray structureIds, final Integer transactionId, final Boolean commit,
+ Handler> handler) {
+ StatementsBuilder s = getApplyDefaultRulesStatements(structureIds);
neo4j.executeTransaction(s.build(), transactionId, commit.booleanValue(), event -> {
if ("ok".equals(event.body().getString("status"))) {
handler.handle(new Either.Right<>(event.body()));
@@ -1745,13 +1807,14 @@ public Future searchVisibleContactsLight(UserInfos user, String searc
final Promise promise = Promise.promise();
String match = "MATCH (visibles) " +
- "OPTIONAL MATCH visibles-[:RELATED]->(parent: User) " +
- "WITH DISTINCT visibles, collect({id: parent.id, displayName: parent.displayName}) as relatives " +
-
- "OPTIONAL MATCH visibles<-[:RELATED]-(child: User) " +
- "WITH visibles, relatives, child " +
+ "OPTIONAL MATCH visibles-[:RELATED]->(parent1: User) " +
+ "OPTIONAL MATCH visibles<-[:RELATED]-(child: User)-[:RELATED]->(parent2: User) " +
+ "WITH DISTINCT visibles, COLLECT(DISTINCT {id: parent1.id, displayName: parent1.displayName}) " +
+ " + COLLECT(DISTINCT {id: parent2.id, displayName: parent2.displayName}) as relatives, child " +
"ORDER BY child.displayName " +
- "WITH visibles, relatives, collect({id: child.id, displayName: child.displayName}) AS children, collect(distinct child.displayName) AS sorted_children " +
+ "WITH visibles, [r IN relatives WHERE r.id IS NOT NULL] AS relatives, " +
+ "collect({id: child.id, displayName: child.displayName}) AS children," +
+ " collect(distinct child.displayName) AS sorted_children " +
"WITH visibles, relatives, children, reduce(s = '', name IN sorted_children | s + name) AS sorted_children_names ";
String preFilter = "";
@@ -2047,9 +2110,10 @@ private void applyMapObjectToContact(Promise promise, UserInfos user,
UserUtils.filterFewOrGetAllVisibles(eventBus, user.getUserId(), result.relativeAddedToTheList)
.onSuccess(relatives -> {
List idsToRemove = Lists.newLinkedList();
+ List relativesIds = relatives.stream().map( o -> ((JsonObject)o).getString("id")).collect(Collectors.toList());
for(int i = 0; i < result.relativeAddedToTheList.size(); i++) {
String id = result.relativeAddedToTheList.getString(i);
- if(!relatives.contains(id)) {
+ if(!relativesIds.contains(id)) {
idsToRemove.add(id);
}
}
diff --git a/directory/src/main/java/org/entcore/directory/services/impl/DefaultShareBookmarkService.java b/directory/src/main/java/org/entcore/directory/services/impl/DefaultShareBookmarkService.java
index 338a408ccd..d6bcd805d2 100644
--- a/directory/src/main/java/org/entcore/directory/services/impl/DefaultShareBookmarkService.java
+++ b/directory/src/main/java/org/entcore/directory/services/impl/DefaultShareBookmarkService.java
@@ -29,12 +29,8 @@
import org.entcore.common.neo4j.Neo4j;
import org.entcore.directory.services.ShareBookmarkService;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
-import java.util.UUID;
import static fr.wseduc.webutils.Utils.getOrElse;
@@ -142,10 +138,13 @@ public void get(String userId, String id, boolean onlyVisibles, Handler jo.getString("id"))
.collect(Collectors.toList());
// Keep only visible users, in the *membersMap*
+ //eliminate himself from the memebers to check
+ int initialCount = membersMapIds.size() - (membersMapIds.contains(userId) ? 1 : 0);
membersMapIds.retainAll(visibleIds);
// Update members array
members.clear();
membersMap.values().stream().forEach( member -> members.add(member) );
+ res.put("notVisibleCount", initialCount - members.size());
})
.onComplete( ar -> {
// Filtered or not, handle the results anyway.
diff --git a/feeder/src/main/java/org/entcore/feeder/utils/Validator.java b/feeder/src/main/java/org/entcore/feeder/utils/Validator.java
index 4abb08020e..1c7a56bbf0 100644
--- a/feeder/src/main/java/org/entcore/feeder/utils/Validator.java
+++ b/feeder/src/main/java/org/entcore/feeder/utils/Validator.java
@@ -20,10 +20,6 @@
package org.entcore.feeder.utils;
import fr.wseduc.webutils.I18n;
-import org.entcore.common.neo4j.Neo4j;
-import org.entcore.common.utils.MapFactory;
-import org.entcore.common.utils.StringUtils;
-import org.joda.time.DateTime;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.Message;
@@ -31,11 +27,16 @@
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
+import org.entcore.common.neo4j.Neo4j;
+import org.entcore.common.utils.MapFactory;
+import org.entcore.common.utils.StringUtils;
+import org.joda.time.DateTime;
import java.security.NoSuchAlgorithmException;
import java.text.Normalizer;
import java.util.*;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import static fr.wseduc.webutils.Utils.isNotEmpty;
@@ -395,6 +396,7 @@ private void displayNameGenerator(String attr, JsonObject object, String... in)
private String generateDisplayNamePermutations(String displayName)
{
List parts = StringUtils.split(removeAccents(displayName).toLowerCase().replaceAll("\\s+", " "), " ");
+ parts = parts.stream().map(Validator::sanitize).collect(Collectors.toList());
StringBuilder permutations = new StringBuilder();
if(parts.size() > 5) // Only compute the permutations for the first five terms, otherwise the string is too big
diff --git a/infra/src/main/java/org/entcore/infra/services/impl/MongoDbEventStore.java b/infra/src/main/java/org/entcore/infra/services/impl/MongoDbEventStore.java
index 12cf97fe80..142590f91d 100644
--- a/infra/src/main/java/org/entcore/infra/services/impl/MongoDbEventStore.java
+++ b/infra/src/main/java/org/entcore/infra/services/impl/MongoDbEventStore.java
@@ -141,9 +141,7 @@ public void storeCustomEvent(String baseEventType, JsonObject payload) {
}
@Override
- @Deprecated
public void listEvents(String eventStoreType, long startEpoch, long duration, boolean skipSynced, List eventTypes, boolean sorted, Handler> handler) {
- log.error("Calling deprecated method EventStoreService.listEvents, please use listEventsPartial instead");
final JsonObject query = new JsonObject().put("date", new JsonObject()
.put("$gte", startEpoch).put("$lt", (startEpoch + duration)));
if (skipSynced) {
diff --git a/package.json b/package.json
index 62a3e864da..1edd661386 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"angular-sanitize": "1.8.3",
"axios": "0.15.3",
"core-js": "^2.4.1",
- "entcore": "dev",
+ "entcore": "develop-b2school",
"entcore-generic-icons": "https://github.com/edificeio/generic-icons.git",
"entcore-toolkit": "^1.0.1",
"humane-js": "^3.2.2",
@@ -76,8 +76,8 @@
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"merge2": "^1.0.3",
- "ode-ngjs-front": "dev",
- "ode-ts-client": "dev",
+ "ode-ngjs-front": "develop-b2school",
+ "ode-ts-client": "develop-b2school",
"sass-loader": "^13.0.2",
"source-map-loader": "^0.1.5",
"ts-loader": "^3.1.1",
diff --git a/pom.xml b/pom.xml
index ea1201a5fa..726f5dc2be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,10 +57,10 @@
- 6.9-SNAPSHOT
+ 6.9-develop-b2school-SNAPSHOT
4.13.2
1.19.3
- 2.0-SNAPSHOT
+ 2.0-develop-b2school-SNAPSHOT
4.0-SNAPSHOT
3.2-SNAPSHOT
3.0-SNAPSHOT
@@ -70,7 +70,7 @@
20220608.1
2.15.2
0.2-SNAPSHOT
- 3.0-SNAPSHOT
+ 3.0-develop-b2school-SNAPSHOT
3.0.2
0.2.0
3.9
diff --git a/tests/src/test/js/it/scenarios/admin/resetCommunication.ts b/tests/src/test/js/it/scenarios/admin/resetCommunication.ts
new file mode 100644
index 0000000000..c0c3bc5d1a
--- /dev/null
+++ b/tests/src/test/js/it/scenarios/admin/resetCommunication.ts
@@ -0,0 +1,124 @@
+import {describe } from "https://jslib.k6.io/k6chaijs/4.3.4.0/index.js";
+
+import {
+ authenticateWeb,
+ initStructure,
+ Session,
+ Structure,
+ getProfileGroupsOfStructureByType,
+ getProfileGroupsRelatedToGroup,
+ resetRulesAndCheck,
+ removeCommunicationBetweenGroups,
+ getAdmlsOrMakThem,
+ addCommunicationBetweenGroups,
+ ProfileGroup
+} from "../../../node_modules/edifice-k6-commons/dist/index.js";
+import {fail} from "k6";
+
+const maxDuration = __ENV.MAX_DURATION || "5m";
+const schoolName = __ENV.DATA_SCHOOL_NAME || "Test it admin";
+const gracefulStop = parseInt(__ENV.GRACEFUL_STOP || "2s");
+
+export const options = {
+ setupTimeout: "1h",
+ thresholds: {
+ checks: ["rate == 1.00"],
+ },
+ scenarios: {
+ testResetCommunicationsRules: {
+ executor: "per-vu-iterations",
+ exec: "testResetCommunicationsRules",
+ vus: 1,
+ maxDuration: maxDuration,
+ gracefulStop,
+ },
+ },
+};
+
+type InitData = {
+ structure: Structure;
+}
+
+export function setup() {
+ let structure: Structure;
+ describe("[Reset-Communications-Rules] Initialize data", () => {
+ authenticateWeb(__ENV.ADMC_LOGIN, __ENV.ADMC_PASSWORD);
+ structure = initStructure(`${schoolName} - School`);
+ });
+ return { structure };
+}
+
+export function testResetCommunicationsRules(data: InitData){
+
+ describe('[Admin][Structure][Communication] Test that we can apply default communication rules ', () => {
+
+ authenticateWeb(__ENV.ADMC_LOGIN, __ENV.ADMC_PASSWORD);
+
+ const profileGroups: ProfileGroup[] = getProfileGroupsOfStructureByType("Teacher", data.structure);
+ const profileGroupTeacherStruct: ProfileGroup = profileGroups.find(p => p.name === (schoolName + ' - School-Teacher'));
+
+ if(profileGroupTeacherStruct === null) {
+ fail("[Admin][Structure][Communication] Unable to find the teacher group of the structure");
+ }
+ let incomingRelation: ProfileGroup[] = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "incoming");
+ let outgoingRelation: ProfileGroup[] = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "outgoing");
+
+ for(let i = 0; i < incomingRelation.length; i++) {
+ removeCommunicationBetweenGroups(incomingRelation[i].id, profileGroupTeacherStruct.id);
+ }
+ for(let i = 0; i < outgoingRelation.length; i++) {
+ removeCommunicationBetweenGroups(profileGroupTeacherStruct.id, outgoingRelation[i].id);
+ }
+
+ incomingRelation = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "incoming");
+ outgoingRelation = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "outgoing");
+
+ if (incomingRelation !== null && incomingRelation.length > 0) {
+ fail("[Admin][Structure][Communication] Incoming group communication should be empty");
+ }
+ if (outgoingRelation !== null && outgoingRelation.length > 0) {
+ fail("[Admin][Structure][Communication] Outgoing group communication should be empty");
+ }
+
+ //add custom communication rule
+ const profilGuestGroups: ProfileGroup[] = getProfileGroupsOfStructureByType("Guest", data.structure);
+
+ const targetGuestGroup = profilGuestGroups[0];
+
+ addCommunicationBetweenGroups(profileGroupTeacherStruct.id, targetGuestGroup.id);
+
+ outgoingRelation = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "outgoing");
+
+ if (outgoingRelation !== null && outgoingRelation.length != 1) {
+ fail("[Admin][Structure][Communication] Outgoing group communication should be equal to 1");
+ }
+
+ //reset all rules => the structure has no communication on the teacher group
+ resetRulesAndCheck(data.structure, 200);
+
+ const incomingUpdatedRelation: ProfileGroup[] = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "incoming");
+ const outgoingUpdatedRelation: ProfileGroup[] = getProfileGroupsRelatedToGroup(profileGroupTeacherStruct.id, "outgoing");
+
+ if (incomingUpdatedRelation === null || incomingUpdatedRelation.length === 0) {
+ fail("[Admin][Structure][Communication] Incoming group communication should not be empty");
+ }
+ if (outgoingUpdatedRelation === null || outgoingUpdatedRelation.length === 0) {
+ fail("[Admin][Structure][Communication] Outgoing group communication should not be empty");
+ }
+ //custom relation test
+ if (outgoingUpdatedRelation.find((p) => p.id === targetGuestGroup.id)) {
+ fail("[Admin][Structure][Communication] Outgoing group communication should not contain custom relation");
+ }
+ });
+
+ describe('[Admin][Structure][Communication] Test that adml cant reset communications rules ', () => {
+
+ authenticateWeb(__ENV.ADMC_LOGIN, __ENV.ADMC_PASSWORD);
+
+ const admlTeacher = getAdmlsOrMakThem(data.structure, 'Teacher', 1, [])[0]
+ authenticateWeb(admlTeacher.login)
+
+ //reset all rules => the structure has no communication on the teacher group
+ resetRulesAndCheck(data.structure, 401);
+ });
+}
diff --git a/tests/src/test/js/pnpm-lock.yaml b/tests/src/test/js/pnpm-lock.yaml
index bf36c21fd7..e2f6689a76 100644
--- a/tests/src/test/js/pnpm-lock.yaml
+++ b/tests/src/test/js/pnpm-lock.yaml
@@ -12,20 +12,20 @@ importers:
specifier: ^0.54.2
version: 0.54.2
edifice-k6-commons:
- specifier: latest
- version: 2.1.1
+ specifier: 2.1.6-develop-b2school-3
+ version: 2.1.6-develop-b2school-3
packages:
'@types/k6@0.54.2':
resolution: {integrity: sha512-B5LPxeQm97JnUTpoKNE1UX9jFp+JiJCAXgZOa2P7aChxVoPQXKfWMzK+739xHq3lPkKj1aV+HeOxkP56g/oWBg==}
- edifice-k6-commons@2.1.1:
- resolution: {integrity: sha512-r+eeO3hjTj4thRwDckG0SsCFkBIw0nuOVVJaZFOoDKHbidxjkgiRYA0Ygmu+yWCffBHpcHUiFMZtANaxYxlnJQ==}
- engines: {node: 18 || 20}
+ edifice-k6-commons@2.1.6-develop-b2school-3:
+ resolution: {integrity: sha512-H4qJMdtIdR025qWy45/T5IsN2SJU1N/GmLXiSqXfbt96rylJoeA7YysZLJAU6vW3kwJBHYjenn5rwCHWkwVMlA==}
+ engines: {node: '>=18'}
snapshots:
'@types/k6@0.54.2': {}
- edifice-k6-commons@2.1.1: {}
+ edifice-k6-commons@2.1.6-develop-b2school-3: {}
diff --git a/timeline/src/main/java/org/entcore/timeline/controllers/TimelineController.java b/timeline/src/main/java/org/entcore/timeline/controllers/TimelineController.java
index 9d16e95daa..ce3c082952 100644
--- a/timeline/src/main/java/org/entcore/timeline/controllers/TimelineController.java
+++ b/timeline/src/main/java/org/entcore/timeline/controllers/TimelineController.java
@@ -19,9 +19,6 @@
package org.entcore.timeline.controllers;
-import io.vertx.core.*;
-import io.vertx.core.logging.Logger;
-import io.vertx.core.logging.LoggerFactory;
import fr.wseduc.bus.BusAddress;
import fr.wseduc.rs.Delete;
import fr.wseduc.rs.Get;
@@ -36,37 +33,34 @@
import fr.wseduc.webutils.collections.TTLSet;
import fr.wseduc.webutils.http.BaseController;
import fr.wseduc.webutils.request.RequestUtils;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
+import io.vertx.core.eventbus.Message;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.logging.Logger;
+import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.shareddata.LocalMap;
import org.entcore.common.cache.CacheService;
import org.entcore.common.events.EventHelper;
import org.entcore.common.events.EventStore;
import org.entcore.common.events.EventStoreFactory;
-import org.entcore.common.http.filter.AdminFilter;
-import org.entcore.common.http.filter.AdmlOfStructures;
-import org.entcore.common.http.filter.ResourceFilter;
-import org.entcore.common.http.filter.SuperAdminFilter;
-import org.entcore.common.http.filter.Trace;
+import org.entcore.common.http.filter.*;
import org.entcore.common.http.request.JsonHttpServerRequest;
import org.entcore.common.mute.MuteHelper;
+import org.entcore.common.notification.NotificationUtils;
import org.entcore.common.notification.TimelineHelper;
import org.entcore.common.notification.TimelineNotificationsLoader;
-import org.entcore.common.notification.NotificationUtils;
import org.entcore.common.user.UserInfos;
import org.entcore.common.user.UserUtils;
import org.entcore.timeline.Timeline;
import org.entcore.timeline.controllers.helper.NotificationHelper;
-import org.entcore.timeline.events.CachedTimelineEventStore;
-import org.entcore.timeline.events.DefaultTimelineEventStore;
-import org.entcore.timeline.events.MobileTimelineEventStore;
-import org.entcore.timeline.events.SplitTimelineEventStore;
-import org.entcore.timeline.events.TimelineEventStore;
+import org.entcore.timeline.events.*;
import org.entcore.timeline.events.TimelineEventStore.AdminAction;
import org.entcore.timeline.services.TimelineConfigService;
import org.entcore.timeline.services.TimelineMailerService;
-import io.vertx.core.eventbus.Message;
-import io.vertx.core.http.HttpServerRequest;
-import io.vertx.core.json.JsonArray;
-import io.vertx.core.json.JsonObject;
import org.vertx.java.core.http.RouteMatcher;
import java.io.StringReader;