diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ef5b25cbafdb9aa5c863508d5db6fe338fb2711..bf2e17ee336a7ae07c646de12e6f107484ae1eb3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -209,6 +209,8 @@ build_doc: changes: - "documentations/*.md" - "documentations/*.json" + - "documentations/*.html" + - "documentations/*.markdown" pages: stage: pages @@ -225,4 +227,6 @@ pages: - $CI_COMMIT_BRANCH == "master" changes: - "documentations/*.md" - - "documentations/*.json" \ No newline at end of file + - "documentations/*.json" + - "documentations/*.html" + - "documentations/*.markdown" \ No newline at end of file diff --git a/documentations/doc_addon.md b/documentations/doc_addon.md index beff2366abf6e1fb04d989e999de4c417abfacac..691e733507837121b763b9e762018e130535fc5f 100644 --- a/documentations/doc_addon.md +++ b/documentations/doc_addon.md @@ -1,5 +1,5 @@ ## Documentations - La [documentation](fichier_de_configuration.pdf) pour écrire le yaml. - Un [lexique des mots clef](lexique.pdf) du fichier de configuration. -- Le [format des services](services_model.html) +- Le [format des services](services_model.html) - Un [fichier de configuration colorisé](https://anaee-dev.pages.mia.inra.fr/implementations-si-ore/cook-book-yaml-creation/) (mots clefs techniques en rouge; mots clefs utilisateurs en violet). Vous pouvez aussi accéder au [fichier source](https://anaee-dev.pages.mia.inra.fr/implementations-si-ore/cook-book-yaml-creation/documentation.yaml) \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/model/Application.java b/src/main/java/fr/inra/oresing/model/Application.java index 7fccc56e32b7f29bf2a5776ba0709ce2751b032f..3d559fbe2b6b66a995bc344aaaed13896ecdd235 100644 --- a/src/main/java/fr/inra/oresing/model/Application.java +++ b/src/main/java/fr/inra/oresing/model/Application.java @@ -30,6 +30,7 @@ public class Application extends OreSiEntity { } public final Application filterFieldsAndHidden(List<ApplicationInformation> filters){ final Application returnApp = new Application(); + returnApp.setId(this.getId()); returnApp.setComment(this.getComment()); returnApp.setVersion(this.getVersion()); returnApp.setName(this.getName()); diff --git a/src/main/java/fr/inra/oresing/model/additionalfiles/AdditionalBinaryFile.java b/src/main/java/fr/inra/oresing/model/additionalfiles/AdditionalBinaryFile.java index 0a2762b3a1b4dedfc4035996c923bf633462f2b9..ab268ddeaf888d05a8d3a4a01aa87d0367833968 100644 --- a/src/main/java/fr/inra/oresing/model/additionalfiles/AdditionalBinaryFile.java +++ b/src/main/java/fr/inra/oresing/model/additionalfiles/AdditionalBinaryFile.java @@ -21,6 +21,7 @@ public class AdditionalBinaryFile extends OreSiEntity { public final static BinaryFileInfos EMPTY_INSTANCE(){ return BinaryFile.EMPTY_INSTANCE(); } + public final static String CHART_FILE_TYPE = "__charte__"; private UUID application; private String fileType; private String fileName; diff --git a/src/main/java/fr/inra/oresing/persistence/AdditionalFileSearchHelper.java b/src/main/java/fr/inra/oresing/persistence/AdditionalFileSearchHelper.java index 658152d78aa2e32ded29dd4c8bf652dbb5874944..0c3e59511541c39064473abe592da05bc3f152d0 100644 --- a/src/main/java/fr/inra/oresing/persistence/AdditionalFileSearchHelper.java +++ b/src/main/java/fr/inra/oresing/persistence/AdditionalFileSearchHelper.java @@ -182,8 +182,8 @@ public class AdditionalFileSearchHelper { } public void addAdditionalFilesToZip(List<AdditionalBinaryFile> additionalBinaryFiles, ZipOutputStream zipOutputStream, String folder) throws IOException { - if(folder==null){ - folder=""; + if (folder == null) { + folder = ""; } List<MemoryFile> memoryFiles = additionalBinaryFiles.stream() .map(this::getMemoriesFiles) @@ -191,7 +191,16 @@ public class AdditionalFileSearchHelper { .filter(Objects::nonNull) .collect(Collectors.toList()); for (MemoryFile memoryFile : memoryFiles) { - ZipEntry zipEntry = new ZipEntry(String.format("%s%s",folder, memoryFile.fileName)); + ZipEntry zipEntry; + if (memoryFile.fileName.startsWith(AdditionalBinaryFile.CHART_FILE_TYPE)) { + if(memoryFile.fileName.endsWith("_infos.txt")){ + continue; + } + String format = memoryFile.fileName.replaceAll("^.*(\\.[A-Za-z]{1,4})", "charte$1"); + zipEntry = new ZipEntry(format); + }else{ + zipEntry = new ZipEntry(String.format("%s%s", folder, memoryFile.fileName)); + } zipOutputStream.putNextEntry(zipEntry); zipOutputStream.write(memoryFile.contents); zipOutputStream.closeEntry(); diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java index ed0562aeb5a9fc287f18e49bf3c13ce4059ec252..715bfb3bad4b06051a7aa6767d9459bcfe28934a 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java @@ -5,6 +5,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.*; import fr.inra.oresing.checker.*; import fr.inra.oresing.model.*; +import fr.inra.oresing.model.additionalfiles.AdditionalBinaryFile; import fr.inra.oresing.model.additionalfiles.AdditionalFilesInfos; import fr.inra.oresing.rest.model.additionalfiles.CreateAdditionalFileRequest; import fr.inra.oresing.rest.model.application.ApplicationResult; @@ -131,10 +132,10 @@ public class OreSiResources { @GetMapping(value = "/applications/{nameOrId}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<ApplicationResult> getApplication(@PathVariable("nameOrId") String nameOrId, @RequestParam(required = false, defaultValue = "") String[] filter) { - Application application = service.getApplicationOrApplicationAccordingToRights(nameOrId); List<ApplicationInformation> filters = Arrays.stream(filter) .map(s -> ApplicationInformation.valueOf(s)) .collect(Collectors.toList()); + Application application = service.getApplicationOrApplicationAccordingToRights(nameOrId, filters); boolean withDatatypes = filters.contains(ApplicationInformation.ALL) || filters.contains(ApplicationInformation.DATATYPE); boolean withReferenceType = filters.contains(ApplicationInformation.ALL) || filters.contains(ApplicationInformation.REFERENCETYPE); boolean withConfiguration = filters.contains(ApplicationInformation.ALL) || filters.contains(ApplicationInformation.CONFIGURATION); @@ -260,7 +261,7 @@ public class OreSiResources { final Map<String, Map<AuthorizationsForUserResult.Roles, Boolean>> authorizationsDatatypesRights = withDatatypes ? service.getAuthorizationsDatatypesRights(nameOrId, dataTypes.keySet()) : new HashMap<>(); Configuration configuration = withConfiguration ? application.getConfiguration() : null; Boolean isAdministrator = service.isAdmnistrator(application); - ApplicationResult applicationResult = new ApplicationResult(application.getId().toString(), application.getName(), application.getConfiguration().getApplication().getName(), application.getComment(), application.getConfiguration().getInternationalization(), references, authorizationReferencesRights, referenceSynthesis, dataTypes, additionalFiles, authorizationsDatatypesRights, rightsRequest, configuration, isAdministrator); + ApplicationResult applicationResult = new ApplicationResult(application.getId().toString(), application.getName(), application.getConfiguration().getApplication().getName(), application.getComment(), application.getVersion(), application.getConfigFile(), application.getConfiguration().getInternationalization(), references, authorizationReferencesRights, referenceSynthesis, dataTypes, additionalFiles, authorizationsDatatypesRights, rightsRequest, configuration, isAdministrator); return ResponseEntity.ok(applicationResult); } @@ -495,8 +496,11 @@ public class OreSiResources { }; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); - headers.set("Content-Disposition", "attachment; filename=additionalFiles.zip"); - + if (AdditionalBinaryFile.CHART_FILE_TYPE.equals(additionalFilesInfos.getFiletype())) { + headers.set("Content-Disposition", "attachment; filename=charte.pdf"); + } else { + headers.set("Content-Disposition", "attachment; filename=additionalFiles.zip"); + } return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .contentLength(additionalFilesNamesZip.length) diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index 8b304b40f1a0019c9ec8db56d4d8cb3266123b48..ba42e9ade48ed3e247657a748ae67d7202b8d7d7 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -12,14 +12,14 @@ import fr.inra.oresing.groovy.Expression; import fr.inra.oresing.groovy.GroovyContextHelper; import fr.inra.oresing.groovy.StringGroovyExpression; import fr.inra.oresing.model.*; -import fr.inra.oresing.model.additionalfiles.*; -import fr.inra.oresing.rest.model.application.ApplicationResult; -import fr.inra.oresing.rest.model.application.ConfigurationParsingResult; +import fr.inra.oresing.model.additionalfiles.AdditionalBinaryFile; +import fr.inra.oresing.model.additionalfiles.AdditionalFilesInfos; import fr.inra.oresing.model.chart.OreSiSynthesis; -import fr.inra.oresing.model.rightsrequest.*; +import fr.inra.oresing.model.rightsrequest.RightsRequest; import fr.inra.oresing.persistence.*; import fr.inra.oresing.persistence.flyway.MigrateService; import fr.inra.oresing.persistence.roles.CurrentUserRoles; +import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; import fr.inra.oresing.persistence.roles.OreSiRole; import fr.inra.oresing.rest.exceptions.additionalfiles.BadAdditionalFileParamsSearchException; import fr.inra.oresing.rest.exceptions.application.NoSuchApplicationException; @@ -29,6 +29,8 @@ import fr.inra.oresing.rest.exceptions.configuration.BadApplicationConfiguration import fr.inra.oresing.rest.model.additionalfiles.AdditionalBinaryFileResult; import fr.inra.oresing.rest.model.additionalfiles.AdditionalFileParamsParsingResult; import fr.inra.oresing.rest.model.additionalfiles.CreateAdditionalFileRequest; +import fr.inra.oresing.rest.model.application.ApplicationResult; +import fr.inra.oresing.rest.model.application.ConfigurationParsingResult; import fr.inra.oresing.rest.model.authorization.*; import fr.inra.oresing.rest.model.data.DownloadDatasetQuery; import fr.inra.oresing.rest.model.rightsrequest.*; @@ -57,6 +59,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Nullable; import javax.sql.DataSource; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.time.Duration; @@ -71,6 +74,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipOutputStream; @Slf4j @Component @@ -248,7 +252,7 @@ public class OreSiService { } } - validateStoredData(new DownloadDatasetQuery(nameOrId, app,null, dataType, null, null, null, null,null,null)); + validateStoredData(new DownloadDatasetQuery(nameOrId, app, null, dataType, null, null, null, null, null, null)); } // on supprime l'ancien fichier vu que tout c'est bien passé @@ -651,7 +655,7 @@ public class OreSiService { IntegerChecker integerChecker = (IntegerChecker) lineChecker; final VariableComponentKey vc = (VariableComponentKey) integerChecker.getTarget(); final String s = datum.get(vc); - if (s!=null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { + if (s != null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { datum.put( vc, String.format("[%s]", s)); @@ -660,7 +664,7 @@ public class OreSiService { FloatChecker floatChecker = (FloatChecker) lineChecker; final VariableComponentKey vc = (VariableComponentKey) floatChecker.getTarget(); final String s = datum.get(vc); - if (s!=null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { + if (s != null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { datum.put( vc, String.format("[%s]", s)); @@ -669,7 +673,7 @@ public class OreSiService { StringChecker StringChecker = (StringChecker) lineChecker; final VariableComponentKey vc = (VariableComponentKey) StringChecker.getTarget(); final String s = datum.get(vc); - if (s!=null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { + if (s != null && s.contains(ReferenceImporterContext.ManyValuesStaticColumn.CSV_CELL_SEPARATOR)) { datum.put( vc, String.format( @@ -1294,17 +1298,30 @@ public class OreSiService { public Application getApplication(String nameOrId) { // TODO filtre tag hidden boucle sur les references et les datatypes authenticationService.setRoleForClient(); - // Application result = repo.application().findApplication(nameOrId); return repo.application().findApplication(nameOrId); } + public Application getApplicationOrApplicationAccordingToRights(String nameOrId, List<ApplicationInformation> filters) { + authenticationService.setRoleForClient(); + try { + Application result = repo.application().findApplication(nameOrId); + return result.filterFieldsAndHidden(filters); + } catch (NoSuchApplicationException e) { + authenticationService.setRoleAdmin(); + return repo.application().findApplication(nameOrId).applicationAccordingToRights(); + + } + } + public Application getApplicationOrApplicationAccordingToRights(String nameOrId) { authenticationService.setRoleForClient(); try { return repo.application().findApplication(nameOrId); } catch (NoSuchApplicationException e) { authenticationService.setRoleAdmin(); - return repo.application().findApplication(nameOrId).applicationAccordingToRights(); + Application application = repo.application().findApplication(nameOrId).applicationAccordingToRights(); + authenticationService.setRoleForClient(); + return application; } } @@ -1402,7 +1419,7 @@ public class OreSiService { } public GetAdditionalFilesResult findAdditionalFile(String nameOrId, AdditionalFilesInfos additionalFilesInfos) { - final Application application = getApplication(nameOrId); + final Application application = getApplicationOrApplicationAccordingToRights(nameOrId); Configuration.AdditionalFileDescription description = Optional.ofNullable(application.getConfiguration().getAdditionalFiles()) .map(map -> map.get(additionalFilesInfos.getFiletype())) .orElseGet(Configuration.AdditionalFileDescription::new); @@ -1666,7 +1683,7 @@ public class OreSiService { } public byte[] getAdditionalFilesNamesZip(String nameOrId, AdditionalFilesInfos additionalFilesInfos) throws IOException { - Application application = getApplication(nameOrId); + Application application = getApplicationOrApplicationAccordingToRights(nameOrId); final AdditionalFileParamsParsingResult additionalFileParamsParsingResult = getAdditionalFileSearchHelper(nameOrId, additionalFilesInfos); BadAdditionalFileParamsSearchException.check(additionalFileParamsParsingResult); AdditionalFileSearchHelper additionalFileSearchHelper = additionalFileParamsParsingResult.getResult(); @@ -1674,6 +1691,9 @@ public class OreSiService { List<AdditionalBinaryFile> additionalBinaryFiles = repo .getRepository(application).additionalBinaryFile() .findByCriteria(additionalFileSearchHelper); + if (additionalBinaryFiles.size() == 1 && AdditionalBinaryFile.CHART_FILE_TYPE.equals(additionalBinaryFiles.get(0).getFileType())) { + return additionalBinaryFiles.get(0).getData(); + } return additionalFileSearchHelper.zip(additionalBinaryFiles); } catch (DataIntegrityViolationException e) { return new byte[0]; @@ -1696,7 +1716,7 @@ public class OreSiService { } public AdditionalFileParamsParsingResult getAdditionalFileSearchHelper(String nameOrId, AdditionalFilesInfos additionalFilesInfos) { - Application application = getApplication(nameOrId); + Application application = getApplicationOrApplicationAccordingToRights(nameOrId); AdditionalFileParamsParsingResult.Builder builder = AdditionalFileParamsParsingResult.builder(); for (Map.Entry<String, AdditionalFilesInfos.AdditionalFileInfos> entry : additionalFilesInfos.getAdditionalFilesInfos().entrySet()) { String additionalFileName = entry.getKey(); @@ -1722,14 +1742,33 @@ public class OreSiService { public UUID createOrUpdate(CreateAdditionalFileRequest createAdditionalFileRequest, String nameOrId, MultipartFile file) { authenticationService.setRoleForClient(); Application application = getApplication(nameOrId); + final AdditionalBinaryFile additionalBinaryFile; + CurrentUserRoles rolesForCurrentUser = userRepository.getRolesForCurrentUser(); + final boolean isApplicationCreator = rolesForCurrentUser.getMemberOf().contains(OreSiRightOnApplicationRole.adminOn(application).getAsSqlRole()); - final AdditionalBinaryFile additionalBinaryFile = Optional.of(createAdditionalFileRequest) - .map(CreateAdditionalFileRequest::getId) - .map(id -> repo.getRepository(application).additionalBinaryFile().findById(id)) - .orElseGet(AdditionalBinaryFile::new); - additionalBinaryFile.setFileInfos(createAdditionalFileRequest.getFields()); + if (AdditionalBinaryFile.CHART_FILE_TYPE.equals(createAdditionalFileRequest.getFileType())) { + if (!isApplicationCreator) { + throw new NotApplicationCreatorRightsException(application.getName()); + } + additionalBinaryFile = Optional.ofNullable(repo.getRepository(application).additionalBinaryFile().findById(application.getId())) + .orElseGet(AdditionalBinaryFile::new); + additionalBinaryFile.setForApplication(true); + additionalBinaryFile.setFileType(AdditionalBinaryFile.CHART_FILE_TYPE); + additionalBinaryFile.setId(application.getId()); + additionalBinaryFile.setFileInfos(new HashMap<>()); + } else { + //vérifier que j'ai les droits + additionalBinaryFile = Optional.of(createAdditionalFileRequest) + .map(CreateAdditionalFileRequest::getId) + .map(id -> repo.getRepository(application).additionalBinaryFile().findById(id)) + .orElseGet(AdditionalBinaryFile::new); + + additionalBinaryFile.setForApplication(Optional.ofNullable(createAdditionalFileRequest.getForApplication()). + orElse(Boolean.FALSE)); + additionalBinaryFile.setFileType(createAdditionalFileRequest.getFileType()); + additionalBinaryFile.setFileInfos(createAdditionalFileRequest.getFields()); + } additionalBinaryFile.setApplication(application.getId()); - additionalBinaryFile.setForApplication(Optional.ofNullable(createAdditionalFileRequest.getForApplication()).orElse(Boolean.FALSE)); if (file != null) { additionalBinaryFile.setSize(file.getSize()); additionalBinaryFile.setFileName(file.getOriginalFilename()); @@ -1739,18 +1778,32 @@ public class OreSiService { throw new RuntimeException(e); } } - additionalBinaryFile.setFileType(createAdditionalFileRequest.getFileType()); - additionalBinaryFile.setCreationUser(additionalBinaryFile.getCreationUser() == null ? getCurrentUser().getId() : additionalBinaryFile.getCreationUser()); + additionalBinaryFile.setCreationUser(additionalBinaryFile.getCreationUser() == null ? + getCurrentUser(). + getId() : additionalBinaryFile.getCreationUser()); additionalBinaryFile.setUpdateUser(getCurrentUser().getId()); - additionalBinaryFile.setComment("un commentaire"); + additionalBinaryFile.setComment(createAdditionalFileRequest.getComment()); additionalBinaryFile.setId(additionalBinaryFile.getId() == null ? UUID.randomUUID() : additionalBinaryFile.getId()); final OreSiAuthorization oreSiAuthorization = new OreSiAuthorization(); oreSiAuthorization.setId(additionalBinaryFile.getId()); oreSiAuthorization.setApplication(application.getId()); - oreSiAuthorization.setAuthorizations(createAdditionalFileRequest.getAssociates().getAuthorizations()); + Optional.ofNullable(createAdditionalFileRequest) + . + + map(CreateAdditionalFileRequest::getAssociates) + . + + map(CreateAuthorizationRequest::getAuthorizations) + . + + ifPresent(auth -> oreSiAuthorization.setAuthorizations(auth)); final List<OreSiAuthorization> authorizations = List.of(oreSiAuthorization); additionalBinaryFile.setAssociates(authorizations); - return repo.getRepository(application).additionalBinaryFile().store(additionalBinaryFile); + return repo.getRepository(application). + + additionalBinaryFile(). + + store(additionalBinaryFile); } diff --git a/src/main/java/fr/inra/oresing/rest/exceptions/additionalfiles/NotApplicationOwnerRightsException.java b/src/main/java/fr/inra/oresing/rest/exceptions/additionalfiles/NotApplicationOwnerRightsException.java new file mode 100644 index 0000000000000000000000000000000000000000..c64533ee5e0536130937464611f64da62862017e --- /dev/null +++ b/src/main/java/fr/inra/oresing/rest/exceptions/additionalfiles/NotApplicationOwnerRightsException.java @@ -0,0 +1,20 @@ +package fr.inra.oresing.rest.exceptions.additionalfiles; + +import fr.inra.oresing.OreSiTechnicalException; +import lombok.Getter; + +import java.util.List; + +@Getter +public class NotApplicationOwnerRightsException extends OreSiTechnicalException { + public final static String NOT_APPLICATION_OWNER_RIGHTS = "NOT_APPLICATION_OWNER_RIGHTS"; + String applicationName; + public NotApplicationOwnerRightsException(String applicationName) { + super(NOT_APPLICATION_OWNER_RIGHTS); + this.applicationName = applicationName; + } + public NotApplicationOwnerRightsException(String applicationName, List<String> authorizationsRestrictions) { + super(NOT_APPLICATION_OWNER_RIGHTS); + this.applicationName = applicationName; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/model/additionalfiles/CreateAdditionalFileRequest.java b/src/main/java/fr/inra/oresing/rest/model/additionalfiles/CreateAdditionalFileRequest.java index 73aecc85cfd577686f8870fd20300e90b8794f97..be274d8173be86167407cbd38fc1ba0b0dbef9bf 100644 --- a/src/main/java/fr/inra/oresing/rest/model/additionalfiles/CreateAdditionalFileRequest.java +++ b/src/main/java/fr/inra/oresing/rest/model/additionalfiles/CreateAdditionalFileRequest.java @@ -13,4 +13,5 @@ public class CreateAdditionalFileRequest { Map<String, String> fields; CreateAuthorizationRequest associates; Boolean forApplication; + String comment; } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/model/application/ApplicationResult.java b/src/main/java/fr/inra/oresing/rest/model/application/ApplicationResult.java index d00fa654bc2faf62755bb37c6b85d8e81e71a9cf..ddd73b21e850129a324f39b16857fe5478f56c73 100644 --- a/src/main/java/fr/inra/oresing/rest/model/application/ApplicationResult.java +++ b/src/main/java/fr/inra/oresing/rest/model/application/ApplicationResult.java @@ -19,6 +19,8 @@ public class ApplicationResult { String name; String title; String comment; + int version; + UUID configFile; InternationalizationMap internationalization; Map<String, Reference> references; AuthorizationsForUserResult authorizationReferencesRights; diff --git a/src/main/resources/migration/application/V9__charteReadingForAll_schema.sql b/src/main/resources/migration/application/V9__charteReadingForAll_schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..3047068cbd7a8bf0b60dc347e491bd979645c3b3 --- /dev/null +++ b/src/main/resources/migration/application/V9__charteReadingForAll_schema.sql @@ -0,0 +1,10 @@ +DROP +POLICY if exists "charteReader" ON Additionalbinaryfile; +CREATE +POLICY "charteReader" + ON Additionalbinaryfile + AS PERMISSIVE + FOR +SELECT + TO public + USING (filetype = '__charte__'); diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index 3de4490e5cb7bbf3c7f98b93ca767077e99f6cbf..fe63848c0bcf58dcb10e14f37adb82e80b97d490 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -8,6 +8,7 @@ import fr.inra.oresing.OreSiNg; import fr.inra.oresing.OreSiTechnicalException; import fr.inra.oresing.ValidationLevel; import fr.inra.oresing.checker.InvalidDatasetContentException; +import fr.inra.oresing.model.ApplicationInformation; import fr.inra.oresing.model.OreSiUser; import fr.inra.oresing.persistence.AuthenticationService; import fr.inra.oresing.persistence.JsonRowMapper; @@ -115,7 +116,7 @@ public class OreSiResourcesTest { private UserRepository userRepository; @Test - @Category(OTHERS_TEST.class) + @Category(SWAGGER_BUILD.class) public void services_model() throws Exception { String services_model = mockMvc.perform(get("/v2/api-docs") .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)) @@ -490,6 +491,7 @@ public class OreSiResourcesTest { } final String getMonsoere = mockMvc.perform(get("/api/v1/applications/monsore") .cookie(authCookie) + .param("filter", ApplicationInformation.CONFIGURATION.name()) .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse().getContentAsString(); final String getSites = mockMvc.perform(get("/api/v1/applications/monsore/references/sites") @@ -1045,6 +1047,7 @@ public class OreSiResourcesTest { MockMultipartFile addFile = new MockMultipartFile("file", "monsoere.yaml", "text/plain", in); String json = "{\n" + " \"id\": \"\",\n" + + " \"comment\": \"un commentaire\",\n" + " \"fileType\": \"fichiers\",\n" + " \"fields\": {\n" + " \"age\": \"10\",\n" + @@ -1082,27 +1085,45 @@ public class OreSiResourcesTest { .andReturn().getResponse().getContentAsString(); mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles/fichiers") - .cookie(authCookie)); + .cookie(authCookie)) + .andExpect(jsonPath("$.fileNames", Matchers.hasSize(1))); mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles/fichiers") .cookie(withRigthsCookie)) - .andExpect(status().is2xxSuccessful()); + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.fileNames", Matchers.hasSize(1))); - String error = mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles/fichiers") + mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles/fichiers") .cookie(lambdaCookie)) - .andExpect(status().is4xxClientError()) - .andReturn().getResolvedException().getMessage(); - Assert.assertEquals("application inconnue 'monsore'", error); + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.fileNames", Matchers.hasSize(0))); + //Assert.assertEquals("application inconnue 'monsore'", error); //pas de droits - mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles") + mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles") .param("nameOrId", "monsore") .param("params", additionalJsonRequest) .cookie(lambdaCookie)) - .andExpect(status().is4xxClientError()) - .andExpect(result -> Assert.assertEquals("application inconnue 'monsore'", result.getResolvedException().getMessage())); + .andExpect(request().asyncStarted()) + .andReturn())) + .andExpect(result -> { + List<ZipEntry> entries = new ArrayList<>(); + ZipInputStream zi = null; + try { + zi = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray())); + ZipEntry zipEntry = null; + while ((zipEntry = zi.getNextEntry()) != null) { + entries.add(zipEntry); + } + } finally { + if (zi != null) { + zi.close(); + } + } + Assert.assertTrue(entries.size()==0); + }); - Assert.assertEquals("application inconnue 'monsore'", error); + //Assert.assertEquals("application inconnue 'monsore'", error); //avec droits mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/additionalFiles") .param("nameOrId", "monsore") diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index a7bfebba3fb1359ed358d86790925d053c712620..7f558e6ccde4efdf13ff32693325a10df9aa2bba 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -131,7 +131,8 @@ "dataTypeFiltreEmpty": "No data matching your criteria", "user-updated": "An email has just been sent to you at the following address: {email}", "user-email-updated": "Your email has been updated successfully. ", - "user-pwd-updated": "Your password has been updated successfully. " + "user-pwd-updated": "Your password has been updated successfully. ", + "charte-updated": "Charter updated" }, "message": { "app-config-error": "Error in yaml file", @@ -155,6 +156,13 @@ "aria-previous-page": "Previous page", "authorizations": "Authorizations" }, + "applicationInfo": { + "close-validate-charte": "Sign the charter and close", + "show-charte": "Consult the charter", + "add-charte": "Adding a charter", + "application-description": "Description :", + "validate-charte": "Validation of the charter" + }, "additionalFiles": { "file": { "forApplication": "This file will be provided with all extractions.", @@ -185,6 +193,7 @@ }, "applications": { "chose-config": "Chose a configuration", + "home": "Application homepage", "create": "Create application", "comment": "Comment : ", "no-comment": "No comment", diff --git a/ui/src/locales/fr.json b/ui/src/locales/fr.json index 6f7721282c64b9297d7cd46eda278ac4310e8858..5c4044135fa09b098aff17cad04c4f73eedc4374 100644 --- a/ui/src/locales/fr.json +++ b/ui/src/locales/fr.json @@ -131,7 +131,8 @@ "dataTypeFiltreEmpty": "Pas de données correspondant à vos critères", "user-updated": "Un mail vient de vous êtres envoyer à l'adresse suivante : {email}", "user-email-updated": "Votre email vient d'être mis à jours avec succès. ", - "user-pwd-updated": "Votre mot de passe vient d'être mis à jours avec succès. " + "user-pwd-updated": "Votre mot de passe vient d'être mis à jours avec succès. ", + "charte-updated": "Charte mis à jour" }, "message": { "app-config-error": "Erreur dans le fichier yaml", @@ -155,6 +156,13 @@ "aria-previous-page": "Page précédente", "authorizations": "Autorisations" }, + "applicationInfo": { + "close-validate-charte": "Signer la charte et fermer", + "show-charte": "Consulter la charte", + "add-charte": "Ajout d'une charte", + "application-description": "Description :", + "validate-charte": "Validation de la charte" + }, "additionalFiles": { "file": { "forApplication": "Ce fichier sera fourni avec toutes les extractions.", @@ -185,6 +193,7 @@ }, "applications": { "chose-config": "Choisir une configuration", + "home": "Page d'accueil de l'application", "create": "Créer l'application", "comment": "Commentaire : ", "no-comment": "Pas de commentaire", diff --git a/ui/src/main.js b/ui/src/main.js index c242eba949f9933cbe108b9759b3fe61f9f8bef5..fb98e33026ad3b324eb394776a4fea94057954a0 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -72,7 +72,7 @@ import { faShare, faChevronLeft, faChevronRight, - faInfoCircle, + faInfoCircle, faLaptopHouse, } from "@fortawesome/free-solid-svg-icons"; import { faCalendar as farCalendar, @@ -175,7 +175,8 @@ library.add( faEllipsisH, faSpinner, faExclamationTriangle, - faShare + faShare, + faLaptopHouse ); Vue.component("vue-fontawesome", FontAwesomeIcon); diff --git a/ui/src/router/index.js b/ui/src/router/index.js index dc51744b6f057e2aef991a3eed1da81f9889eaf7..764bd59fc771270a0592b08c674e25f0b53ffb51 100644 --- a/ui/src/router/index.js +++ b/ui/src/router/index.js @@ -22,6 +22,7 @@ import DataTypeAuthorizationsRightsRequestView from "@/views/authorizations/Data import RequestAuthorizationManagementView from "@/views/authorizations/RequestAuthorizationManagementView.vue"; import AuthorizationManagementForApplicationCreatorView from "@/views/authorizations/AuthorizationManagementForApplicationCreatorView.vue"; import UserView from "@/views/users/UserView.vue"; +import ApplicationInfoView from "@/views/application/ApplicationInfoView.vue"; Vue.use(VueRouter); @@ -51,6 +52,12 @@ const routes = [ name: "Applications", component: ApplicationsView, }, + { + path: "/applications/:applicationName", + name: "Application management view", + component: ApplicationInfoView, + props: true, + }, { path: "/applicationCreation", name: "Application creation", diff --git a/ui/src/services/rest/AdditionalFileService.js b/ui/src/services/rest/AdditionalFileService.js index 0c22e223723432f767a1bbf572f9f52c79c01d13..a0546abee8025294f8763771d7cacc3b3d5342a1 100644 --- a/ui/src/services/rest/AdditionalFileService.js +++ b/ui/src/services/rest/AdditionalFileService.js @@ -1,69 +1,102 @@ -import { Fetcher } from "../Fetcher"; +import {Fetcher} from "../Fetcher"; export class AdditionalFileService extends Fetcher { - static INSTANCE = new AdditionalFileService(); + static INSTANCE = new AdditionalFileService(); - constructor() { - super(); - } + constructor() { + super(); + } - async saveAdditionalFile( - id, - fileType, - applicationName, - additionalFileName, - file, - fields, - associates, - forApplication - ) { - /* associates = Object.keys(associates) - .reduce((acc, dataType)=>{ - acc[dataType] = associates[dataType].scopes.associate; - return acc; - }, {});*/ - return this.post(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { - file, - params: JSON.stringify({ + async saveAdditionalFile( id, fileType, + applicationName, + additionalFileName, + file, fields, associates, forApplication, - }), - }); - } + comment + ) { + /* associates = Object.keys(associates) + .reduce((acc, dataType)=>{ + acc[dataType] = associates[dataType].scopes.associate; + return acc; + }, {});*/ + return this.post(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { + file, + params: JSON.stringify({ + id, + fileType, + fields, + associates, + forApplication, + comment + }), + }); + } - async getAdditionalFilesWithGrantable(applicationName) { - return this.get(`applications/${applicationName}/additionalFiles`); - } + async saveCharteFile( + applicationName, + file, + comment + ) { + /* associates = Object.keys(associates) + .reduce((acc, dataType)=>{ + acc[dataType] = associates[dataType].scopes.associate; + return acc; + }, {});*/ + let additionalFileName = "__charte__" + return this.post(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { + file, + params: JSON.stringify({ + fileType: additionalFileName, + forApplication: true, + comment + }), + }); + } - async getAdditionalFiles(applicationName, additionalFileName, params) { - return this.get(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { - params: JSON.stringify(params), - }); - } + async getAdditionalFilesWithGrantable(applicationName) { + return this.get(`applications/${applicationName}/additionalFiles`); + } - async addAdditionalFile(applicationName, additionalFileName, additionalFile, params) { - return this.post(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { - file: additionalFile, - params: JSON.stringify(params), - }); - } + async getAdditionalFiles(applicationName, additionalFileName, params) { + return this.get(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { + params: JSON.stringify(params), + }); + } - getAdditionalFileZip(applicationName, additionalFilesInfos) { - return this.getPath( - `applications/${applicationName}/additionalFiles`, - { - params: JSON.stringify(additionalFilesInfos), - }, - "blob" - ); - } + async addAdditionalFile(applicationName, additionalFileName, additionalFile, params) { + return this.post(`applications/${applicationName}/additionalFiles/${additionalFileName}`, { + file: additionalFile, + params: JSON.stringify(params), + }); + } - async deleteAdditionalFile(applicationName, additionalFilesInfos) { - return this.delete(`applications/${applicationName}/additionalFiles`, { - params: JSON.stringify(additionalFilesInfos), - }); - } + getAdditionalFileZip(applicationName, additionalFilesInfos) { + return this.getPath( + `applications/${applicationName}/additionalFiles`, + { + params: JSON.stringify(additionalFilesInfos), + }, + "blob" + ); + } + + getCharte(applicationName, applicationIds) { + return this.getPath( + `applications/${applicationName}/additionalFiles`, + { + params: JSON.stringify(applicationIds), + }, + "blob" + ); + } + + async deleteAdditionalFile(applicationName, additionalFilesInfos) { + return this.delete(`applications/${applicationName}/additionalFiles`, { + params: JSON.stringify(additionalFilesInfos), + }); + } } diff --git a/ui/src/services/rest/ApplicationService.js b/ui/src/services/rest/ApplicationService.js index 719a1ce200a6618f8926fc4d1799b94ce7784777..f3e87d4caac486f7c13120d775cd19516f33bc32 100644 --- a/ui/src/services/rest/ApplicationService.js +++ b/ui/src/services/rest/ApplicationService.js @@ -23,7 +23,7 @@ export class ApplicationService extends Fetcher { } async getApplication(name, filter) { - var application = await this.get("applications/" + name, { filter }); + let application = await this.get("applications/" + name, { filter }); return InternationalisationService.INSTANCE.mergeInternationalization(application); } diff --git a/ui/src/services/rest/LoginService.js b/ui/src/services/rest/LoginService.js index 5a21a23f7c73bbac6043fc8f81ce20e55f24fcb7..2c3fa3aed257f712b60a854bc28cea4d7c5fbdd6 100644 --- a/ui/src/services/rest/LoginService.js +++ b/ui/src/services/rest/LoginService.js @@ -66,7 +66,6 @@ export class LoginService extends Fetcher { this.notifyCrendentialsLost(); } async getByIdOrLogin(userLoginOrId) { - console.log("userLoginOrId", userLoginOrId); return this.get(`users/${userLoginOrId}`); } } diff --git a/ui/src/views/additionalfiles/AdditionalFileInfosView.vue b/ui/src/views/additionalfiles/AdditionalFileInfosView.vue index 431928103e3a352c368afdda3c87d1ca67f6ae51..5dee094c840b9e6a02d015d47ef736094252269e 100644 --- a/ui/src/views/additionalfiles/AdditionalFileInfosView.vue +++ b/ui/src/views/additionalfiles/AdditionalFileInfosView.vue @@ -1,17 +1,17 @@ <template> <PageView class="with-submenu"> <SubMenu - :aria-label="$t('menu.aria-sub-menu')" - :paths="subMenuPaths" - :root="application.localName || application.title" - role="navigation" + :aria-label="$t('menu.aria-sub-menu')" + :paths="subMenuPaths" + :root="application.localName || application.title" + role="navigation" /> <h1 class="title main-title"> {{ $t("titles.additionalFileWithType", { localName: internationalisationService.getLocaleforPath( - application, - "additionalFiles." + additionalFileName + ".internationalizationName" + application, + "additionalFiles." + additionalFileName + ".internationalizationName" ), }) }} @@ -21,16 +21,16 @@ <ValidationObserver ref="observer"> <article class="fieldsForm"> <FieldsForm - :application="application" - :comment="comment" - :description="description" - :fields="fields" - :format="format" - :ref-values="references" - :showComment="true" - pathForKey="rightsRequest.format" - @update:fields="updateFields" - @update:comment="updateComment" + :application="application" + :comment="comment" + :description="description" + :fields="fields" + :format="format" + :ref-values="references" + :showComment="true" + pathForKey="rightsRequest.format" + @update:fields="updateFields" + @update:comment="updateComment" > </FieldsForm> </article> @@ -38,16 +38,16 @@ <b-collapse v-model="isOpenFileUpload" animation="slide" class="panel"> <template #trigger> <div - :aria-expanded="isOpenFileUpload" - aria-controls="contentIdForA11y2" - class="panel-heading" - role="button" + :aria-expanded="isOpenFileUpload" + aria-controls="contentIdForA11y2" + class="panel-heading" + role="button" > <strong> <FontAwesomeIcon - :icon="isOpenFileUpload ? 'caret-down' : 'caret-right'" - class="clickable mr-3" - tabindex="0" + :icon="isOpenFileUpload ? 'caret-down' : 'caret-right'" + class="clickable mr-3" + tabindex="0" /> {{ $t("additionalFiles.titles.upload") }} </strong> @@ -55,28 +55,28 @@ </template> <ValidationProvider - ref="file" - v-slot="{ errors, valid }" - :rules="rules()" - class="column is-12" + ref="file" + v-slot="{ errors, valid }" + :rules="rules()" + class="column is-12" > <b-field - :label="$t('additionalFiles.additionalFile')" - :message="errors" - :type="{ + :label="$t('additionalFiles.additionalFile')" + :message="errors" + :type="{ 'is-danger': errors && errors.length > 0, 'is-success': valid, }" - class="file is-primary column is-12" + class="file is-primary column is-12" > <b-upload - v-model="file" - class="file-label" - data-cy="changeFileButton" - expanded - required - style="margin-top: 30px" - @input="loadFile" + v-model="file" + class="file-label" + data-cy="changeFileButton" + expanded + required + style="margin-top: 30px" + @input="loadFile" > <span class="file-cta"> <b-icon class="file-icon" icon="upload"></b-icon> @@ -88,24 +88,24 @@ </ValidationProvider> <ValidationProvider - ref="fileName" - v-slot="{ errors, valid }" - :rules="rules()" - class="column is-12" + ref="fileName" + v-slot="{ errors, valid }" + :rules="rules()" + class="column is-12" > <b-field - :label="'nom du fichier'" - :message="errors" - :type="{ + :label="'nom du fichier'" + :message="errors" + :type="{ 'is-danger': errors && errors.length > 0, 'is-success': valid, }" - class="file is-primary column is-12" + class="file is-primary column is-12" > - <b-input v-model="fileName" type="text" /> + <b-input v-model="fileName" type="text"/> </b-field> <b-field :label="$t('additionalFiles.forApplication')"> - <b-switch v-model="forApplication" /> + <b-switch v-model="forApplication"/> </b-field> </ValidationProvider> </b-collapse> @@ -114,16 +114,16 @@ <b-collapse v-model="isOpenFileAssociate" animation="slide" class="panel"> <template #trigger> <div - :aria-expanded="isOpenFileAssociate" - aria-controls="contentIdForA11y2" - class="panel-heading" - role="button" + :aria-expanded="isOpenFileAssociate" + aria-controls="contentIdForA11y2" + class="panel-heading" + role="button" > <strong> <FontAwesomeIcon - :icon="isOpenFileAssociate ? 'caret-down' : 'caret-right'" - class="clickable mr-3" - tabindex="0" + :icon="isOpenFileAssociate ? 'caret-down' : 'caret-right'" + class="clickable mr-3" + tabindex="0" /> {{ $t("additionalFiles.titles.associate") }} </strong> @@ -131,34 +131,34 @@ </template> <div v-for="(datatypeInfos, datatype) in datatypes" :key="datatype"> <div - v-if="dataGroups[datatype] && authReferences[datatype] && columnsVisible[datatype]" + v-if="dataGroups[datatype] && authReferences[datatype] && columnsVisible[datatype]" > <AuthorizationTableForDatatype - :auth-references="authReferences[datatype]" - :authorization="authorization.authorizations[datatype]" - :authorization-scopes="authorizationScopes[datatype]" - :columns-visible="columnsVisible[datatype]" - :current-authorization-scope="{}" - :data-groups="dataGroups[datatype]" - :datatype="{ id: datatype, name: datatypeInfos.name }" - :is-root="true" - :isApplicationAdmin="canManage" - :ownAuthorizations="ownAuthorizations[datatype]" - :ownAuthorizationsColumnsByPath="ownAuthorizationsColumnsByPath[datatype]" - :publicAuthorizations="publicAuthorizations[datatype] || {}" - class="rows" - @modifyAuthorization="modifyAuthorization($event, datatype)" - @registerCurrentAuthorization="registerCurrentAuthorization($event, datatype)" + :auth-references="authReferences[datatype]" + :authorization="authorization.authorizations[datatype]" + :authorization-scopes="authorizationScopes[datatype]" + :columns-visible="columnsVisible[datatype]" + :current-authorization-scope="{}" + :data-groups="dataGroups[datatype]" + :datatype="{ id: datatype, name: datatypeInfos.name }" + :is-root="true" + :isApplicationAdmin="canManage" + :ownAuthorizations="ownAuthorizations[datatype]" + :ownAuthorizationsColumnsByPath="ownAuthorizationsColumnsByPath[datatype]" + :publicAuthorizations="publicAuthorizations[datatype] || {}" + class="rows" + @modifyAuthorization="modifyAuthorization($event, datatype)" + @registerCurrentAuthorization="registerCurrentAuthorization($event, datatype)" > <div class="row"> <div class="columns"> <b-field - v-for="(column, indexColumn) of columnsVisible[datatype]" - :key="indexColumn" - :field="indexColumn" - :label="getColumnTitle(column)" - :style="!column.display ? 'display : contents' : ''" - class="column" + v-for="(column, indexColumn) of columnsVisible[datatype]" + :key="indexColumn" + :field="indexColumn" + :label="getColumnTitle(column)" + :style="!column.display ? 'display : contents' : ''" + class="column" ></b-field> </div> </div> @@ -170,18 +170,18 @@ <div class="buttons"> <b-button - :type="'is-danger'" - icon-left="times-circle" - @click="modifyAssociateFile('consult')" + :type="'is-danger'" + icon-left="times-circle" + @click="modifyAssociateFile('consult')" > {{ $t("additionalFiles.buttons.cancel") }} </b-button> <b-button - :active="validFile && validForm" - :disabled="!validFile || !validForm" - :type="additionalFileId === 'new' ? 'is-primary' : 'is-warning'" - icon-left="edit" - @click="changeConfiguration" + :active="validFile && validForm" + :disabled="!validFile || !validForm" + :type="additionalFileId === 'new' ? 'is-primary' : 'is-warning'" + icon-left="edit" + @click="changeConfiguration" > {{ $t("additionalFiles.buttons.submit") }} </b-button> @@ -192,27 +192,27 @@ </template> <script> -import { ValidationObserver, ValidationProvider, extend } from "vee-validate"; +import {ValidationObserver, ValidationProvider, extend} from "vee-validate"; import CollapsibleTree from "@/components/common/CollapsibleTree.vue"; -import SubMenu, { SubMenuPath } from "@/components/common/SubMenu.vue"; -import { AlertService } from "@/services/AlertService"; -import { ApplicationService } from "@/services/rest/ApplicationService"; -import { AuthorizationService } from "@/services/rest/AuthorizationService"; -import { UserPreferencesService } from "@/services/UserPreferencesService"; -import { AdditionalFileService } from "@/services/rest/AdditionalFileService"; -import { Component, Prop, Vue, Watch } from "vue-property-decorator"; +import SubMenu, {SubMenuPath} from "@/components/common/SubMenu.vue"; +import {AlertService} from "@/services/AlertService"; +import {ApplicationService} from "@/services/rest/ApplicationService"; +import {AuthorizationService} from "@/services/rest/AuthorizationService"; +import {UserPreferencesService} from "@/services/UserPreferencesService"; +import {AdditionalFileService} from "@/services/rest/AdditionalFileService"; +import {Component, Prop, Vue, Watch} from "vue-property-decorator"; import PageView from "../common/PageView.vue"; -import { InternationalisationService } from "@/services/InternationalisationService"; -import { ApplicationResult } from "@/model/ApplicationResult"; -import { ReferenceService } from "@/services/rest/ReferenceService"; +import {InternationalisationService} from "@/services/InternationalisationService"; +import {ApplicationResult} from "@/model/ApplicationResult"; +import {ReferenceService} from "@/services/rest/ReferenceService"; import AuthorizationTable from "@/components/common/AuthorizationTable"; import AuthorizationTableForDatatype from "@/components/common/AuthorizationTableForDatatype.vue"; -import { Authorization } from "@/model/authorization/Authorization"; -import { Authorizations } from "@/model/authorization/Authorizations"; +import {Authorization} from "@/model/authorization/Authorization"; +import {Authorizations} from "@/model/authorization/Authorizations"; import FieldsForm from "@/components/common/provider/FieldsForm.vue"; -import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import {FontAwesomeIcon} from "@fortawesome/vue-fontawesome"; import LoadingAnimate from "@/components/common/LoadingAnimate.vue"; @Component({ @@ -233,7 +233,7 @@ import LoadingAnimate from "@/components/common/LoadingAnimate.vue"; export default class AdditionalFileInfosView extends Vue { @Prop() applicationName; @Prop() additionalFileName; - @Prop({ default: "new" }) additionalFileId; + @Prop({default: "new"}) additionalFileId; __DEFAULT__ = "__DEFAULT__"; referenceService = ReferenceService.INSTANCE; references = {}; @@ -275,7 +275,7 @@ export default class AdditionalFileInfosView extends Vue { label: { title: "Label", display: true, - internationalizationName: { fr: "Domaine", en: "Domain" }, + internationalizationName: {fr: "Domaine", en: "Domain"}, }, }; columnsVisible = false; @@ -306,8 +306,8 @@ export default class AdditionalFileInfosView extends Vue { getColumnTitle(column) { if (column.display) { return ( - (column.internationalizationName && column.internationalizationName[this.$i18n.locale]) || - column.title + (column.internationalizationName && column.internationalizationName[this.$i18n.locale]) || + column.title ); } } @@ -323,19 +323,19 @@ export default class AdditionalFileInfosView extends Vue { var toDeleteElement = event.authorizations.toDelete[authorizationKeytoDelete]; authorizations = authorizations.filter((auth) => { return !new Authorization(auth).equals( - toDeleteElement, - this.authorizationScopes[datatype].map((scope) => scope.id) + toDeleteElement, + this.authorizationScopes[datatype].map((scope) => scope.id) ); }); } authorization.authorizations[event.indexColumn] = authorizations; this.$set( - this.authorization.authorizations, - datatype, - new Authorizations( - authorization, - this.authorizationScopes[datatype].map((as) => as.id) - ) + this.authorization.authorizations, + datatype, + new Authorizations( + authorization, + this.authorizationScopes[datatype].map((as) => as.id) + ) ); } @@ -356,10 +356,10 @@ export default class AdditionalFileInfosView extends Vue { ]; authorizations = authorizations.map((auth) => { if ( - !new Authorization(auth).equals( - authorizationToReplace, - this.authorizationScopes[datatype].map((scope) => scope.id) - ) + !new Authorization(auth).equals( + authorizationToReplace, + this.authorizationScopes[datatype].map((scope) => scope.id) + ) ) { return auth; } else { @@ -368,12 +368,12 @@ export default class AdditionalFileInfosView extends Vue { }); authorization.authorizations[event.indexColumn] = authorizations; this.$set( - this.authorization.authorizations, - event.datatype, - new Authorizations( - authorization, - this.authorizationScopes.map((as) => as.id) - ) + this.authorization.authorizations, + event.datatype, + new Authorizations( + authorization, + this.authorizationScopes.map((as) => as.id) + ) ); } @@ -382,27 +382,29 @@ export default class AdditionalFileInfosView extends Vue { this.chosenLocale = this.userPreferencesService.getUserPrefLocale(); this.subMenuPaths = [ new SubMenuPath( - this.$t("additionalFilesManagement.additionalFiles").toLowerCase(), - () => this.$router.push(`/applications/${this.applicationName}/additionalFiles`), - () => this.$router.push("/applications") + this.$t("additionalFilesManagement.additionalFiles").toLowerCase(), + () => this.$router.push(`/applications/${this.applicationName}/additionalFiles`), + () => this.$router.push("/applications/"+this.applicationName) ), new SubMenuPath( - this.$t( - this.additionalFileId === "new" - ? `referencesAuthorizations.sub-menu-new-authorization` - : "referencesAuthorizations.sub-menu-modify-authorization", - { additionalFileId: this.additionalFileId } - ), - () => {}, - () => { - this.$router.push(`/applications/${this.applicationName}/additionalFiles`); - } + this.$t( + this.additionalFileId === "new" + ? `referencesAuthorizations.sub-menu-new-authorization` + : "referencesAuthorizations.sub-menu-modify-authorization", + {additionalFileId: this.additionalFileId} + ), + () => { + }, + () => { + this.$router.push(`/applications/${this.applicationName}/additionalFiles`); + } ), ]; this.isLoading = false; } - mounted() {} + mounted() { + } async init() { this.isLoading = true; @@ -413,21 +415,21 @@ export default class AdditionalFileInfosView extends Vue { "RIGHTSREQUEST", ]); this.datatypes = (Object.keys(this.application.configuration.dataTypes) || []).reduce( - (acc, datatype) => { - acc[datatype] = { - name: - this.internationalisationService.localeDataTypeIdName( - this.application, - this.application.dataTypes[datatype] - ) || datatype, - }; - return acc; - }, - {} + (acc, datatype) => { + acc[datatype] = { + name: + this.internationalisationService.localeDataTypeIdName( + this.application, + this.application.dataTypes[datatype] + ) || datatype, + }; + return acc; + }, + {} ); this.format = - ((this.application?.configuration?.additionalFiles || {})[this.additionalFileName] || {}) - .format || {}; + ((this.application?.configuration?.additionalFiles || {})[this.additionalFileName] || {}) + .format || {}; this.description = this.$t("titles.additionalFileDescription"); this.fields = (Object.keys(this.format) || []).reduce((acc, field) => { acc[field] = ""; @@ -440,7 +442,7 @@ export default class AdditionalFileInfosView extends Vue { this.application = { ...this.application, localName: this.internationalisationService.mergeInternationalization(this.application) - .localName, + .localName, }; this.authorizations = (Object.keys(this.datatypes) || []).reduce((acc, datatype) => { acc[datatype] = this.configuration[datatype]?.authorization?.authorizationScopes || []; @@ -451,7 +453,7 @@ export default class AdditionalFileInfosView extends Vue { return acc; }, {}); const grantableInfos = await this.authorizationService.getAuthorizationGrantableInfos( - this.applicationName + this.applicationName ); ({ authorizationScopes: this.authorizationScopes, @@ -465,15 +467,15 @@ export default class AdditionalFileInfosView extends Vue { } = Authorizations.parseGrantableInfos(grantableInfos, this.datatypes, this.repository)); let additionalFiles = await this.additionalFileService.getAdditionalFiles( - this.applicationName, - this.additionalFileName, - {} + this.applicationName, + this.additionalFileName, + {} ); this.fileNames = additionalFiles.fileNames.filter( - (file) => this.additionalFile && this.additionalFile.fileName != file.fileName + (file) => this.additionalFile && this.additionalFile.fileName != file.fileName ); let additionalFile = additionalFiles.additionalBinaryFiles.find( - (file) => file.id == this.additionalFileId + (file) => file.id == this.additionalFileId ); this.fileName = additionalFile && additionalFile.fileName; if (this.additionalFileId != "new") { @@ -481,8 +483,8 @@ export default class AdditionalFileInfosView extends Vue { this.validFile = true; this.forApplication = additionalFile.forApplication; this.currentUser = additionalFiles.users.find( - (user) => - user.id == (additionalFile.user || JSON.parse(localStorage.authenticatedUser).id) + (user) => + user.id == (additionalFile.user || JSON.parse(localStorage.authenticatedUser).id) ); this.comment = additionalFile.comment; this.fields = Object.keys(this.format || {} || []).reduce((acc, field) => { @@ -492,43 +494,43 @@ export default class AdditionalFileInfosView extends Vue { let authorizations = (additionalFile && additionalFile.associates) || []; let initialValue = new Authorizations( - { - authorizations: {}, - applicationNameOrId: this.applicationName, - users: [this.users], - name: additionalFile.comment, - uuid: additionalFile.id, - }, - [] + { + authorizations: {}, + applicationNameOrId: this.applicationName, + users: [this.users], + name: additionalFile.comment, + uuid: additionalFile.id, + }, + [] ); this.authorization = (Object.keys(this.datatypes) || []).reduce((auth, datatype) => { auth.authorizations[datatype] = new Authorizations( - { authorizations: authorizations[datatype] }, - (this.authorizationScopes[datatype] || []).map((as) => as.id) + {authorizations: authorizations[datatype]}, + (this.authorizationScopes[datatype] || []).map((as) => as.id) ); return auth; }, initialValue); this.canManage = - this.isApplicationAdmin || - (authorizations.users && - authorizations.users[0].login == - JSON.parse(localStorage.getItem("authenticatedUser")).login); + this.isApplicationAdmin || + (authorizations.users && + authorizations.users[0].login == + JSON.parse(localStorage.getItem("authenticatedUser")).login); } else { this.fileNames = additionalFiles.fileNames; let initialValue = new Authorizations( - { - authorizations: {}, - applicationNameOrId: this.applicationName, - users: [], - name: "", - uuid: null, - }, - [] + { + authorizations: {}, + applicationNameOrId: this.applicationName, + users: [], + name: "", + uuid: null, + }, + [] ); this.authorization = (Object.keys(this.datatypes) || []).reduce((acc, datatype) => { acc.authorizations[datatype] = new Authorizations( - { dataType: datatype, applicationNameOrId: this.applicationName }, - (this.authorizationScopes[datatype] || []).map((as) => as.id) + {dataType: datatype, applicationNameOrId: this.applicationName}, + (this.authorizationScopes[datatype] || []).map((as) => as.id) ); return acc; }, initialValue); @@ -542,10 +544,10 @@ export default class AdditionalFileInfosView extends Vue { }); this.selectedUsers.sort(); this.authReferences = await Authorizations.initAuthReferences( - this.configuration, - this.authorizations, - this.authorizationScopes, - this.getOrLoadReferences + this.configuration, + this.authorizations, + this.authorizationScopes, + this.getOrLoadReferences ); let columnsVisible = {}; @@ -557,7 +559,7 @@ export default class AdditionalFileInfosView extends Vue { display: true, forPublic: true, forRequest: true, - internationalizationName: { fr: "Associer", en: "Associate" }, + internationalizationName: {fr: "Associer", en: "Associate"}, }, }; } @@ -663,22 +665,23 @@ export default class AdditionalFileInfosView extends Vue { } }*/ await this.additionalFileService.saveAdditionalFile( - this.additionalFileId == "new" ? "" : this.additionalFileId, - this.additionalFileName, - this.applicationName, - this.additionalFileName, - this.file, - { ...this.fields }, - authorizationToSend, - this.forApplication + this.additionalFileId === "new" ? "" : this.additionalFileId, + this.additionalFileName, + this.applicationName, + this.additionalFileName, + this.file, + {...this.fields}, + authorizationToSend, + this.forApplication, + this.comment ); - if ("new" == this.additionalFileId) { + if ("new" === this.additionalFileId) { this.alertService.toastSuccess(this.$t("alert.create-request")); } else { this.alertService.toastSuccess(this.$t("alert.modified-request")); } this.$router.push( - `/applications/${this.applicationName}/additionalFiles/${this.additionalFileName}` + `/applications/${this.applicationName}/additionalFiles/${this.additionalFileName}` ); } catch (error) { this.alertService.toastServerError(error); @@ -725,7 +728,7 @@ export default class AdditionalFileInfosView extends Vue { modifyAssociateFile(id) { this.$router.push( - `/applications/${this.applicationName}/additionalFiles/${this.additionalFileName}/${id}` + `/applications/${this.applicationName}/additionalFiles/${this.additionalFileName}/${id}` ); } diff --git a/ui/src/views/additionalfiles/AdditionalFilesManagementView.vue b/ui/src/views/additionalfiles/AdditionalFilesManagementView.vue index 1182ad10fff82de804ec78a094da24104e0cfa9c..009bb27712172add0e7e85526252e65aaed0f13a 100644 --- a/ui/src/views/additionalfiles/AdditionalFilesManagementView.vue +++ b/ui/src/views/additionalfiles/AdditionalFilesManagementView.vue @@ -241,8 +241,8 @@ export default class AdditionalFilesManagementView extends Vue { this.subMenuPaths = [ new SubMenuPath( this.$t("additionalFilesManagement.additionalFilesManagement").toLowerCase(), - () => {}, - () => this.$router.push("/applications") + () => this.$router.push(`/applications/${this.applicationName}/additionalFiles`), + () => this.$router.push("/applications/"+this.applicationName) ), ]; } diff --git a/ui/src/views/application/ApplicationCreationView.vue b/ui/src/views/application/ApplicationCreationView.vue index 80490216403f116241ad3f3b8915fa44572c5852..691596a0d5926d6ec29069f59a0071854bc300eb 100644 --- a/ui/src/views/application/ApplicationCreationView.vue +++ b/ui/src/views/application/ApplicationCreationView.vue @@ -187,7 +187,7 @@ export default class ApplicationCreationView extends Vue { try { await this.applicationService.changeConfiguration(this.applicationConfig, this.comment); this.alertService.toastSuccess(this.$t("alert.application-edit-success")); - this.$router.push("/applications"); + this.$router.push("/applications/"+this.applicationName); } catch (error) { this.checkMessageErrors(error); } diff --git a/ui/src/views/application/ApplicationInfoView.vue b/ui/src/views/application/ApplicationInfoView.vue new file mode 100644 index 0000000000000000000000000000000000000000..1b6c0bf5d183590fc2db1e03f1bd43f371825920 --- /dev/null +++ b/ui/src/views/application/ApplicationInfoView.vue @@ -0,0 +1,302 @@ +<template> + <PageView> + <h1 class="title main-title">{{ application.localName }}</h1> + <LoadingAnimate v-if="loading" :size="'is-large'"></LoadingAnimate> + <b-tabs v-else v-model="activeTab"> + <b-tab-item :label="$t('applicationInfo.application-description')"> + <div class="columns"> + <p class="column" v-html=" + $t('applications.version', { + applicationName: application.localName, + version: application.version, + }) + " + /> + <div v-show="!canCreateApplication && charteApplication.length !== 0" class="column"> + <div class="columns" style="margin: 0"> + <b-button icon-left="eye" + type="is-light" + @click="getCharte(applicationName ,charteApplication)"> + {{ $t('applicationInfo.show-charte') }} + </b-button> + <b-checkbox class="column" v-model="showAllApplication" :disabled="!showAllApplication">{{ $t('applicationInfo.validate-charte') }}</b-checkbox> + </div> + </div> + </div> + <div class="columns"> + <p class="comment column"> + <span + :class="application.comment ? 'has-text-primary' : 'has-text-warning'" + > + {{ + application.comment + ? $t("applications.comment") + : $t("applications.no-comment") + }} + </span> + <span>{{ application.comment }}</span> + </p> + </div> + <div v-show="showAllApplication || canCreateApplication" class="columns"> + <div v-if="application.references && Object.keys(application.references).length !== 0" + class="column"> + <b-button + icon-left="drafting-compass" + @click="displayReferencesManagement(application)" + >{{ $t("applications.references") }} + </b-button> + </div> + <div v-if="application.dataTypes && Object.keys(application.dataTypes).length !== 0" + class="column"> + <b-button icon-left="poll" @click="displayDataSetManagement(application)" + >{{ $t("applications.dataset") }} + </b-button> + </div> + <div + v-if="application.additionalFiles && Object.keys(application.additionalFiles).length !== 0" + class="column" + > + <b-button + icon-left="file" + @click="displayAdditionalFilesManagement(application)" + > + {{ $t("applications.additionalFile") }} + </b-button> + </div> + </div> + <div class="columns"> + <div v-if="canCreateApplication" class="column"> + <b-button + icon-left="pen-square" + type="is-warning" + @click="updateApplication(application.id)" + > + {{ $t("applications.change") }} + </b-button> + </div> + <div v-if="canCreateApplication" class="column"> + <b-button + icon-left="download" + type="is-primary" + @click="downloadYamlApplication(application)" + > + {{ $t("referencesManagement.download") }} + </b-button> + </div> + <div class="column"> + <b-button + icon-left="users-cog" + type="is-primary" + @click="requestRights(application)" + > + {{ $t("dataTypeAuthorizations.request") }} + </b-button> + </div> + </div> + </b-tab-item> + + <b-tab-item :visible="canCreateApplication" label="Gestion de la charte"> + <b-field :label="$t('applications.comment')"> + <b-input v-model="comment" + :placeholder="$t('login.activatedKey-placeholder')" + required + type="textarea"> + </b-input> + </b-field> + <b-field class="file"> + <b-upload v-show="comment !== ''" + v-model="file" + accept=".pdf" + class="file-label" + @input="uploadCharte()" + > + <span class="file-cta"> + <LoadingAnimate v-if="isUploading && file" :size="'is-small'"></LoadingAnimate> + <b-icon v-else + icon="upload" + ></b-icon> + <span>{{ $t('applicationInfo.add-charte') }}</span> + </span> + <span v-if="file" class="file-name"> + {{ file.name }} + </span> + </b-upload> + </b-field> + </b-tab-item> + </b-tabs> + </PageView> +</template> + +<script> + +import {ApplicationService} from "@/services/rest/ApplicationService"; +import {InternationalisationService} from "@/services/InternationalisationService"; +import {Component, Prop, Vue} from "vue-property-decorator"; +import PageView from "@/views/common/PageView.vue"; +import {LoginService} from "@/services/rest/LoginService"; +import {FileService} from "@/services/rest/FileService"; +import LoadingAnimate from "@/components/common/LoadingAnimate.vue"; +import {AdditionalFileService} from "@/services/rest/AdditionalFileService"; +import {HttpStatusCodes} from "@/utils/HttpUtils"; +import {AlertService} from "@/services/AlertService"; + +@Component({ + components: {LoadingAnimate, PageView}, +}) +export default class ApplicationInfoView extends Vue { + @Prop() applicationName; + applicationService = ApplicationService.INSTANCE; + internationalisationService = InternationalisationService.INSTANCE; + additionnalFileService = AdditionalFileService.INSTANCE; + alertService = AlertService.INSTANCE; + application = []; + file = null; + isUploading = false; + canCreateApplication = + LoginService.INSTANCE.getAuthenticatedUser().authorizedForApplicationCreation; + fileService = FileService.INSTANCE; + loginService = LoginService.INSTANCE; + loading = false; + charteApplication = []; + comment = ""; + activeTab = 0; + showAllApplication = false; + currentUser = JSON.parse(localStorage.getItem("authenticatedUser")); + + async downloadYamlApplication(application) { + await this.fileService.download(application.name, application.configFile); + return false; + } + + async requestRights(application) { + this.$router.push(`/applications/${application.name}/authorizationsRequest`); + return false; + } + + async showRequestRights(application) { + this.$router.push(`/applications/${application.name}/authorizationsRequest/new`); + return false; + } + + async created() { + await this.init(); + } + + async init() { + this.loading = true; + this.application = await this.applicationService.getApplication(this.applicationName, [ + "ALL", + ]); + this.application = { + ...this.application, + localName: this.internationalisationService.mergeInternationalization(this.application) + .localName, + }; + let additionalFiles = await this.additionnalFileService.getAdditionalFiles(this.applicationName, "__charte__", null); + for (let i = 0; i < additionalFiles.additionalBinaryFiles.length; i++) { + if (additionalFiles.additionalBinaryFiles[i].additionalBinaryFileType === "__charte__") { + this.charteApplication.push(additionalFiles.additionalBinaryFiles[i]); + } + } + if (this.charteApplication.length === 0) { + this.showAllApplication = true; + } else { + this.showAllApplication = localStorage.getItem( + "charte_"+this.applicationName + ); + } + this.loading = false; + } + + async uploadCharte() { + this.isUploading = true; + this.errorsMessages = []; + try { + await this.additionnalFileService.saveCharteFile(this.applicationName, this.file, this.comment); + this.alertService.toastSuccess(this.$t("alert.charte-updated")); + this.isUploading = false; + } catch (errors) { + await this.checkMessageErrors(errors); + } + } + + validationCharte() { + if (this.showAllApplication === false) { + localStorage.setItem( + "charte_"+this.applicationName, + "true" + ); + } + this.showAllApplication = true; + } + + async checkMessageErrors(errors) { + if (errors.httpResponseCode === HttpStatusCodes.BAD_REQUEST) { + errors.content.then((value) => { + for (let i = 0; i < value.length; i++) { + this.errorsList[i] = value[i]; + } + if (this.errorsList.length !== 0) { + this.errorsMessages = this.errorsService.getCsvErrorsMessages(this.errorsList); + } else { + this.errorsMessages = this.errorsService.getErrorsMessages(errors); + } + }); + } else { + this.alertService.toastError(this.$t("alert.reference-csv-upload-error"), errors); + } + } + + async getCharte(applicationName) { + this.errorsMessages = []; + try { + let param = {uuids: [this.application.id], filetype: "__charte__"}; + let path = await this.additionnalFileService.getAdditionalFileZip(applicationName, param) + let link = document.createElement("a"); + link.href = path; + link.download = "charte.pdf"; + link.click(); + window.URL.revokeObjectURL(link.href); + this.alertService.toastSuccess(this.$t("alert.charte-updated")); + this.validationCharte(); + } catch (errors) { + await this.checkMessageErrors(errors); + } + } + + updateApplication() { + this.$router.push(`/applicationCreation`); + } + + displayReferencesManagement(application) { + if (!application) { + return; + } + this.$router.push("/applications/" + application.name + "/references"); + } + + displayDataSetManagement(application) { + if (!application) { + return; + } + this.$router.push("/applications/" + application.name + "/dataTypes"); + } + + displayAdditionalFilesManagement(application) { + if (!application) { + return; + } + this.$router.push("/applications/" + application.name + "/additionalFiles"); + } + +} +</script> + +<style scoped> +footer { + position: absolute; + bottom: 0; + width: 100%; + height: 50px; +} +</style> \ No newline at end of file diff --git a/ui/src/views/application/ApplicationsView.vue b/ui/src/views/application/ApplicationsView.vue index 1a20e55af0bee469aace6c347fea562235d84082..bc990ef021ef50f9277180f4458dce826ac6945a 100644 --- a/ui/src/views/application/ApplicationsView.vue +++ b/ui/src/views/application/ApplicationsView.vue @@ -7,11 +7,11 @@ <section> <div v-if="canCreateApplication" class="card is-clickable"> <div - class="card-header createApplication" - role="button" - style="margin-bottom: 50px" - tabindex="0" - @click="createApplication" + class="card-header createApplication" + role="button" + style="margin-bottom: 50px" + tabindex="0" + @click="createApplication" > <a class="card-header-icon createApplication"> <b-icon icon="plus"></b-icon> @@ -31,37 +31,37 @@ <div class="content"> <b-field class="columns"> <b-checkbox - v-model="checkboxDate" - class="column" - false-value="false" - field="name" - true-value="true" - @input="recalculate" - >{{ $t("applications.trierRecent") }} + v-model="checkboxDate" + class="column" + false-value="false" + field="name" + true-value="true" + @input="recalculate" + >{{ $t("applications.trierRecent") }} </b-checkbox> </b-field> </div> <div class="content"> <b-field class="columns"> <b-checkbox - id="checkboxTrieA_z" - v-model="checkboxTrieA_z" - class="column" - false-value="false" - field="name" - true-value="true" - @input="recalculate" - >{{ $t("applications.trierA_z") }} + id="checkboxTrieA_z" + v-model="checkboxTrieA_z" + class="column" + false-value="false" + field="name" + true-value="true" + @input="recalculate" + >{{ $t("applications.trierA_z") }} </b-checkbox> <b-checkbox - id="checkboxTrieZ_a" - v-model="checkboxTrieZ_a" - class="column" - false-value="false" - field="name" - true-value="true" - @input="recalculate" - >{{ $t("applications.trierZ_a") }} + id="checkboxTrieZ_a" + v-model="checkboxTrieZ_a" + class="column" + false-value="false" + field="name" + true-value="true" + @input="recalculate" + >{{ $t("applications.trierZ_a") }} </b-checkbox> </b-field> </div> @@ -76,12 +76,12 @@ <b-field> {{ $t("applications.name") }} <b-autocomplete - v-model="filterName" - :data="selectedApplications" - field="localName" - placeholder="olac" - @click.native="recalculate" - @keyup.native="recalculate" + v-model="filterName" + :data="selectedApplications" + field="localName" + placeholder="olac" + @click.native="recalculate" + @keyup.native="recalculate" > </b-autocomplete> </b-field> @@ -94,116 +94,27 @@ <LoadingAnimate v-if="loading" :size="'is-large'"></LoadingAnimate> <div class="columns"> <div - v-for="(application, index) in selectedApplications" - :key="application.name" - style="margin-left: 30px" + v-for="(application, index) in selectedApplications" + :key="application.name" + style="margin-left: 30px" > <div class="column is-3-widescreen is-6-desktop is-12-tablet"> <div - v-if="index >= (current - 1) * perPage && index < current * perPage" - class="applicationCard card" - style="padding-bottom: 10px" + v-if="index >= (current - 1) * perPage && index < current * perPage" + class="applicationCard card" + style="padding-bottom: 10px" > <div class="card-header"> <div class="title card-header-title"> <p field="name" style="font-size: 1.5rem">{{ application.localName }}</p> </div> - <b-button - class="btnModal" - icon-left="ellipsis-h" - size="is-medium" - type="is-primary" - @click="showModal(application.name)" - /> - <b-modal - v-show="isSelectedName === application.name" - :id="application.name" - v-model="isCardModalActive" - > - <div class="card"> - <div class="card-header"> - <div class="title card-header-title"> - <p field="name">{{ application.localName }}</p> - </div> - </div> - <div class="card-content"> - <div class="content"> - <p - v-html=" - $t('applications.version', { - applicationName: application.localName, - version: application.version, - }) - " - /> - <p class="comment"> - <span - :class="application.comment ? 'has-text-primary' : 'has-text-warning'" - > - {{ - application.comment - ? $t("applications.comment") - : $t("applications.no-comment") - }} - </span> - <span>{{ application.comment }}</span> - </p> - </div> - </div> - <div class="card-footer"> - <div class="card-footer-item"> - <b-button - icon-left="drafting-compass" - @click="displayReferencesManagement(application)" - >{{ $t("applications.references") }} - </b-button> - </div> - <div class="card-footer-item"> - <b-button icon-left="poll" @click="displayDataSetManagement(application)" - >{{ $t("applications.dataset") }} - </b-button> - </div> - <div v-if="canCreateApplication" class="card-footer-item"> - <b-button - icon-left="pen-square" - type="is-warning" - @click="updateApplication(application.id)" - > - {{ $t("applications.change") }} - </b-button> - </div> - </div> - <div class="card-footer"> - <div v-if="canCreateApplication" class="card-footer-item"> - <b-button - icon-left="download" - type="is-primary" - @click="downloadYamlApplication(application)" - > - {{ $t("referencesManagement.download") }} - </b-button> - </div> - <div v-if="!canCreateApplication" class="card-footer-item"> - <b-button - icon-left="users-cog" - type="is-primary" - @click="showRequestRights(application)" - > - {{ $t("dataTypeAuthorizations.showRequests") }} - </b-button> - </div> - <div v-else class="card-footer-item"> - <b-button - icon-left="users-cog" + <b-button v-if="canCreateApplication" + class="btnModal" + icon-left="laptop-house" + size="is-medium" type="is-primary" - @click="requestRights(application)" - > - {{ $t("dataTypeAuthorizations.request") }} - </b-button> - </div> - </div> - </div> - </b-modal> + @click="showInfoApplication(application)"> + </b-button> </div> <div class="card-content"> <div class="content"> @@ -212,58 +123,76 @@ </p> </div> </div> - <div class="card-footer"> - <div class="card-footer-item"> - <b-button - v-if="application.referenceType.length !== 0" - icon-left="drafting-compass" - @click="displayReferencesManagement(application)" - > - {{ $t("applications.references") }} - </b-button> + <div v-if="!canCreateApplication && showAllApplications[application.name]" class="btn_info_application"> + <b-button + icon-left="laptop-house" + type="is-primary" + @click="showInfoApplication(application)"> {{ $t('applications.home') }} + </b-button> + </div> + <div v-else> + <div class="card-footer"> + <div class="card-footer-item"> + <b-button v-if="!canCreateApplication" + icon-left="laptop-house" + type="is-primary" + @click="showInfoApplication(application)"> {{ $t('applications.home') }} + </b-button> + </div> </div> - <div class="card-footer-item"> - <b-button - v-if="application.dataType.length !== 0" - icon-left="poll" - @click="displayDataSetManagement(application)" - > - {{ $t("applications.dataset") }} - </b-button> + <div class="card-footer"> + <div class="card-footer-item"> + <b-button + v-if="application.referenceType.length !== 0" + icon-left="drafting-compass" + @click="displayReferencesManagement(application)" + > + {{ $t("applications.references") }} + </b-button> + </div> + <div class="card-footer-item"> + <b-button + v-if="application.dataType.length !== 0" + icon-left="poll" + @click="displayDataSetManagement(application)" + > + {{ $t("applications.dataset") }} + </b-button> + </div> </div> - </div> - <div class="card-footer"> - <div - v-if="application.additionalFile && application.additionalFile.length !== 0" - class="card-footer-item" - > - <b-button - icon-left="file" - @click="displayAdditionalFilesManagement(application)" + <div class="card-footer"> + <div + v-if="application.additionalFile && application.additionalFile.length !== 0" + class="card-footer-item" > - {{ $t("applications.additionalFile") }} - </b-button> + <b-button + icon-left="file" + @click="displayAdditionalFilesManagement(application)" + > + {{ $t("applications.additionalFile") }} + </b-button> + </div> </div> </div> </div> </div> </div> </div> - <hr /> + <hr/> <b-pagination - v-if="perPage <= applications.length" - :aria-current-label="$t('menu.aria-curent-page')" - :aria-label="$t('menu.aria-pagination')" - :aria-next-label="$t('menu.aria-next-page')" - :aria-previous-label="$t('menu.aria-previous-page')" - :current.sync="current" - :per-page="perPage" - :range-after="2" - :range-before="2" - :rounded="true" - :total="applications.length" - order="is-centered" - role="navigation" + v-if="perPage <= applications.length" + :aria-current-label="$t('menu.aria-curent-page')" + :aria-label="$t('menu.aria-pagination')" + :aria-next-label="$t('menu.aria-next-page')" + :aria-previous-label="$t('menu.aria-previous-page')" + :current.sync="current" + :per-page="perPage" + :range-after="2" + :range-before="2" + :rounded="true" + :total="applications.length" + order="is-centered" + role="navigation" > </b-pagination> </div> @@ -272,24 +201,26 @@ </template> <script> -import { ApplicationService } from "@/services/rest/ApplicationService"; -import { InternationalisationService } from "@/services/InternationalisationService"; -import { Component, Vue } from "vue-property-decorator"; +import {ApplicationService} from "@/services/rest/ApplicationService"; +import {InternationalisationService} from "@/services/InternationalisationService"; +import {Component, Vue} from "vue-property-decorator"; import PageView from "@/views/common/PageView.vue"; -import { LoginService } from "@/services/rest/LoginService"; -import { FileService } from "@/services/rest/FileService"; +import {LoginService} from "@/services/rest/LoginService"; +import {FileService} from "@/services/rest/FileService"; import LoadingAnimate from "@/components/common/LoadingAnimate.vue"; +import {AdditionalFileService} from "@/services/rest/AdditionalFileService"; @Component({ - components: { LoadingAnimate, PageView }, + components: {LoadingAnimate, PageView}, }) export default class ApplicationsView extends Vue { applicationService = ApplicationService.INSTANCE; internationalisationService = InternationalisationService.INSTANCE; + additionnalFileService = AdditionalFileService.INSTANCE; applications = []; canCreateApplication = - LoginService.INSTANCE.getAuthenticatedUser().authorizedForApplicationCreation; + LoginService.INSTANCE.getAuthenticatedUser().authorizedForApplicationCreation; fileService = FileService.INSTANCE; // show modal and cards isSelectedName = ""; @@ -302,6 +233,9 @@ export default class ApplicationsView extends Vue { // filtre variable filterName = ""; selected = null; + charteApplication = []; + charteApplications = []; + showAllApplications = []; // tie variable checkboxTrieA_z = "false"; checkboxTrieZ_a = "false"; @@ -317,7 +251,7 @@ export default class ApplicationsView extends Vue { // filter by name this.selectedApplications = this.selectedApplications.filter( - (a) => a.localName.toString().toLowerCase().indexOf(this.filterName.toLowerCase()) >= 0 + (a) => a.localName.toString().toLowerCase().indexOf(this.filterName.toLowerCase()) >= 0 ); // order by date or name @@ -326,14 +260,14 @@ export default class ApplicationsView extends Vue { else this.selectedApplications.sort((a, b) => b.creationDate - a.creationDate).reverse(); if (this.checkboxTrieZ_a === "true" || this.checkboxTrieA_z === "true") { if ( - this.checkboxTrieA_z === "true" && - document.activeElement.parentElement === document.getElementById("checkboxTrieA_z") + this.checkboxTrieA_z === "true" && + document.activeElement.parentElement === document.getElementById("checkboxTrieA_z") ) { this.selectedApplications.sort((a, b) => a.name.localeCompare(b.name)); this.checkboxTrieZ_a = "false"; } else if ( - this.checkboxTrieZ_a === "true" && - document.activeElement.parentElement === document.getElementById("checkboxTrieZ_a") + this.checkboxTrieZ_a === "true" && + document.activeElement.parentElement === document.getElementById("checkboxTrieZ_a") ) { this.selectedApplications.sort((a, b) => a.name.localeCompare(b.name)).reverse(); this.checkboxTrieA_z = "false"; @@ -372,12 +306,38 @@ export default class ApplicationsView extends Vue { return { ...application, localName: - this.internationalisationService.mergeInternationalization(application).localName, + this.internationalisationService.mergeInternationalization(application).localName, }; }); this.selectedApplications = this.applications; - if (this.checkboxDate === "true") + if (this.checkboxDate === "true") { this.selectedApplications.sort((a, b) => b.creationDate - a.creationDate); + } + for (let i = 0; i < this.applications.length; i++) { + let additionalFiles = await this.additionnalFileService.getAdditionalFiles(this.applications[i].name, "__charte__", null); + if (additionalFiles.additionalBinaryFiles.length !== 0) { + for (let i = 0; i < additionalFiles.additionalBinaryFiles.length; i++) { + if (additionalFiles.additionalBinaryFiles[i].additionalBinaryFileType === "__charte__") { + this.charteApplication.push(additionalFiles.additionalBinaryFiles[i]); + } + } + this.charteApplications[this.applications[i].name] = this.charteApplication; + if (this.charteApplications[this.applications[i].name].length === 0) { + this.showAllApplications[this.applications[i].name] = {name: this.applications[i].name, value: true}; + } else if (localStorage.getItem( + "charte_" + this.applicationName + )) { + this.showAllApplications[this.applications[i].name] = { + name: this.applications[i].name, value: localStorage.getItem( + "charte_" + this.applicationName + ) + }; + } else { + this.showAllApplications[this.applications[i].name] = {name: this.applications[i].name, value: false}; + } + this.showAllApplications.push(this.showAllApplications[this.applications[i].name]); + } + } this.loading = false; } @@ -410,6 +370,10 @@ export default class ApplicationsView extends Vue { this.$router.push("/applications/" + application.name + "/additionalFiles"); } + showInfoApplication(application) { + this.$router.push(`/applications/${application.name}`); + } + showModal(name) { this.isSelectedName = name; this.isCardModalActive = true; @@ -444,8 +408,10 @@ export default class ApplicationsView extends Vue { .card-footer { border: none; + .card-footer-item { padding-right: 0px; + .button { padding-right: 10px; padding-left: 10px; @@ -468,6 +434,12 @@ export default class ApplicationsView extends Vue { } } +.btn_info_application { + display: flex; + align-items: center; + justify-content: center; +} + .createApplication { background-color: $dark; color: white; diff --git a/ui/src/views/authorizations/AdditionalFilesAuthorizationInfoView.vue b/ui/src/views/authorizations/AdditionalFilesAuthorizationInfoView.vue index a76a27fdf013b1151dc564619cf5d4bcbf7b7522..061948762d467b7a9e13d021bb69800258f329ae 100644 --- a/ui/src/views/authorizations/AdditionalFilesAuthorizationInfoView.vue +++ b/ui/src/views/authorizations/AdditionalFilesAuthorizationInfoView.vue @@ -225,7 +225,7 @@ export default class AdditionalFilesAuthorizationInfoView extends Vue { new SubMenuPath( this.$t("additionalFilesManagement.additionalFiles").toLowerCase(), () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), - () => this.$router.push("/applications") + () => this.$router.push("/applications/"+this.applicationName) ), new SubMenuPath( this.$t(`additionalFilesAuthorizations.sub-menu-additional-file-authorizations`), diff --git a/ui/src/views/authorizations/DataTypeAuthorizationInfoView.vue b/ui/src/views/authorizations/DataTypeAuthorizationInfoView.vue index e5e82aba9929afb974597c9cd1d5457ad0df7f71..890838e647f2b3369b159befcaf960fc6b82fc6e 100644 --- a/ui/src/views/authorizations/DataTypeAuthorizationInfoView.vue +++ b/ui/src/views/authorizations/DataTypeAuthorizationInfoView.vue @@ -320,7 +320,7 @@ export default class DataTypeAuthorizationInfoView extends Vue { new SubMenuPath( this.$t("dataTypesManagement.data-types").toLowerCase(), () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), - () => this.$router.push("/applications") + () => this.$router.push("/applications/"+this.applicationName) ), new SubMenuPath( this.$t(`dataTypeAuthorizations.sub-menu-data-type-authorizations`), diff --git a/ui/src/views/authorizations/DataTypeAuthorizationsRightsRequestView.vue b/ui/src/views/authorizations/DataTypeAuthorizationsRightsRequestView.vue index 43c608bec0376b3f8d1287b7b02a693f8d598660..ca375610c9091beb250f318bda4c6d9ddf82dca9 100644 --- a/ui/src/views/authorizations/DataTypeAuthorizationsRightsRequestView.vue +++ b/ui/src/views/authorizations/DataTypeAuthorizationsRightsRequestView.vue @@ -269,23 +269,18 @@ export default class DataTypeAuthorizationsRightsRequestView extends Vue { this.init(); this.chosenLocale = this.userPreferencesService.getUserPrefLocale(); this.subMenuPaths = [ - new SubMenuPath( - this.$t("dataTypesManagement.data-types").toLowerCase(), - () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), - () => this.$router.push("/applications") - ), new SubMenuPath( this.$t(`dataTypeAuthorizations.sub-menu-request-authorization`), () => { this.$router.push(`/applications/${this.applicationName}/authorizationsRequest`); }, - () => this.$router.push(`/applications/${this.applicationName}/dataTypes`) + () => this.$router.push(`/applications/${this.applicationName}`) ), new SubMenuPath( this.$t(`dataTypeAuthorizations.sub-menu-new-authorization`), () => {}, () => { - this.$router.push(`/applications/${this.applicationName}/authorizationsRequest/new`); + this.$router.push(`/applications/${this.applicationName}/authorizationsRequest`); } ), ]; diff --git a/ui/src/views/authorizations/DataTypeAuthorizationsView.vue b/ui/src/views/authorizations/DataTypeAuthorizationsView.vue index 7b48e10bf9dfdd0d0f6c85c76a53a2e2956cd40b..1817cd077fd07a4e327f7cc8e8511cde08d0f630 100644 --- a/ui/src/views/authorizations/DataTypeAuthorizationsView.vue +++ b/ui/src/views/authorizations/DataTypeAuthorizationsView.vue @@ -285,7 +285,7 @@ export default class DataTypeAuthorizationsView extends Vue { new SubMenuPath( this.$t("dataTypesManagement.data-types").toLowerCase(), () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), - () => this.$router.push("/applications") + () => this.$router.push("/applications/"+this.applicationName) ), new SubMenuPath( this.$t(`dataTypeAuthorizations.sub-menu-data-type-authorizations`, { diff --git a/ui/src/views/authorizations/ReferencesAuthorizationInfoView.vue b/ui/src/views/authorizations/ReferencesAuthorizationInfoView.vue index 30b443c1a13a3a0c222d3d1489216b82b072be6f..1277dcb7b9f2b21073b608111763e4473ca3f149 100644 --- a/ui/src/views/authorizations/ReferencesAuthorizationInfoView.vue +++ b/ui/src/views/authorizations/ReferencesAuthorizationInfoView.vue @@ -192,7 +192,7 @@ export default class ReferencesAuthorizationInfoView extends Vue { new SubMenuPath( this.$t("referencesManagement.references").toLowerCase(), () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), - () => this.$router.push("/applications") + () => this.$router.push("/applications/"+this.applicationName) ), new SubMenuPath( this.$t(`referencesAuthorizations.sub-menu-reference-authorizations`), diff --git a/ui/src/views/authorizations/RequestAuthorizationManagementView.vue b/ui/src/views/authorizations/RequestAuthorizationManagementView.vue index 7b1ebaa575f5fd530d70b1ab2cf7d16432a6276e..cdb89e4ba18a66a8df70ae47c2fd8f4e4afe3eb3 100644 --- a/ui/src/views/authorizations/RequestAuthorizationManagementView.vue +++ b/ui/src/views/authorizations/RequestAuthorizationManagementView.vue @@ -13,12 +13,23 @@ }) }} </h1> - <div> - <b-select :placeholder="filterStates[filterState].label" v-model="filterState"> + <div class="columns"> + <b-select class="column" :placeholder="filterStates[filterState].label" v-model="filterState"> <option v-for="(option, id) in filterStates" :key="id" :value="id"> {{ option.label }} </option> </b-select> + <div v-if="!canCreateApplication" class="column"> + <b-button + icon-left="users-cog" + type="is-primary" + @click="showRequestRights(application)" + > + {{ $t("dataTypeAuthorizations.showRequests") }} + </b-button> + </div> + </div> + <div class="columns"> <div v-for="(rightsRequest, i) in rightsRequests.rightsRequests" :key="i"> <CollapsibleTree v-if="isVisibleRequest(rightsRequest.setted)" @@ -92,6 +103,7 @@ import PageView from "../common/PageView.vue"; import { ApplicationResult } from "@/model/ApplicationResult"; import { Button } from "@/model/Button"; import CollapsibleTree from "@/components/common/CollapsibleTree.vue"; +import {LoginService} from "@/services/rest/LoginService"; @Component({ components: { PageView, SubMenu, CollapsibleTree }, @@ -102,6 +114,8 @@ export default class RequestAuthorizationManagementView extends Vue { toList; internationalisationService = InternationalisationService.INSTANCE; alertService = AlertService.INSTANCE; + canCreateApplication = + LoginService.INSTANCE.getAuthenticatedUser().authorizedForApplicationCreation; requestRightsService = RequestRightsService.INSTANCE; applicationService = ApplicationService.INSTANCE; @@ -144,13 +158,18 @@ export default class RequestAuthorizationManagementView extends Vue { }, ]; + async showRequestRights(application) { + this.$router.push(`/applications/${application.name}/authorizationsRequest/new`); + return false; + } + created() { this.init(); this.subMenuPaths = [ new SubMenuPath( this.$t("requestAuthorization.request").toLowerCase(), () => {}, - () => this.$router.push("/applications") + () => this.$router.push("/applications/"+this.applicationName) ), ]; } diff --git a/ui/src/views/datatype/DataTypesManagementView.vue b/ui/src/views/datatype/DataTypesManagementView.vue index 5f2d1cc7acdc8b2eba0d35ee1e3bf4612eb5c45d..1fa57fefe040372a8779f243a5fd80991693bc2e 100644 --- a/ui/src/views/datatype/DataTypesManagementView.vue +++ b/ui/src/views/datatype/DataTypesManagementView.vue @@ -218,8 +218,8 @@ export default class DataTypesManagementView extends Vue { this.subMenuPaths = [ new SubMenuPath( this.$t("dataTypesManagement.data-types").toLowerCase(), - () => {}, - () => this.$router.push("/applications") + () => this.$router.push(`/applications/${this.applicationName}/dataTypes`), + () => this.$router.push("/applications/"+this.applicationName) ), ]; diff --git a/ui/src/views/documentation/HelpView.vue b/ui/src/views/documentation/HelpView.vue index 9db19966b34726514e645d8457c6fa187d0cebcf..2d31e96a065fbbec42c154b6e6c20acc111134bc 100644 --- a/ui/src/views/documentation/HelpView.vue +++ b/ui/src/views/documentation/HelpView.vue @@ -1,4 +1,3 @@ -> <template> <PageView> <h1 class="title main-title">{{ $t("titles.aide") }}</h1> diff --git a/ui/src/views/references/ReferencesManagementView.vue b/ui/src/views/references/ReferencesManagementView.vue index ea506c11ba22b8951f9b9a8eab8c67ad91c523e9..e4be22989c40978eab469305c918ad48fb97b600 100644 --- a/ui/src/views/references/ReferencesManagementView.vue +++ b/ui/src/views/references/ReferencesManagementView.vue @@ -137,7 +137,7 @@ export default class ReferencesManagementView extends Vue { new SubMenuPath( this.$t("referencesManagement.references").toLowerCase(), () => this.$router.push(`/applications/${this.applicationName}/references`), - () => this.$router.push(`/applications`) + () => this.$router.push(`/applications/${this.applicationName}`) ), ]; this.init();