refactor plan + public plan models, fix isActive getter

This commit is contained in:
mchouliara 2024-08-13 14:02:48 +03:00
parent b774d0e43b
commit d41952da5b
10 changed files with 98 additions and 14302 deletions

2
.gitignore vendored
View File

@ -58,3 +58,5 @@ backend/node_modules/.yarn-integrity
dmp-frontend/.nx/
notification-service/notification-web/src/main/resources/config/app.env
backend/web/src/main/resources/config/app.env
backend/pom.xml
frontend/package-lock.json

View File

@ -1,312 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.opencdmp</groupId>
<artifactId>opencdmp-backend</artifactId>
<name>OpenCDMP Backend Parent</name>
<version>${revision}</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<modules>
<module>core</module>
<module>web</module>
</modules>
<properties>
<project.http.version>1.19.0</project.http.version>
<project.oauth.version>1.19.0</project.oauth.version>
<project.version>0.2.0</project.version>
<java.version>21</java.version>
<commons-codec.version>1.9</commons-codec.version>
<org.junit.version>4.11</org.junit.version>
<log4j.version>1.2.17</log4j.version>
<log4j2.version>2.15.0</log4j2.version>
<revision>1.0.0-SNAPSHOT</revision>
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>8.5.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.13</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.19</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>7.6.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-c3p0</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackeu.eudat.corecore/jackeu.eudat.corecore -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackeu.eudat.corecore/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
<!-- g/a spring -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackeu.eudat.corecore/jackson-databind -->
<!-- Various libs -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/fop -->
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
</dependency>-->
<!--<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.8.2</version>
</dependency>-->
<!-- &lt;!&ndash; The client &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.prometheus</groupId>-->
<!-- <artifactId>simpleclient</artifactId>-->
<!-- <version>0.11.0</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; Hotspot JVM metrics&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.prometheus</groupId>-->
<!-- <artifactId>simpleclient_hotspot</artifactId>-->
<!-- <version>0.11.0</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; Exposition HTTPServer&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.prometheus</groupId>-->
<!-- <artifactId>simpleclient_httpserver</artifactId>-->
<!-- <version>0.11.0</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; Pushgateway exposition&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.prometheus</groupId>-->
<!-- <artifactId>simpleclient_pushgateway</artifactId>-->
<!-- <version>0.11.0</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-registry-prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.11.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micormeter core dependecy -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<!--CITE DEPENDENCIES-->
<dependency>
<groupId>gr.cite</groupId>
<artifactId>data-tools</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>exceptions</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>logging</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>cache</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>elastic</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>keycloak-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>rabbitmq-core</artifactId>
<version>2.1.2</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>devel</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<packaging.type>jar</packaging.type>
</properties>
</profile>
<!-- <profile>-->
<!-- <id>intellij-properties-launcher</id>-->
<!-- <dependencies>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-loader</artifactId>-->
<!-- </dependency>-->
<!-- </dependencies>-->
<!-- </profile>-->
<profile>
<id>production</id>
<properties>
<packaging.type>jar</packaging.type>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<activation>
<property>
<name>profile</name>
<value>production</value>
</property>
</activation>
</profile>
<profile>
<id>cite-dev</id>
<repositories>
<repository>
<id>cite-maven</id>
<name>CITE Maven Repository</name>
<url>https://crepo.cite.gr/repository/cite-maven/</url>
</repository>
</repositories>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
</project>

13874
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { PublicReferenceType } from "../plan/plan";
import { ReferenceType } from "../reference-type/reference-type";
export interface DashboardStatistics {
planCount: number;
@ -8,5 +8,5 @@ export interface DashboardStatistics {
export interface DashboardReferenceTypeStatistics {
count: number;
referenceType: PublicReferenceType
referenceType: ReferenceType
}

View File

@ -1,12 +1,12 @@
import { BaseEntity } from "@common/base/base-entity.model";
import { Reference } from "../reference/reference";
import { Plan } from "./plan";
import { BaseReference, Reference } from "../reference/reference";
import { Plan, PublicPlan } from "./plan";
import { Guid } from "@common/types/guid";
export interface PlanReference extends BaseEntity {
plan?: Plan;
reference?: Reference;
data: PlanReferenceData;
plan: Plan;
reference: Reference;
data?: PlanReferenceData;
}
export interface PlanReferenceData {

View File

@ -15,29 +15,41 @@ import { IsActive } from '@app/core/common/enum/is-active.enum';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { EntityType } from '@app/core/common/enum/entity-type';
export interface Plan extends BasePlan {
label?: string;
export interface BasePlan extends BaseEntity {
label?: string;
version?: number;
versionStatus?: PlanVersionStatus;
properties?: PlanProperties;
groupId?: String;
description?: String;
finalizedAt?: Date;
publishedAt?: Date;
creator?: User;
finalizedAt?: Date;
accessType?: PlanAccessType;
planReferences?: PlanReference[];
entityDois?: EntityDoi[];
tenantId?: Guid;
status?: PlanStatus;
descriptions?: BaseDescription[];
}
export interface Plan extends BasePlan {
versionStatus?: PlanVersionStatus;
properties?: PlanProperties;
creator?: User;
blueprint?: PlanBlueprint;
language?: String;
publicAfter?: Date;
planReferences?: PlanReference[];
planUsers?: PlanUser[];
descriptions?: Description[];
planDescriptionTemplates?: PlanDescriptionTemplate[];
entityDois?: EntityDoi[];
otherPlanVersions?: Plan[];
authorizationFlags?: AppPermission[];
}
export interface PublicPlan extends BasePlan {
planUsers: PublicPlanUser[];
descriptions: PublicDescription[];
otherPlanVersions?: PublicPlan[];
}
export interface PlanProperties {
planBlueprintValues: PlanBlueprintValue[];
contacts: PlanContact[];
@ -56,14 +68,21 @@ export interface PlanContact {
email: string;
}
export interface BasePlanUser extends BaseEntity {
user: User;
role: PlanUserRole;
}
export interface PlanUser extends BaseEntity {
export interface PlanUser extends BasePlanUser {
plan: Plan;
user: User;
role: PlanUserRole;
sectionId: Guid;
}
export interface PublicPlanUser extends BasePlanUser{
plan: PublicPlan;
}
export interface PlanDescriptionTemplate extends BaseEntity {
plan?: Plan;
currentDescriptionTemplate?: DescriptionTemplate;
@ -161,66 +180,4 @@ export interface PlanUserInvitePersist {
//
// Public
//
export interface PublicPlan extends BasePlan {
label?: string;
version?: number;
description?: string;
finalizedAt?: Date;
publishedAt?: Date;
groupId?: String;
accessType: PlanAccessType;
planReferences: PublicPlanReference[];
planUsers: PublicPlanUser[];
descriptions: PublicDescription[];
entityDois: PublicEntityDoi[];
otherPlanVersions?: PublicPlan[];
}
export interface PublicPlanReference {
id: Guid;
plan: PublicPlan;
reference: PublicReference;
isActive?: IsActive;
}
export interface PublicReference {
id: Guid;
label: string;
type: PublicReferenceType;
description?: string;
reference?: string;
}
export interface PublicReferenceType {
id: Guid;
name: string;
code: string;
}
export interface PublicPlanUser {
id: Guid;
plan: PublicPlan;
user: PublicUser;
role: PlanUserRole;
}
export interface PublicUser {
id: Guid;
name: string;
}
export interface PublicEntityDoi {
id: Guid;
entityType: EntityType;
entityId: Guid;
repositoryId: string;
doi: string;
}
export interface BasePlan extends BaseEntity {
tenantId?: Guid;
status?: PlanStatus;
descriptions?: BaseDescription[];
}
//

View File

@ -15,6 +15,14 @@ export interface Reference extends BaseEntity {
sourceType: ReferenceSourceType;
}
export interface BaseReference extends BaseEntity{
label: string;
type: ReferenceType;
description?: string;
reference?: string;
}
export interface Definition {
fields: Field[];
}

View File

@ -58,7 +58,7 @@ import { RouterUtilsService } from '@app/core/services/router/router-utils.servi
})
export class PlanOverviewComponent extends BaseComponent implements OnInit {
plan: any | Plan | PublicPlan;
plan: Plan | PublicPlan;
selectedBlueprint: PlanBlueprint;
researchers: PlanReference[] = [];
isNew = true;
@ -214,14 +214,14 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
}
get isActive(): boolean {
return this.plan && this.plan.isActive === IsActive.Active;
return this.plan?.isActive != IsActive.Inactive;
}
get selectedPlanVersion(): number {
return this.plan?.version;
}
get otherPlanVersions(): Plan | PublicPlan{
get otherPlanVersions(): Plan[] | PublicPlan[]{
return this.plan?.otherPlanVersions;
}
@ -257,53 +257,68 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
}
canEditPlan(): boolean {
return (this.isDraftPlan()) && (this.plan.authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authentication.hasPermission(AppPermission.EditPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (this.isDraftPlan()) && (authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authentication.hasPermission(AppPermission.EditPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
canCreateNewVersion(): boolean {
return (this.plan.authorizationFlags?.some(x => x === AppPermission.CreateNewVersionPlan) || this.authentication.hasPermission(AppPermission.CreateNewVersionPlan)) && this.plan.versionStatus === PlanVersionStatus.Current && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
const versionStatus = !this.isPublicView ? (this.plan as Plan)?.versionStatus : null;
return (authorizationFlags?.some(x => x === AppPermission.CreateNewVersionPlan) || this.authentication.hasPermission(AppPermission.CreateNewVersionPlan)) && versionStatus === PlanVersionStatus.Current && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
canDeletePlan(): boolean {
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (
this.isActive &&
(this.plan.authorizationFlags?.some(x => x === AppPermission.DeletePlan) || this.authentication.hasPermission(AppPermission.DeletePlan))) &&
this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
(authorizationFlags?.some(x => x === AppPermission.DeletePlan) || this.authentication.hasPermission(AppPermission.DeletePlan)) &&
this.isPublicView == false && this.plan.belongsToCurrentTenant != false
)
}
canClonePlan(): boolean {
return (this.plan.authorizationFlags?.some(x => x === AppPermission.ClonePlan) || this.authentication.hasPermission(AppPermission.ClonePlan) || (this.authentication.hasPermission(AppPermission.PublicClonePlan) && this.isPublicView));
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (
authorizationFlags?.some(x => x === AppPermission.ClonePlan) ||
this.authentication.hasPermission(AppPermission.ClonePlan) ||
(this.authentication.hasPermission(AppPermission.PublicClonePlan) && this.isPublicView)
);
}
canFinalizePlan(): boolean {
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (
this.isActive &&
(this.plan.authorizationFlags?.some(x => x === AppPermission.FinalizePlan) || this.authentication.hasPermission(AppPermission.FinalizePlan))) &&
(authorizationFlags?.some(x => x === AppPermission.FinalizePlan) || this.authentication.hasPermission(AppPermission.FinalizePlan))) &&
this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
canExportPlan(): boolean {
return (this.plan.authorizationFlags?.some(x => x === AppPermission.ExportPlan) || this.authentication.hasPermission(AppPermission.ExportPlan));
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.ExportPlan) || this.authentication.hasPermission(AppPermission.ExportPlan));
}
canInvitePlanUsers(): boolean {
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (
this.isActive &&
(this.plan.authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers) || this.authentication.hasPermission(AppPermission.InvitePlanUsers))) &&
(authorizationFlags?.some(x => x === AppPermission.InvitePlanUsers) || this.authentication.hasPermission(AppPermission.InvitePlanUsers))) &&
this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
canAssignPlanUsers(): boolean {
return (this.plan.authorizationFlags?.some(x => x === AppPermission.AssignPlanUsers) || this.authentication.hasPermission(AppPermission.AssignPlanUsers)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.AssignPlanUsers) || this.authentication.hasPermission(AppPermission.AssignPlanUsers)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
canDepositPlan(): boolean {
return (this.plan.authorizationFlags?.some(x => x === AppPermission.DepositPlan) || this.authentication.hasPermission(AppPermission.DepositPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
const authorizationFlags = !this.isPublicView ? (this.plan as Plan).authorizationFlags : [];
return (authorizationFlags?.some(x => x === AppPermission.DepositPlan) || this.authentication.hasPermission(AppPermission.DepositPlan)) && this.isPublicView == false && this.plan.belongsToCurrentTenant != false;
}
editClicked() {
this.router.navigate([this.routerUtils.generateUrl(['/plans/edit', this.plan.id], '/')]);
this.router.navigate([this.routerUtils.generateUrl(['/plans/edit', this.plan.id.toString()], '/')]);
}
cloneClicked() {
@ -588,8 +603,8 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit {
getSectionNameById(sectionId: Guid): string {
if (sectionId == null) return '';
let sections: PlanBlueprintDefinitionSection[] = this.plan?.blueprint?.definition?.sections?.filter((section: PlanBlueprintDefinitionSection) => sectionId === section.id);
const blueprint = this.isPublicView ? null : (this.plan as Plan)?.blueprint;
let sections: PlanBlueprintDefinitionSection[] = blueprint?.definition?.sections?.filter((section: PlanBlueprintDefinitionSection) => sectionId === section.id);
return sections == null ? '' : sections[0].label;
}

View File

@ -332,7 +332,7 @@ export class PlanBlueprintValueEditorModel implements PlanBlueprintValuePersist
this.fieldValue = item.fieldValue;
this.dateValue = item.dateValue;
this.numberValue = item.numberValue;
const references = planReferences?.filter(x => x.data.blueprintFieldId == this.fieldId && x.isActive == IsActive.Active).map(x => {
const references = planReferences?.filter(x => x.data?.blueprintFieldId == this.fieldId && x.isActive == IsActive.Active).map(x => {
return {
data: x.data,
reference: {

View File

@ -9,24 +9,24 @@
"defaultCulture": "en",
"defaultBlueprintId": "86635178-36a6-484f-9057-a934e4eeecd5",
"keycloak": {
"enabled": true,
"address": null,
"realm": null,
"flow": "standard",
"clientId": null,
"silentCheckSsoRedirectUri": "http://localhost:4200/assets/silent-check-sso.html",
"scope": "openid profile email address phone dmp_web dmp_notification identity_provider",
"clientSecret": null,
"grantType": "code"
},
"enabled": true,
"address": "http://dev03.local.cite.gr:60201",
"realm": "dmp-development",
"flow": "standard",
"clientId": "dmp_webapp",
"silentCheckSsoRedirectUri": "http://localhost:4200/assets/silent-check-sso.html",
"scope": "openid profile email address phone dmp_web dmp_notification dmp_annotation identity_provider",
"clientSecret": null,
"grantType": "code"
},
"inAppNotificationsCountInterval": "30",
"notification_service": {
"enabled": true,
"address": "http://localhost:8086/api/"
"address": "http://dev04.local.cite.gr:55330/api/notification/"
},
"annotation_service": {
"enabled": true,
"address": "http://localhost:8087/api/",
"address": "http://dev04.local.cite.gr:55330/api/annotation/",
"statusIcons": [
{
"internalStatus": "0",
@ -70,7 +70,7 @@
"name": "Default",
"providerClass": "defaultIcon"
},
"authProviders": [
"authProviders": [
{
"name": "google",
"providerClass": "googleIcon",
@ -88,7 +88,7 @@
"grantId": "5b9c284f-f041-4995-96cc-fad7ad13289c",
"organizationId": "7eeffb98-58fb-4921-82ec-e27f32f8e738"
},
"deposit":{
"deposit": {
"recordUrlIdPlaceholder": "{doi_id}"
},
"sidebar": {
@ -99,14 +99,14 @@
"externalUrl": "/splash/resources/co-branding.html",
"accessLevel": "public"
},
{
"title": "SIDE-BAR.SUPPORT",
{
"title": "SIDE-BAR.SUPPORT",
"icon": "help",
"routerPath": "/contact-support",
"accessLevel": "authenticated"
},
{
"title": "SIDE-BAR.SUPPORT",
{
"title": "SIDE-BAR.SUPPORT",
"icon": "help",
"externalUrl": "/splash/contact.html",
"accessLevel": "unauthenticated"