From 03a7098c378a8a6f3df9d0d1da4ada9b55fc2217 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 2 Jun 2017 15:30:45 +0300 Subject: [PATCH 001/702] OCE-331 Create more efficient activeAwardsCount endpoint to tackle infinite M&E scrollable section of frequent tenderers --- .../web/rest/controller/FrequentTenderersController.java | 6 ++++-- .../rest/controller/FrequentTenderersControllerTest.java | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java index 663cbcf67..8ea952609 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java @@ -104,8 +104,10 @@ public List activeAwardsCount(@ModelAttribute @Valid final YearFilterP match(where("awards.status").is("active") .andOperator(getYearDefaultFilterCriteria(filter, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), - group().count().as("cnt"), - project("cnt").andExclude(Fields.UNDERSCORE_ID) + unwind("awards.suppliers"), + group("awards.suppliers._id").count().as("cnt"), + project("cnt").and(Fields.UNDERSCORE_ID).as("supplierId") + .andExclude(Fields.UNDERSCORE_ID) ); AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersControllerTest.java index 94a91321f..f1aa5ac42 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersControllerTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersControllerTest.java @@ -30,8 +30,9 @@ public void activeAwardsCountTest() throws Exception { final List frequentTenderers = frequentTenderersController .activeAwardsCount(new YearFilterPagingRequest()); - Assert.assertEquals(1, frequentTenderers.size()); - Assert.assertEquals(2, frequentTenderers.get(0).get("cnt")); + Assert.assertEquals(2, frequentTenderers.size()); + Assert.assertEquals(1, frequentTenderers.get(0).get("cnt")); + Assert.assertEquals(1, frequentTenderers.get(1).get("cnt")); } } From 9d653d9f07ed75772161ca11736a3106696c5e9d Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 6 Jun 2017 18:03:45 +0300 Subject: [PATCH 002/702] OCE-309 Parent-child translations --- .../controller/TranslationController.java | 32 +++++++++ .../ocds/web/spring/TranslationService.java | 70 ++++++++++++++++--- .../resources/allowedApiEndpoints.properties | 3 +- 3 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 web/src/main/java/org/devgateway/ocds/web/rest/controller/TranslationController.java diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/TranslationController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/TranslationController.java new file mode 100644 index 000000000..5c8e2ad70 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/TranslationController.java @@ -0,0 +1,32 @@ +package org.devgateway.ocds.web.rest.controller; + +import io.swagger.annotations.ApiOperation; +import java.io.IOException; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.devgateway.ocds.web.spring.TranslationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Created by mpostelnicu on 16-May-17. + */ +@RestController +public class TranslationController { + + @Autowired + private TranslationService translationService; + + @ApiOperation(value = "Returns a json with the merged translations, based on language specified") + @RequestMapping(value = "/api/translations/{language}", method = {RequestMethod.GET, RequestMethod.POST}) + @ResponseBody + public Map translations(final HttpServletResponse response, + @PathVariable String language) throws IOException { + return translationService.getAllTranslationsForLanguage(language); + } + +} diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java b/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java index 73f20c1f1..59fe76403 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java @@ -4,7 +4,13 @@ import java.io.IOException; import java.io.InputStream; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.apache.log4j.Logger; +import org.reflections.Reflections; +import org.reflections.scanners.ResourcesScanner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; @@ -15,26 +21,65 @@ @Service public class TranslationService { + private static final String TRANSLATION_PKG_NAME = "public.languages"; + + protected static Logger logger = Logger.getLogger(TranslationService.class); + + @Autowired private ObjectMapper objectMapper; private Map> translations = new ConcurrentHashMap<>(); - + /** + * Loads all translation files for the given language in the hashmap. + * + * @param language + */ private void ensureLanguageLoaded(String language) { + Assert.notNull(language, "Language must not be null!"); + Assert.isTrue(language.matches("^[a-z]{2}_[A-Z]{2}$"), + "Language string must comply with RFC5646 standard!"); + if (translations.containsKey(language)) { return; } + Set fileNames = getAllTranslationFileNamesForLanguage(language); + Assert.notEmpty(fileNames, "No translations found for language " + language); - try { - InputStream stream = this.getClass().getResourceAsStream("/public/languages/" + language + ".json"); - Map map = objectMapper.readValue(stream, ConcurrentHashMap.class); - - - translations.put(language, map); + fileNames.forEach(file -> + loadLanguageFromTranslationFile(language, file)); + logger.info("Loaded translations for language " + language + " from the following files " + fileNames); + } + /** + * Returns all the translation files for a specific language, sorted naturally by file name + * The trnaslation files have to start with @param language and end with ".json" + * + * @param language + * @return + */ + private Set getAllTranslationFileNamesForLanguage(String language) { + return new TreeSet(new Reflections(TRANSLATION_PKG_NAME, new ResourcesScanner()). + getResources(Pattern.compile(language + "(.*).json"))).descendingSet(); + } + /** + * Loads all translation keys from the given file. If the keys already exist, they will be overwritten. + * + * @param language + * @param translationFileName + */ + private void loadLanguageFromTranslationFile(String language, String translationFileName) { + try { + InputStream stream = this.getClass().getResourceAsStream("/" + translationFileName); + Map map = objectMapper.readValue(stream, ConcurrentHashMap.class); + if (translations.containsKey(language)) { + translations.get(language).putAll(map); + } else { + translations.put(language, map); + } } catch (IOException e) { e.printStackTrace(); } @@ -49,11 +94,20 @@ private void ensureLanguageLoaded(String language) { * @return */ public String getValue(String language, String key) { - Assert.notNull(language, "Language must not be null!"); Assert.notNull(key, "Key must not be null!"); ensureLanguageLoaded(language); return translations.get(language).get(key); } + /** + * Returns the entire map of key-value translations for given language + * + * @param language + * @return + */ + public Map getAllTranslationsForLanguage(String language) { + ensureLanguageLoaded(language); + return translations.get(language); + } } diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties index cead00365..846827894 100644 --- a/web/src/main/resources/allowedApiEndpoints.properties +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -67,4 +67,5 @@ allowedApiEndpoints=/api/tenderPriceByProcurementMethod**,\ /api/tendersByLocation**,\ /api/planningByLocation**,\ /api/awardsByLocation**,\ -/api/activeAwardsCount** \ No newline at end of file +/api/activeAwardsCount**,\ +/api/translations/** \ No newline at end of file From 74d01303a02031c5c9f659c53c9f1f521c85a6d1 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 6 Jun 2017 18:51:10 +0300 Subject: [PATCH 003/702] OCE-309 Parent-child translations - use natural sort of names and renamed en_US to en_US-1 to be consistent --- web/public/languages/{en_US.json => en_US-1.json} | 0 .../java/org/devgateway/ocds/web/spring/TranslationService.java | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename web/public/languages/{en_US.json => en_US-1.json} (100%) diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US-1.json similarity index 100% rename from web/public/languages/en_US.json rename to web/public/languages/en_US-1.json diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java b/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java index 59fe76403..cf8072861 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/TranslationService.java @@ -62,7 +62,7 @@ private void ensureLanguageLoaded(String language) { */ private Set getAllTranslationFileNamesForLanguage(String language) { return new TreeSet(new Reflections(TRANSLATION_PKG_NAME, new ResourcesScanner()). - getResources(Pattern.compile(language + "(.*).json"))).descendingSet(); + getResources(Pattern.compile(language + "(.*).json"))); } /** From 7a39ee4ff0603265ee49fb7a6159739648295c80 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 9 Jun 2017 00:44:47 +0300 Subject: [PATCH 004/702] OCE-400 The export link should've been wrapped --- ui/oce/index.jsx | 14 +++++++++----- ui/oce/style.less | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 02893a1a3..8d15a4229 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -309,11 +309,15 @@ class OCApp extends React.Component{ .addSearch('monthly', true); } - return - - {this.t('export:export')} - - + return ( + + ) } toggleDashboardSwitcher(e){ diff --git a/ui/oce/style.less b/ui/oce/style.less index eff6f84e0..d651633df 100644 --- a/ui/oce/style.less +++ b/ui/oce/style.less @@ -64,7 +64,7 @@ } } -a.filters{ +.filters a.export-link{ text-decoration: none; color: white; } From 81c1637560f450a984a2be66fe9dc02663ddecef Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 12 Jun 2017 20:59:37 +0300 Subject: [PATCH 005/702] OCVN-401 Automate vietnam import routine - created shadow template and mongo database that can be used to import data in a different place than main database --- .../mongo/reader/ReleaseRowImporter.java | 2 +- .../mongo/reader/XMLFileImport.java | 2 +- .../{ => main}/ClassificationRepository.java | 2 +- .../{ => main}/DefaultLocationRepository.java | 2 +- .../{ => main}/FlaggedReleaseRepository.java | 2 +- .../GenericOrganizationRepository.java | 2 +- .../{ => main}/GenericReleaseRepository.java | 2 +- .../{ => main}/OrganizationRepository.java | 2 +- .../{ => main}/RecordRepository.java | 2 +- .../{ => main}/ReleaseRepository.java | 2 +- .../ShadowClassificationRepository.java | 7 ++ .../ShadowFlaggedReleaseRepository.java | 12 +++ .../shadow/ShadowOrganizationRepository.java | 14 +++ .../shadow/ShadowRecordRepository.java | 7 ++ .../shadow/ShadowReleaseRepository.java | 14 +++ .../mongo/spring/ReleaseCompilerService.java | 4 +- .../mongo/spring/ReleaseRecordMonitor.java | 7 +- .../mongo/spring/json/ReleaseJsonImport.java | 2 +- .../spring/json/ReleasePackageJsonImport.java | 2 +- .../persistence/mongo/application.properties | 4 +- .../mongo/repository/CustomerRepository.java | 15 --- ...> AbstractMongoDatabaseConfiguration.java} | 93 +++++++++---------- .../spring/MongoDatabaseConfiguration.java | 34 +++++++ .../spring/MongoPersistenceApplication.java | 9 +- .../mongo/spring/MongoTemplateConfig.java | 41 ++++++++ .../ShadowMongoDatabaseConfiguration.java | 34 +++++++ .../mongo/reader/XMLFileImportTest.java | 2 +- .../spring/json/ReleaseJsonImportTest.java | 2 +- .../json/ReleasePackageJsonImportTest.java | 2 +- .../test/OrganizationRepositoryTest.java | 2 +- .../mongo/test/RecordRepositoryTest.java | 4 +- .../mongo/test/ReleaseRepositoryTest.java | 2 +- .../web/rest/controller/OcdsController.java | 2 +- .../AbstractOrganizationSearchController.java | 2 +- .../selector/BidTypesSearchController.java | 2 +- .../ocds/web/spring/OCDSPopulatorService.java | 6 +- .../web/spring/ReleaseFlaggingService.java | 2 +- .../controller/SaveCustomerController.java | 41 -------- .../AbstractEndPointControllerTest.java | 2 +- ...ashboardIndicatorsStatsControllerTest.java | 2 +- ...tionRiskDashboardTablesControllerTest.java | 2 +- .../controller/OrganizationEndpointsTest.java | 2 +- .../rest/controller/ReleaseExportTest.java | 2 +- .../ReleaseFlaggingServiceTest.java | 2 +- .../AbstractExcelControllerTest.java | 2 +- 45 files changed, 250 insertions(+), 152 deletions(-) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/ClassificationRepository.java (93%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/DefaultLocationRepository.java (94%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/FlaggedReleaseRepository.java (76%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/GenericOrganizationRepository.java (89%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/GenericReleaseRepository.java (90%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/OrganizationRepository.java (93%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/RecordRepository.java (79%) rename persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/{ => main}/ReleaseRepository.java (74%) create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java delete mode 100644 persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java rename persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/{MongoTemplateConfiguration.java => AbstractMongoDatabaseConfiguration.java} (64%) create mode 100644 persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java delete mode 100644 web/src/main/java/org/devgateway/toolkit/web/rest/controller/SaveCustomerController.java diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java index 213cf35d7..9bdeeb93d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.reader; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.ImportService; import java.text.ParseException; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java index 7da219796..86ed4533b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImport.java @@ -4,7 +4,7 @@ import org.apache.commons.digester3.binder.AbstractRulesModule; import org.apache.commons.digester3.binder.DigesterLoader; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.xml.sax.SAXException; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java similarity index 93% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java index 829c07166..affdbb5c4 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ClassificationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ClassificationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Classification; import org.springframework.cache.annotation.CacheConfig; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java similarity index 94% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java index a1e9b9881..34c34d5b7 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/DefaultLocationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/DefaultLocationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.DefaultLocation; import org.springframework.cache.annotation.CacheConfig; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java similarity index 76% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java index e3c7ee9d5..bb2c2fa0c 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/FlaggedReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/FlaggedReleaseRepository.java @@ -1,7 +1,7 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.FlaggedRelease; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java similarity index 89% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java index 6e05f40b1..bf147c73b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericOrganizationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericOrganizationRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Organization; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java similarity index 90% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java index 6e1c3f166..3e676fb56 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/GenericReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/GenericReleaseRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Release; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java similarity index 93% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java index c6f5d61e5..3b2a96240 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/OrganizationRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/OrganizationRepository.java @@ -1,7 +1,7 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import java.util.Collection; import java.util.List; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java similarity index 79% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java index 8c1f5be09..388d6661a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/RecordRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/RecordRepository.java @@ -1,4 +1,4 @@ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Record; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java similarity index 74% rename from persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java rename to persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java index 1b761ba0a..44a55500f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/ReleaseRepository.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/main/ReleaseRepository.java @@ -1,7 +1,7 @@ /** * */ -package org.devgateway.ocds.persistence.mongo.repository; +package org.devgateway.ocds.persistence.mongo.repository.main; import org.devgateway.ocds.persistence.mongo.Release; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java new file mode 100644 index 000000000..5eb492bf1 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowClassificationRepository.java @@ -0,0 +1,7 @@ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.ClassificationRepository; + +public interface ShadowClassificationRepository extends ClassificationRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java new file mode 100644 index 000000000..ab5e42968 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowFlaggedReleaseRepository.java @@ -0,0 +1,12 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +/** + * @author mpostelnicu + * + */ +public interface ShadowFlaggedReleaseRepository extends ShadowReleaseRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java new file mode 100644 index 000000000..62f1704b2 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowOrganizationRepository.java @@ -0,0 +1,14 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; + +/** + * @author mpostelnicu + * + */ +public interface ShadowOrganizationRepository extends OrganizationRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java new file mode 100644 index 000000000..ab8d44ee2 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowRecordRepository.java @@ -0,0 +1,7 @@ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; + +public interface ShadowRecordRepository extends RecordRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java new file mode 100644 index 000000000..ace7f0f39 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/repository/shadow/ShadowReleaseRepository.java @@ -0,0 +1,14 @@ +/** + * + */ +package org.devgateway.ocds.persistence.mongo.repository.shadow; + +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; + +/** + * @author mpostelnicu + * + */ +public interface ShadowReleaseRepository extends ReleaseRepository { + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java index 94faebf19..6ec96981b 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseCompilerService.java @@ -22,8 +22,8 @@ import org.devgateway.ocds.persistence.mongo.Tag; import org.devgateway.ocds.persistence.mongo.merge.Merge; import org.devgateway.ocds.persistence.mongo.merge.MergeStrategy; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java index 426fdc97c..b016e0e8a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ReleaseRecordMonitor.java @@ -9,14 +9,11 @@ import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.AfterReturning; -import org.aspectj.lang.annotation.Aspect; import org.devgateway.ocds.persistence.mongo.Record; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; /** * @author mpostelnicu diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java index 21ce6e527..da5a65446 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImport.java @@ -2,7 +2,7 @@ import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json2object.JsonToObject; import org.devgateway.ocds.persistence.mongo.spring.json2object.ReleaseJsonToObject; import org.springframework.transaction.annotation.Transactional; diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java index 1a3e96ef5..1490b80f4 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImport.java @@ -3,7 +3,7 @@ import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json2object.JsonToObject; import org.devgateway.ocds.persistence.mongo.spring.json2object.ReleasePackageJsonToObject; diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties index 6aa0df64e..76d303475 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties @@ -12,4 +12,6 @@ security.basic.enabled=false server.port = 8090 -spring.data.mongodb.uri=mongodb://localhost:27017/ocexplorer +spring.data.mongodb.host=localhost +spring.data.mongodb.port=27017 +spring.data.mongodb.database=ocexplorer diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java deleted file mode 100644 index 3c9b15607..000000000 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/repository/CustomerRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.devgateway.toolkit.persistence.mongo.repository; - -import java.util.List; - -import org.devgateway.toolkit.persistence.mongo.dao.Customer; -import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.repository.query.Param; - -public interface CustomerRepository extends MongoRepository { - - List findByFirstName(@Param("firstName") String firstName); - - List findByLastName(@Param("lastName") String lastName); - -} \ No newline at end of file diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java similarity index 64% rename from persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java rename to persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java index 77b3e8e70..f3414c1e7 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java @@ -1,5 +1,8 @@ package org.devgateway.toolkit.persistence.mongo.spring; +import java.io.IOException; +import java.net.URL; +import javax.annotation.PostConstruct; import org.apache.commons.io.IOUtils; import org.devgateway.ocds.persistence.mongo.DefaultLocation; import org.devgateway.ocds.persistence.mongo.Organization; @@ -7,9 +10,6 @@ import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.ScriptOperations; @@ -18,49 +18,44 @@ import org.springframework.data.mongodb.core.script.ExecutableMongoScript; import org.springframework.data.mongodb.core.script.NamedMongoScript; -import javax.annotation.PostConstruct; -import java.io.IOException; -import java.net.URL; - -@Configuration -public class MongoTemplateConfiguration { +public abstract class AbstractMongoDatabaseConfiguration { - private final Logger logger = LoggerFactory.getLogger(MongoTemplateConfiguration.class); + protected abstract Logger getLogger(); - @Autowired - private MongoTemplate mongoTemplate; + protected abstract MongoTemplate getTemplate(); public void createMandatoryImportIndexes() { //mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("planning.budget.projectID", Direction.ASC)); //mongoTemplate.indexOps(Location.class).ensureIndex(new Index().on("description", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex(new Index().on("identifier._id", Direction.ASC)); - mongoTemplate.indexOps(Organization.class) + getTemplate().indexOps(Organization.class).ensureIndex(new Index().on("identifier._id", Direction.ASC)); + getTemplate().indexOps(Organization.class) .ensureIndex(new Index().on("additionalIdentifiers._id", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex( + getTemplate().indexOps(Organization.class).ensureIndex( new Index().on("roles", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex(new Index().on("name", Direction.ASC).unique()); - mongoTemplate.indexOps(DefaultLocation.class).ensureIndex(new Index().on("description", Direction.ASC)); - logger.info("Added mandatory Mongo indexes"); + getTemplate().indexOps(Organization.class).ensureIndex(new Index().on("name", Direction.ASC).unique()); + getTemplate().indexOps(DefaultLocation.class).ensureIndex(new Index().on("description", Direction.ASC)); + getLogger().info("Added mandatory Mongo indexes"); } public void createCorruptionFlagsIndexes() { - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.totalFlagged", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("flags.totalFlagged", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.flaggedStats.type", Direction.ASC) + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("flags.flaggedStats.type", Direction.ASC) .on("flags.flaggedStats.count", Direction.ASC) ); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("flags.eligibleStats.type", Direction.ASC) + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("flags.eligibleStats.type", Direction.ASC) .on("flags.eligibleStats.count", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I038_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I007_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I004_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I077_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I180_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I019_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I002_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I085_VALUE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I171_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I038_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I007_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I004_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I077_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I180_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I019_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I002_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I085_VALUE, Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on(FlagsConstants.I171_VALUE, Direction.ASC)); + getLogger().info("Added corruption flags indexes"); } @PostConstruct @@ -74,37 +69,37 @@ public void createPostImportStructures() { createCorruptionFlagsIndexes(); // initialize some extra indexes - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("ocid", Direction.ASC).unique()); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("ocid", Direction.ASC).unique()); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.procurementMethod", Direction.ASC)); - mongoTemplate.indexOps(Release.class) + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("tender.procurementMethod", Direction.ASC)); + getTemplate().indexOps(Release.class) .ensureIndex(new Index().on("tender.procurementMethodRationale", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.status", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.status", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.suppliers._id", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.date", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("awards.value.amount", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.value.amount", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.numberOfTenderers", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index().on("tender.submissionMethod", Direction.ASC)); - mongoTemplate.indexOps(Release.class) + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("tender.status", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("awards.status", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("awards.suppliers._id", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("awards.date", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("awards.value.amount", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("tender.value.amount", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("tender.numberOfTenderers", Direction.ASC)); + getTemplate().indexOps(Release.class).ensureIndex(new Index().on("tender.submissionMethod", Direction.ASC)); + getTemplate().indexOps(Release.class) .ensureIndex(new Index().on(MongoConstants.FieldNames.TENDER_PERIOD_START_DATE, Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index() + getTemplate().indexOps(Release.class).ensureIndex(new Index() .on(MongoConstants.FieldNames.TENDER_PERIOD_END_DATE, Direction.ASC)); - mongoTemplate.indexOps(Release.class) + getTemplate().indexOps(Release.class) .ensureIndex(new Index().on("tender.items.classification._id", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index(). + getTemplate().indexOps(Release.class).ensureIndex(new Index(). on("tender.items.deliveryLocation._id", Direction.ASC)); - mongoTemplate.indexOps(Release.class).ensureIndex(new Index(). + getTemplate().indexOps(Release.class).ensureIndex(new Index(). on("tender.items.deliveryLocation.geometry.coordinates", Direction.ASC)); - mongoTemplate.indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder().onField("name") + getTemplate().indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder().onField("name") .onField("id").onField("additionalIdentifiers._id").build()); - logger.info("Added extra Mongo indexes"); + getLogger().info("Added extra Mongo indexes"); - ScriptOperations scriptOps = mongoTemplate.scriptOps(); + ScriptOperations scriptOps = getTemplate().scriptOps(); // add script to calculate the percentiles endpoint URL scriptFile = getClass().getResource("/tenderBidPeriodPercentilesMongo.js"); diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java new file mode 100644 index 000000000..8640c075f --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoDatabaseConfiguration.java @@ -0,0 +1,34 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@EnableMongoRepositories( + basePackages = {"org.devgateway.ocds.persistence.mongo.repository.main"}, + mongoTemplateRef = "mongoTemplate" +) +public class MongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(MongoDatabaseConfiguration.class); + + @Autowired + private MongoTemplate mongoTemplate; + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected MongoTemplate getTemplate() { + return mongoTemplate; + } +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java index c688a35bc..739d08d15 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java @@ -12,6 +12,9 @@ package org.devgateway.toolkit.persistence.mongo.spring; import com.mongodb.DBObject; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @@ -22,14 +25,9 @@ import org.springframework.data.mongodb.config.EnableMongoAuditing; import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; -import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.List; - /** * Run this application only when you need access to Spring Data JPA but without * Wicket frontend @@ -39,7 +37,6 @@ @SpringBootApplication @ComponentScan("org.devgateway") @PropertySource("classpath:/org/devgateway/toolkit/persistence/mongo/application.properties") -@EnableMongoRepositories(basePackages = "org.devgateway") @EnableMongoAuditing @EnableCaching public class MongoPersistenceApplication { diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java new file mode 100644 index 000000000..eaefd4c64 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java @@ -0,0 +1,41 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import com.mongodb.MongoClient; +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +public class MongoTemplateConfig { + + @Autowired + private MongoProperties properties; + + @Bean(autowire = Autowire.BY_NAME, name = "mongoTemplate") + public MongoTemplate mongoTemplate() throws Exception { + return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(properties.getHost(), properties.getPort()), + properties.getDatabase())); + } + + /** + * Creates a shadow template configuration by adding "-shadow" as postfix of database name. + * This is used to replicate the entire database structure in a shadow/temporary database location + * + * @return + * @throws Exception + */ + @Bean(autowire = Autowire.BY_NAME, name = "shadowMongoTemplate") + public MongoTemplate shadowMongoTemplate() throws Exception { + return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(properties.getHost(), properties.getPort()), + properties.getDatabase() + "-shadow")); + } + + +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java new file mode 100644 index 000000000..019f20bf3 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java @@ -0,0 +1,34 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@EnableMongoRepositories( + basePackages = {"org.devgateway.ocds.persistence.mongo.repository.shadow"}, + mongoTemplateRef = "shadowMongoTemplate" +) +public class ShadowMongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(ShadowMongoDatabaseConfiguration.class); + + @Autowired + private MongoTemplate shadowMongoTemplate; + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected MongoTemplate getTemplate() { + return shadowMongoTemplate; + } +} diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java index 4d1de5485..53391d066 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/reader/XMLFileImportTest.java @@ -2,7 +2,7 @@ import org.apache.commons.digester3.binder.AbstractRulesModule; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java index 856a1e6c0..9176c3852 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.spring.json; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java index e5ea8f6df..b14076c6d 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleasePackageJsonImportTest.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.spring.json; import org.devgateway.ocds.persistence.mongo.*; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.After; import org.junit.Assert; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java index b64807c08..8053439c5 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/OrganizationRepositoryTest.java @@ -6,7 +6,7 @@ import org.devgateway.ocds.persistence.mongo.ContactPoint; import org.devgateway.ocds.persistence.mongo.Identifier; import org.devgateway.ocds.persistence.mongo.Organization; -import org.devgateway.ocds.persistence.mongo.repository.OrganizationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; import org.junit.Before; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java index 50cb15fe6..61d4a8566 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/RecordRepositoryTest.java @@ -8,8 +8,8 @@ import org.devgateway.ocds.persistence.mongo.Planning; import org.devgateway.ocds.persistence.mongo.Record; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.RecordRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.RecordRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.ReleaseCompilerService; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java index 4f690dc78..7cca0e72f 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/test/ReleaseRepositoryTest.java @@ -1,7 +1,7 @@ package org.devgateway.ocds.persistence.mongo.test; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.AbstractMongoTest; import org.junit.Assert; import org.junit.Test; diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index 007e02359..e54503760 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -16,7 +16,7 @@ import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json.Views; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.beans.factory.annotation.Autowired; diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/AbstractOrganizationSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/AbstractOrganizationSearchController.java index c67f276b4..dc5ec04f7 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/AbstractOrganizationSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/AbstractOrganizationSearchController.java @@ -4,7 +4,7 @@ import java.util.regex.Pattern; import javax.validation.Valid; import org.devgateway.ocds.persistence.mongo.Organization; -import org.devgateway.ocds.persistence.mongo.repository.OrganizationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; import org.devgateway.ocds.web.rest.controller.GenericOCDSController; import org.devgateway.ocds.web.rest.controller.request.OrganizationSearchRequest; import org.springframework.beans.factory.annotation.Autowired; diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BidTypesSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BidTypesSearchController.java index 2665bc840..4782732cc 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BidTypesSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BidTypesSearchController.java @@ -12,7 +12,7 @@ package org.devgateway.ocds.web.rest.controller.selector; import org.devgateway.ocds.persistence.mongo.Classification; -import org.devgateway.ocds.persistence.mongo.repository.ClassificationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ClassificationRepository; import org.devgateway.ocds.web.rest.controller.GenericOCDSController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/OCDSPopulatorService.java b/web/src/main/java/org/devgateway/ocds/web/spring/OCDSPopulatorService.java index 48e0ed39b..2624a37e7 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/OCDSPopulatorService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/OCDSPopulatorService.java @@ -6,9 +6,9 @@ import org.devgateway.ocds.persistence.mongo.Identifiable; import org.devgateway.ocds.persistence.mongo.Organization; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ClassificationRepository; -import org.devgateway.ocds.persistence.mongo.repository.OrganizationRepository; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ClassificationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.toolkit.persistence.mongo.spring.MongoUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/ReleaseFlaggingService.java b/web/src/main/java/org/devgateway/ocds/web/spring/ReleaseFlaggingService.java index d87f6670d..26e1a9fd2 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/ReleaseFlaggingService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/ReleaseFlaggingService.java @@ -7,7 +7,7 @@ import org.devgateway.ocds.persistence.mongo.FlaggedRelease; import org.devgateway.ocds.persistence.mongo.flags.AbstractFlaggedReleaseFlagProcessor; import org.devgateway.ocds.persistence.mongo.flags.ReleaseFlags; -import org.devgateway.ocds.persistence.mongo.repository.FlaggedReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.web.flags.release.ReleaseFlagI002Processor; import org.devgateway.ocds.web.flags.release.ReleaseFlagI007Processor; import org.devgateway.ocds.web.flags.release.ReleaseFlagI019Processor; diff --git a/web/src/main/java/org/devgateway/toolkit/web/rest/controller/SaveCustomerController.java b/web/src/main/java/org/devgateway/toolkit/web/rest/controller/SaveCustomerController.java deleted file mode 100644 index 7105d7a49..000000000 --- a/web/src/main/java/org/devgateway/toolkit/web/rest/controller/SaveCustomerController.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Development Gateway, Inc and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the MIT License (MIT) - * which accompanies this distribution, and is available at - * https://opensource.org/licenses/MIT - * - * Contributors: - * Development Gateway - initial API and implementation - *******************************************************************************/ -package org.devgateway.toolkit.web.rest.controller; - -import java.util.List; - -import org.devgateway.toolkit.persistence.mongo.dao.Customer; -import org.devgateway.toolkit.persistence.mongo.repository.CustomerRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * - * @author mpostelnicu - * - */ -@RestController -public class SaveCustomerController { - - @Autowired - private CustomerRepository customerRepository; - - @RequestMapping("/createCustomer") - public List createCustomer() { - customerRepository.save(new Customer("Alice", "Smith")); - customerRepository.save(new Customer("Bob", "Smith")); - - List findAll = customerRepository.findAll(); - return findAll; - } -} \ No newline at end of file diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/AbstractEndPointControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/AbstractEndPointControllerTest.java index 45d2c68ac..34210ac16 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/AbstractEndPointControllerTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/AbstractEndPointControllerTest.java @@ -2,7 +2,7 @@ import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json.JsonImportPackage; import org.devgateway.ocds.persistence.mongo.spring.json.ReleasePackageJsonImport; import org.devgateway.toolkit.web.AbstractWebTest; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardIndicatorsStatsControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardIndicatorsStatsControllerTest.java index 1a338c72e..2172526ca 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardIndicatorsStatsControllerTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardIndicatorsStatsControllerTest.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.DBObject; import org.devgateway.ocds.persistence.mongo.flags.FlagType; -import org.devgateway.ocds.persistence.mongo.repository.FlaggedReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.devgateway.ocds.web.spring.ReleaseFlaggingService; import org.junit.Assert; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesControllerTest.java index 55d097447..95bd392cb 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesControllerTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesControllerTest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.DBObject; -import org.devgateway.ocds.persistence.mongo.repository.FlaggedReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.devgateway.ocds.web.spring.ReleaseFlaggingService; import org.junit.Assert; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java index 22b83853a..ab465c2eb 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java @@ -4,7 +4,7 @@ import org.devgateway.ocds.persistence.mongo.ContactPoint; import org.devgateway.ocds.persistence.mongo.Identifier; import org.devgateway.ocds.persistence.mongo.Organization; -import org.devgateway.ocds.persistence.mongo.repository.OrganizationRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.OrganizationRepository; import org.devgateway.ocds.web.rest.controller.request.OrganizationSearchRequest; import org.devgateway.ocds.web.rest.controller.selector.BuyerSearchController; import org.devgateway.ocds.web.rest.controller.selector.OrganizationSearchController; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseExportTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseExportTest.java index 3c784ff0d..8921e70d7 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseExportTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseExportTest.java @@ -6,7 +6,7 @@ import java.io.File; import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.OcdsSchemaValidatorService; import org.devgateway.ocds.persistence.mongo.spring.json.JsonImport; import org.devgateway.ocds.persistence.mongo.spring.json.ReleaseJsonImport; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java index d495a7b86..ec12ef2de 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java @@ -6,7 +6,7 @@ import org.devgateway.ocds.persistence.mongo.FlaggedRelease; import org.devgateway.ocds.persistence.mongo.flags.FlagType; import org.devgateway.ocds.persistence.mongo.flags.ReleaseFlags; -import org.devgateway.ocds.persistence.mongo.repository.FlaggedReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.web.spring.ReleaseFlaggingService; import org.junit.Assert; import org.junit.Before; diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/excelchart/AbstractExcelControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/excelchart/AbstractExcelControllerTest.java index 08f222d7f..2a08bf8ea 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/excelchart/AbstractExcelControllerTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/excelchart/AbstractExcelControllerTest.java @@ -4,7 +4,7 @@ import java.util.List; import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.Release; -import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json.JsonImportPackage; import org.devgateway.ocds.persistence.mongo.spring.json.ReleasePackageJsonImport; import org.devgateway.ocds.web.rest.controller.request.LangGroupingFilterPagingRequest; From 5d149f96c57e8060aff57909fc3984baa7809bcb Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 13 Jun 2017 14:35:56 +0300 Subject: [PATCH 006/702] OCVN-401 added orgreference to support validation --- .../release-schema-all-required.json | 69 ++++++++++++++++++ .../src/main/resources/release-schema.json | 70 +++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/persistence-mongodb/src/main/resources/release-schema-all-required.json b/persistence-mongodb/src/main/resources/release-schema-all-required.json index acae98b55..c2c8cbe09 100644 --- a/persistence-mongodb/src/main/resources/release-schema-all-required.json +++ b/persistence-mongodb/src/main/resources/release-schema-all-required.json @@ -697,6 +697,75 @@ } } }, + "OrganizationReference": { + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the party being referenced. This must match the name of an entry in the parties section.", + "title": "Organization name", + "minLength": 1 + }, + "id": { + "type": [ + "string", + "integer" + ], + "description": "The id of the party being referenced. This must match the id of an entry in the parties section.", + "title": "Organization ID", + "minLength": 1 + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and detailed legal identifier information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Identifier" + }, + "address": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and address information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Address", + "description": "(Deprecated outside the parties section)", + "title": "Address" + }, + "additionalIdentifiers": { + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and additional identifiers for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "items": { + "$ref": "#/definitions/Identifier" + }, + "title": "Additional identifiers", + "uniqueItems": true, + "wholeListMerge": true, + "description": "(Deprecated outside the parties section) A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier." + }, + "contactPoint": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and contact point information for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/ContactPoint", + "description": "(Deprecated outside the parties section)", + "title": "Contact point" + } + }, + "required": [ + "id", "name", "identifier", "address", "additionalIdentifiers", "contactPoint" + ], + "type": "object", + "description": "The id and name of the party being referenced. Used to cross-reference to the parties section", + "title": "Organization reference" + }, "Organization": { "title": "Organization", "description": "An organization.", diff --git a/persistence-mongodb/src/main/resources/release-schema.json b/persistence-mongodb/src/main/resources/release-schema.json index 4fa58b106..27d3a950e 100644 --- a/persistence-mongodb/src/main/resources/release-schema.json +++ b/persistence-mongodb/src/main/resources/release-schema.json @@ -844,6 +844,76 @@ } } }, + "OrganizationReference": { + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the party being referenced. This must match the name of an entry in the parties section.", + "title": "Organization name", + "minLength": 1 + }, + "id": { + "type": [ + "string", + "integer" + ], + "description": "The id of the party being referenced. This must match the id of an entry in the parties section.", + "title": "Organization ID", + "minLength": 1 + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and detailed legal identifier information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Identifier" + }, + "address": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and address information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Address", + "description": "(Deprecated outside the parties section)", + "title": "Address" + }, + "additionalIdentifiers": { + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and additional identifiers for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "items": { + "$ref": "#/definitions/Identifier" + }, + "title": "Additional identifiers", + "uniqueItems": true, + "wholeListMerge": true, + "description": "(Deprecated outside the parties section) A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier." + }, + "contactPoint": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and contact point information for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/ContactPoint", + "description": "(Deprecated outside the parties section)", + "title": "Contact point" + } + }, + "required": [ + "id", + "name" + ], + "type": "object", + "description": "The id and name of the party being referenced. Used to cross-reference to the parties section", + "title": "Organization reference" + }, "Organization": { "title": "Organization", "description": "An organization.", From 62abadffff50254029d0af3d9f2b77f4b4e2e65b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 14 Jun 2017 03:50:41 +0300 Subject: [PATCH 007/702] OCE-309 Renamed back en_US.json --- web/public/languages/{en_US-1.json => en_US.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/public/languages/{en_US-1.json => en_US.json} (100%) diff --git a/web/public/languages/en_US-1.json b/web/public/languages/en_US.json similarity index 100% rename from web/public/languages/en_US-1.json rename to web/public/languages/en_US.json From cf5f775e5bec7fbfa6c7ce82dee4e50e577598f4 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 14 Jun 2017 17:06:41 +0300 Subject: [PATCH 008/702] OCVN-401 fixed unit tests --- .../toolkit/persistence/mongo/application.properties | 4 +--- .../persistence/mongo/spring/MongoTemplateConfig.java | 10 +++++----- .../mongo/spring/ShadowMongoDatabaseConfiguration.java | 2 ++ persistence-mongodb/src/test/resources/test.properties | 4 ++-- web/src/test/resources/test.properties | 3 ++- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties index 76d303475..8afcf639e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/application.properties @@ -12,6 +12,4 @@ security.basic.enabled=false server.port = 8090 -spring.data.mongodb.host=localhost -spring.data.mongodb.port=27017 -spring.data.mongodb.database=ocexplorer +spring.data.mongodb.uri=mongodb://localhost:27017/ocexplorer \ No newline at end of file diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java index eaefd4c64..42362c97d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java @@ -1,11 +1,12 @@ package org.devgateway.toolkit.persistence.mongo.spring; -import com.mongodb.MongoClient; +import com.mongodb.MongoClientURI; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.mongo.MongoProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; @@ -13,6 +14,7 @@ * Created by mpostelnicu on 6/12/17. */ @Configuration +@Profile("!integration") public class MongoTemplateConfig { @Autowired @@ -20,8 +22,7 @@ public class MongoTemplateConfig { @Bean(autowire = Autowire.BY_NAME, name = "mongoTemplate") public MongoTemplate mongoTemplate() throws Exception { - return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(properties.getHost(), properties.getPort()), - properties.getDatabase())); + return new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri()))); } /** @@ -33,8 +34,7 @@ public MongoTemplate mongoTemplate() throws Exception { */ @Bean(autowire = Autowire.BY_NAME, name = "shadowMongoTemplate") public MongoTemplate shadowMongoTemplate() throws Exception { - return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(properties.getHost(), properties.getPort()), - properties.getDatabase() + "-shadow")); + return new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + "-shadow"))); } diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java index 019f20bf3..866166764 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java @@ -4,6 +4,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @@ -15,6 +16,7 @@ basePackages = {"org.devgateway.ocds.persistence.mongo.repository.shadow"}, mongoTemplateRef = "shadowMongoTemplate" ) +@Profile("!integration") public class ShadowMongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { protected final Logger logger = LoggerFactory.getLogger(ShadowMongoDatabaseConfiguration.class); diff --git a/persistence-mongodb/src/test/resources/test.properties b/persistence-mongodb/src/test/resources/test.properties index 31c200f84..645ce89ee 100644 --- a/persistence-mongodb/src/test/resources/test.properties +++ b/persistence-mongodb/src/test/resources/test.properties @@ -1,5 +1,3 @@ -local.mongo.port=27018 - spring.mongodb.embedded.version=3.4.4 # LOGGING @@ -11,3 +9,5 @@ logging.level.org.springframework.transaction=error logging.level.org.springframework.test=error logging.level.org.springframework.web=error logging.level.org.hibernate=error + +spring.data.mongodb.port=0 \ No newline at end of file diff --git a/web/src/test/resources/test.properties b/web/src/test/resources/test.properties index f4cb5cd64..c8fd8a9e9 100644 --- a/web/src/test/resources/test.properties +++ b/web/src/test/resources/test.properties @@ -1,4 +1,3 @@ -local.mongo.port=27018 spring.mongodb.embedded.version=3.4.4 # liquibase properties @@ -16,3 +15,5 @@ logging.level.org.hibernate=error spring.datasource.driver-class-name=org.apache.derby.jdbc.EmbeddedDriver spring.datasource.url=jdbc:derby:memory:ocexplorer;create=true + +spring.data.mongodb.port=0 \ No newline at end of file From c97cac6e16f600f504479c449157bac872dd554c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 15 Jun 2017 20:06:15 +0300 Subject: [PATCH 009/702] OCVN 401 any error that is found during import produces a failed impor attempt removed a --- .../ocds/persistence/mongo/reader/RowImporter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java index ed9fd1e1c..37c048288 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java @@ -101,7 +101,7 @@ private boolean isRowEmpty(final String[] row) { } public boolean importRows(final List rows) throws ParseException { - + boolean r = true; for (String[] row : rows) { if (cursorRowNo++ < skipRows || isRowEmpty(row)) { continue; @@ -114,11 +114,12 @@ public boolean importRows(final List rows) throws ParseException { importService.logMessage( "Error importing row " + cursorRowNo + ". " + e + ""); // throw e; we do not stop + r = false; } } logger.debug("Finished importing " + importedRows + " rows."); - return true; + return r; } public abstract void importRow(String[] row) throws ParseException; From f2ef6ab44b93ed5fbc9afc4f2355302dc4812eb1 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 15 Jun 2017 20:06:28 +0300 Subject: [PATCH 010/702] OCVN 401 forgot to set customconversions they have to be explicitly set when mongotemplat --- .../mongo/spring/MongoTemplateConfig.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java index 42362c97d..b46be66f5 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java @@ -9,6 +9,8 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.core.convert.CustomConversions; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; /** * Created by mpostelnicu on 6/12/17. @@ -20,9 +22,14 @@ public class MongoTemplateConfig { @Autowired private MongoProperties properties; + @Autowired + private CustomConversions customConversions; + @Bean(autowire = Autowire.BY_NAME, name = "mongoTemplate") public MongoTemplate mongoTemplate() throws Exception { - return new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri()))); + MongoTemplate template = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri()))); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; } /** @@ -34,7 +41,10 @@ public MongoTemplate mongoTemplate() throws Exception { */ @Bean(autowire = Autowire.BY_NAME, name = "shadowMongoTemplate") public MongoTemplate shadowMongoTemplate() throws Exception { - return new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + "-shadow"))); + MongoTemplate template = new + MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + "-shadow"))); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; } From 2765cc2d008edbd1fd797a44d3bd9276bbcb30c2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 19 Jun 2017 04:54:40 +0300 Subject: [PATCH 011/702] OCE-342 Dynamic map positioning for default layout --- ui/oce/tabs/location/index.jsx | 53 ++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/ui/oce/tabs/location/index.jsx b/ui/oce/tabs/location/index.jsx index e64320a51..2f52ad8fd 100644 --- a/ui/oce/tabs/location/index.jsx +++ b/ui/oce/tabs/location/index.jsx @@ -1,8 +1,10 @@ -import Tab from "../index"; +import ReactDOM from 'react-dom'; +import cn from "classnames"; import {Set} from "immutable"; +import Tab from "../index"; import TenderLocations from "../../visualizations/map/tender-locations"; -import cn from "classnames"; import style from "./style.less"; +import {debounce} from '../../tools.es6'; class LocationTab extends Tab{ static getName(t){return t('tabs:location:title')} @@ -53,13 +55,31 @@ class LocationTab extends Tab{ componentDidMount(){ super.componentDidMount(); - let zoom = document.querySelector('.leaflet-control-zoom'); + const zoom = document.querySelector('.leaflet-control-zoom'); + this.recalcHeight(); + this.windowResizeListener = debounce(this.recalcHeight.bind(this)); + window.addEventListener('resize', this.windowResizeListener); + this.setState({ switcherPos: { top: zoom.offsetTop, left: zoom.offsetLeft + zoom.offsetWidth + 10 } - }) + }); + } + + componentWillUnmount(){ + window.removeEventListener('resize', this.windowResizeListener); + } + + getHeight(){ + const TOP_OFFSET = 64; + const BOTTOM_OFFSET = 66; + return window.innerHeight - TOP_OFFSET - BOTTOM_OFFSET; + } + + recalcHeight(){ + ReactDOM.findDOMNode(this.refs.the_layer).style.height = this.getHeight() + 'px'; } render(){ @@ -69,18 +89,19 @@ class LocationTab extends Tab{ let Map = LAYERS[currentLayer]; return (
- {this.maybeGetSwitcher()} - requestNewData([currentLayer], data)} - translations={translations} - filters={filters} - years={years} - styling={styling} - center={CENTER} - zoom={ZOOM} - /> + {this.maybeGetSwitcher()} + requestNewData([currentLayer], data)} + translations={translations} + filters={filters} + years={years} + styling={styling} + center={CENTER} + zoom={ZOOM} + ref="the_layer" + />
) } From 6e0d6747527a03e1683b0cb61e3bcaad8d18e360 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 19 Jun 2017 05:24:50 +0300 Subject: [PATCH 012/702] OCE-342 Dynamic map positioning for standalone oce --- ui/oce-standalone/index.jsx | 8 +++++++- ui/oce-standalone/style.less | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/oce-standalone/index.jsx b/ui/oce-standalone/index.jsx index 6b64e8cae..954074e28 100644 --- a/ui/oce-standalone/index.jsx +++ b/ui/oce-standalone/index.jsx @@ -12,7 +12,13 @@ import ViewSwitcher from "../oce/switcher.jsx"; import CorruptionRickDashboard from "../oce/corruption-risk"; import cn from "classnames"; -class OCEDemoLocation extends LocationTab{} +class OCEDemoLocation extends LocationTab{ + getHeight(){ + const TOP_OFFSET = 128; + const BOTTOM_OFFSET = 66; + return window.innerHeight - TOP_OFFSET - BOTTOM_OFFSET; + } +} OCEDemoLocation.CENTER = [37, -100]; class OCEChild extends OCApp{ diff --git a/ui/oce-standalone/style.less b/ui/oce-standalone/style.less index 088495d81..0af48e69f 100644 --- a/ui/oce-standalone/style.less +++ b/ui/oce-standalone/style.less @@ -210,7 +210,8 @@ aside [role=navigation]{ .content.map-content{ padding-left: 0; - margin-top: 64px; + margin-top: @headerHeight * 2; + margin-bottom: 66px; padding-right: 0; overflow: hidden; .leaflet-container{ From 744311c6f3357075ca19a1dc397caa691e50d42d Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 00:48:39 +0300 Subject: [PATCH 013/702] OCVN-401 added admin settings for automated importing --- .../wicket/page/EditAdminSettingsPage.html | 2 ++ .../wicket/page/EditAdminSettingsPage.java | 14 ++++++++++++- .../page/EditAdminSettingsPage.properties | 7 ++++++- .../persistence/dao/AdminSettings.java | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html index 5b08a747d..60ead9214 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html @@ -31,6 +31,8 @@

+
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java index ba7bb04ee..29bbd842c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java @@ -32,6 +32,11 @@ public class EditAdminSettingsPage extends AbstractEditPage { private CheckBoxToggleBootstrapFormComponent disableApiSecurity; + private TextFieldBootstrapFormComponent adminEmail; + + private CheckBoxToggleBootstrapFormComponent enableDailyAutomatedImport; + + @SpringBean protected AdminSettingsRepository adminSettingsRepository; @@ -72,8 +77,15 @@ protected void onInitialize() { // rebootServer = new CheckBoxToggleBootstrapFormComponent("rebootServer"); // editForm.add(rebootServer); - disableApiSecurity = new CheckBoxToggleBootstrapFormComponent("disableApiSecurity"); editForm.add(disableApiSecurity); + + enableDailyAutomatedImport = new CheckBoxToggleBootstrapFormComponent("enableDailyAutomatedImport"); + editForm.add(enableDailyAutomatedImport); + + + adminEmail = new TextFieldBootstrapFormComponent<>("adminEmail"); + editForm.add(adminEmail); + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties index 8c2858f17..187162707 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties @@ -6,4 +6,9 @@ rebootServer.label=Enable server reboot warning (just an example) disableApiSecurity.label=Disable API Security for /api/ endpoints (REQUIRES SERVER REBOOT) disableApiSecurity.help=This option will disable API security for any endpoint that starts with /api/. This should \ only be used for demo purposes, where full access to the data through the /api/ endpoints is not a problem. The \ - default option on new installations for this setting is OFF. \ No newline at end of file + default option on new installations for this setting is OFF. +adminEmail.label=Admin Notification Email +adminEmail.help=The email where notifications about scheduled activities can be sent +enableDailyAutomatedImport.label=Enable Daily Automated Import +enableDailyAutomatedImport.help=This will enable daily automated import and transformation of data from third party \ + sources \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java index 593d8d805..386ce86ed 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java @@ -21,6 +21,10 @@ public class AdminSettings extends AbstractAuditableEntity implements Serializab private Boolean rebootServer = false; + private String adminEmail = null; + + private Boolean enableDailyAutomatedImport = false; + /** * This disables the security of /api/ endpoints, should be used for demo purposes only */ @@ -54,4 +58,21 @@ public Boolean getDisableApiSecurity() { public void setDisableApiSecurity(Boolean disableApiSecurity) { this.disableApiSecurity = disableApiSecurity; } + + + public String getAdminEmail() { + return adminEmail; + } + + public void setAdminEmail(String adminEmail) { + this.adminEmail = adminEmail; + } + + public Boolean getEnableDailyAutomatedImport() { + return enableDailyAutomatedImport; + } + + public void setEnableDailyAutomatedImport(Boolean enableDailyAutomatedImport) { + this.enableDailyAutomatedImport = enableDailyAutomatedImport; + } } From 775556d316ec77719bd2a254267b9f048f05a93b Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 00:48:59 +0300 Subject: [PATCH 014/702] OCVN-401 checkstyle changes for import --- checkstyle.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/checkstyle.xml b/checkstyle.xml index 41e92e6e7..d90aa6ce9 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -47,7 +47,9 @@ - + + + From 890058b1002f7d0da3f9d893fd106ba12ec17c1f Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 00:49:24 +0300 Subject: [PATCH 015/702] OCVN-401 started writing generic schedule import service --- .../service/ScheduledExcelImportService.java | 46 +++++++++++++++++++ .../mongo/spring/ExcelImportService.java | 8 +++- .../mongo/spring/ImportResult.java | 27 +++++++++++ .../ocds/web/util/SettingsUtils.java | 2 +- 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java new file mode 100644 index 000000000..6efac9489 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.toolkit.forms.service; + +import org.apache.commons.lang.BooleanUtils; +import org.apache.log4j.Logger; +import org.devgateway.ocds.persistence.mongo.spring.ExcelImportService; +import org.devgateway.ocds.web.util.SettingsUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class ScheduledExcelImportService { + + private static final Logger LOGGER = Logger.getLogger(ScheduledExcelImportService.class); + + @Autowired + private ExcelImportService excelImportService; + + @Autowired + private SettingsUtils settingsUtils; + + @Scheduled(cron = "0 0 3 * * ?") + public void excelImportService() { + + if (BooleanUtils.isFalse(settingsUtils.getSettings().getEnableDailyAutomatedImport())) { + return; + } + + // excelImportService=excelImportService.importAllSheets() + + + } + + +} \ No newline at end of file diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java index 79c0a9b02..439d4bd0d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ExcelImportService.java @@ -1,10 +1,16 @@ package org.devgateway.ocds.persistence.mongo.spring; +import java.util.List; + /** * @author idobre * @since 5/20/16 - * + *

* Service that imports Excel sheets in OCDS format */ public interface ExcelImportService extends ImportService { + + ImportResult importAllSheets(List fileTypes, byte[] prototypeDatabase, byte[] + locations, byte[] publicInstitutionsSuppliers, byte[] cdg, Boolean purgeDatabase, + Boolean validateData, Boolean flagData) throws InterruptedException; } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java new file mode 100644 index 000000000..12df97c4e --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java @@ -0,0 +1,27 @@ +package org.devgateway.ocds.persistence.mongo.spring; + +/** + * Created by mpost on 21-Jun-17. + */ +public class ImportResult { + + private Boolean success; + + private StringBuffer msgBuffer; + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public StringBuffer getMsgBuffer() { + return msgBuffer; + } + + public void setMsgBuffer(StringBuffer msgBuffer) { + this.msgBuffer = msgBuffer; + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java index 3f07f5d70..1703ebfca 100644 --- a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java +++ b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java @@ -24,7 +24,7 @@ public class SettingsUtils { @Autowired private AdminSettingsRepository adminSettingsRepository; - private AdminSettings getSettings() { + public AdminSettings getSettings() { List list = adminSettingsRepository.findAll(); if (list.size() == 0) { return new AdminSettings(); From c3d6952757240de23394d00657af045d5cd881c5 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 11:46:16 +0300 Subject: [PATCH 016/702] OCVN-401 added import files path property --- .../forms/wicket/page/EditAdminSettingsPage.html | 1 + .../forms/wicket/page/EditAdminSettingsPage.java | 4 ++++ .../forms/wicket/page/EditAdminSettingsPage.properties | 4 +++- .../toolkit/persistence/dao/AdminSettings.java | 10 ++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html index 60ead9214..c3ca6bc78 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html @@ -33,6 +33,7 @@

+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java index 29bbd842c..33666690c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java @@ -36,6 +36,7 @@ public class EditAdminSettingsPage extends AbstractEditPage { private CheckBoxToggleBootstrapFormComponent enableDailyAutomatedImport; + private TextFieldBootstrapFormComponent importFilesPath; @SpringBean protected AdminSettingsRepository adminSettingsRepository; @@ -87,5 +88,8 @@ protected void onInitialize() { adminEmail = new TextFieldBootstrapFormComponent<>("adminEmail"); editForm.add(adminEmail); + importFilesPath = new TextFieldBootstrapFormComponent<>("importFilesPath"); + editForm.add(importFilesPath); + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties index 187162707..d4b85289c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties @@ -11,4 +11,6 @@ adminEmail.label=Admin Notification Email adminEmail.help=The email where notifications about scheduled activities can be sent enableDailyAutomatedImport.label=Enable Daily Automated Import enableDailyAutomatedImport.help=This will enable daily automated import and transformation of data from third party \ - sources \ No newline at end of file + sources +importFilesPath.label=Import Files Path +importFilesPath.help=The directory on the server where the automated import files are uploaded \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java index 386ce86ed..0f9c9c6bc 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java @@ -25,6 +25,8 @@ public class AdminSettings extends AbstractAuditableEntity implements Serializab private Boolean enableDailyAutomatedImport = false; + private String importFilesPath = null; + /** * This disables the security of /api/ endpoints, should be used for demo purposes only */ @@ -75,4 +77,12 @@ public Boolean getEnableDailyAutomatedImport() { public void setEnableDailyAutomatedImport(Boolean enableDailyAutomatedImport) { this.enableDailyAutomatedImport = enableDailyAutomatedImport; } + + public String getImportFilesPath() { + return importFilesPath; + } + + public void setImportFilesPath(String importFilesPath) { + this.importFilesPath = importFilesPath; + } } From db4a77985955a65c24a32940a336420f152d35f3 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 11:46:59 +0300 Subject: [PATCH 017/702] OCVN-401 added a more generic sendEmail method --- .../forms/service/SendEmailService.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java index 02a73cbbd..2e4a58788 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java @@ -20,8 +20,8 @@ /** * Service to send emails to users to validate email addresses or reset passwords - * @author mpostelnicu * + * @author mpostelnicu */ @Component public class SendEmailService { @@ -42,20 +42,25 @@ public void setTemplateMessage(final SimpleMailMessage templateMessage) { /** * Send a reset password email. This is UNSAFE because passwords are sent in clear text. * Nevertheless some customers will ask for these emails to be sent, so ... + * * @param person * @param newPassword */ public void sendEmailResetPassword(final Person person, final String newPassword) { - - SimpleMailMessage msg = new SimpleMailMessage(); - msg.setTo(person.getEmail()); - msg.setFrom("support@developmentgateway.org"); - msg.setSubject("Recover your password"); - msg.setText("Dear " + person.getFirstName() + " " + person.getLastName() + ",\n\n" + sendEmail("Recover your password", "Dear " + person.getFirstName() + " " + person.getLastName() + + ",\n\n" + "These are your new login credentials for E-Procurement Toolkit.\n\n" + "Username: " + person.getUsername() + "\n" + "Password: " + newPassword + "\n\n" + "At login, you will be prompted to change your password to one of your choice.\n\n" + "Thank you,\n" - + "DG Team"); + + "DG Team", person.getEmail()); + } + + public void sendEmail(final String subject, final String text, final String to) { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setTo(to); + msg.setFrom("support@developmentgateway.org"); + msg.setSubject(subject); + msg.setText(text); try { javaMailSenderImpl.send(msg); } catch (MailException e) { From 47cd4f00cf502efcde275619c264d537942c27ab Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 11:49:16 +0300 Subject: [PATCH 018/702] OCVN-401 added a simple schedule routine for import and email notification --- .../service/ScheduledExcelImportService.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java index 6efac9489..90c98d6d4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java @@ -14,12 +14,12 @@ import org.apache.commons.lang.BooleanUtils; import org.apache.log4j.Logger; import org.devgateway.ocds.persistence.mongo.spring.ExcelImportService; +import org.devgateway.ocds.persistence.mongo.spring.ImportResult; import org.devgateway.ocds.web.util.SettingsUtils; +import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -@Service +//@Service public class ScheduledExcelImportService { private static final Logger LOGGER = Logger.getLogger(ScheduledExcelImportService.class); @@ -27,20 +27,27 @@ public class ScheduledExcelImportService { @Autowired private ExcelImportService excelImportService; + @Autowired + private SendEmailService sendEmailService; + @Autowired private SettingsUtils settingsUtils; - @Scheduled(cron = "0 0 3 * * ?") + //@Scheduled(cron = "0 0 3 * * ?") public void excelImportService() { - if (BooleanUtils.isFalse(settingsUtils.getSettings().getEnableDailyAutomatedImport())) { + AdminSettings settings = settingsUtils.getSettings(); + + if (BooleanUtils.isFalse(settings.getEnableDailyAutomatedImport())) { return; } - // excelImportService=excelImportService.importAllSheets() - + ImportResult result = null; + //result = excelImportService.importAllSheets(); + if (!result.getSuccess()) { + sendEmailService.sendEmail("Excel import failed!", result.getMsgBuffer().toString(), + settings.getAdminEmail()); + } } - - } \ No newline at end of file From 0b204b464c61fad3202e76dda3a93f8ea9f75603 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 13:41:49 +0300 Subject: [PATCH 019/702] OCVN-401 added import result constructor --- .../ocds/persistence/mongo/spring/ImportResult.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java index 12df97c4e..2dd9396cb 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/ImportResult.java @@ -5,6 +5,11 @@ */ public class ImportResult { + public ImportResult(Boolean success, StringBuffer msgBuffer) { + this.success = success; + this.msgBuffer = msgBuffer; + } + private Boolean success; private StringBuffer msgBuffer; From 654d80a455070c74fa8c4e9d8747b92e035a962c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 14:44:57 +0300 Subject: [PATCH 020/702] OCVN-401 used interface for mail sender --- .../devgateway/toolkit/forms/service/SendEmailService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java index 2e4a58788..3990fb7b2 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java @@ -15,7 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Component; /** @@ -27,7 +27,7 @@ public class SendEmailService { @Autowired - private JavaMailSenderImpl javaMailSenderImpl; + private JavaMailSender javaMailSenderImpl; private SimpleMailMessage templateMessage; From 8fa693b1d5f1b7fcac2e0fb324682801110cbcec Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 16:24:14 +0300 Subject: [PATCH 021/702] OCVN-401 email sender does not run in integration testing mode --- .../toolkit/web/spring/EmailServiceConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java b/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java index 13fcfd2d9..7eed600fe 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java @@ -13,9 +13,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.mail.javamail.JavaMailSenderImpl; @Configuration +@Profile("!integration") public class EmailServiceConfiguration { private static final int SMTP_PORT = 25; From 957a35baf0fd1c6c7db3e7d97b32a08975452d48 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 17:23:19 +0300 Subject: [PATCH 022/702] OCVN-401 moved some services to web module --- .../devgateway/toolkit/forms/wicket/page/user/EditUserPage.java | 2 +- .../toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java | 2 +- .../ocds/web/spring}/ScheduledExcelImportService.java | 2 +- .../java/org/devgateway/ocds/web/spring}/SendEmailService.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename {forms/src/main/java/org/devgateway/toolkit/forms/service => web/src/main/java/org/devgateway/ocds/web/spring}/ScheduledExcelImportService.java (97%) rename {forms/src/main/java/org/devgateway/toolkit/forms/service => web/src/main/java/org/devgateway/ocds/web/spring}/SendEmailService.java (98%) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java index 8b57470ba..9297db43c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java @@ -35,7 +35,7 @@ import org.devgateway.toolkit.forms.WebConstants; import org.devgateway.toolkit.web.security.SecurityConstants; import org.devgateway.toolkit.web.security.SecurityUtil; -import org.devgateway.toolkit.forms.service.SendEmailService; +import org.devgateway.ocds.web.spring.SendEmailService; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.PasswordFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java index 4e46b0239..40a74423b 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.java @@ -10,7 +10,7 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.devgateway.toolkit.forms.service.SendEmailService; +import org.devgateway.ocds.web.spring.SendEmailService; import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.BasePage; import org.devgateway.toolkit.persistence.dao.Person; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java b/web/src/main/java/org/devgateway/ocds/web/spring/ScheduledExcelImportService.java similarity index 97% rename from forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java rename to web/src/main/java/org/devgateway/ocds/web/spring/ScheduledExcelImportService.java index 90c98d6d4..3fb0ab994 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/service/ScheduledExcelImportService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/ScheduledExcelImportService.java @@ -9,7 +9,7 @@ * Contributors: * Development Gateway - initial API and implementation *******************************************************************************/ -package org.devgateway.toolkit.forms.service; +package org.devgateway.ocds.web.spring; import org.apache.commons.lang.BooleanUtils; import org.apache.log4j.Logger; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java similarity index 98% rename from forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java rename to web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java index 3990fb7b2..0bd0874e3 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/service/SendEmailService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java @@ -9,7 +9,7 @@ * Contributors: * Development Gateway - initial API and implementation *******************************************************************************/ -package org.devgateway.toolkit.forms.service; +package org.devgateway.ocds.web.spring; import org.devgateway.toolkit.persistence.dao.Person; import org.springframework.beans.factory.annotation.Autowired; From 10b31678299390b6d812687056333d0be2fec096 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 21 Jun 2017 17:59:50 +0300 Subject: [PATCH 023/702] OCVN-401 disabled for integration tests --- .../org/devgateway/ocds/web/spring/SendEmailService.java | 6 ++++++ .../java/org/devgateway/ocds/web/util/SettingsUtils.java | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java index 0bd0874e3..6473f7faa 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java @@ -11,8 +11,10 @@ *******************************************************************************/ package org.devgateway.ocds.web.spring; +import org.apache.log4j.Logger; import org.devgateway.toolkit.persistence.dao.Person; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; @@ -24,8 +26,11 @@ * @author mpostelnicu */ @Component +@Profile("!integration") public class SendEmailService { + private static final Logger LOGGER = Logger.getLogger(SendEmailService.class); + @Autowired private JavaMailSender javaMailSenderImpl; @@ -62,6 +67,7 @@ public void sendEmail(final String subject, final String text, final String to) msg.setSubject(subject); msg.setText(text); try { + LOGGER.info("Sending email " + msg); javaMailSenderImpl.send(msg); } catch (MailException e) { e.printStackTrace(); diff --git a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java index 1703ebfca..c7195eb4a 100644 --- a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java +++ b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java @@ -1,19 +1,20 @@ package org.devgateway.ocds.web.util; +import java.util.List; import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.devgateway.toolkit.persistence.repository.AdminSettingsRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import java.util.List; - /** * @author idobre * @since 6/22/16 */ @Service +@Profile("!integration") public class SettingsUtils { protected static Logger logger = LoggerFactory.getLogger(SettingsUtils.class); From 953499cedd6b89be35624ca677611c18e463dd1c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 11:59:16 +0300 Subject: [PATCH 024/702] OCVN-401 Automate vietnam import routine - working unit tests with complete import process and shadow dbs --- .../forms/wicket/FormsWebApplication.java | 6 +- persistence-mongodb/pom.xml | 1 - .../spring/MongoPersistenceApplication.java | 5 +- .../mongo/spring/MongoTemplateConfig.java | 5 +- .../mongo/spring/MongoTemplateTestConfig.java | 113 ++++++++++++++++++ .../ShadowMongoDatabaseConfiguration.java | 2 - .../toolkit/ui/UIWebApplication.java | 3 +- .../ocds/web/util/SettingsUtils.java | 2 - .../toolkit/web/spring/WebApplication.java | 3 +- 9 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateTestConfig.java diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java index 1a379673b..dc58f2da8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/FormsWebApplication.java @@ -24,6 +24,7 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.editor.SummernoteStoredImageResourceReference; import de.agilecoders.wicket.less.BootstrapLess; import de.agilecoders.wicket.webjars.WicketWebjars; +import java.math.BigDecimal; import nl.dries.wicket.hibernate.dozer.SessionFinderHolder; import org.apache.wicket.Application; import org.apache.wicket.ConverterLocator; @@ -49,6 +50,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; @@ -56,8 +58,6 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.wicketstuff.annotation.scan.AnnotatedMountScanner; -import java.math.BigDecimal; - /** * The web application class also serves as spring boot starting point by using * spring boot's EnableAutoConfiguration annotation and providing the main @@ -67,7 +67,7 @@ * */ @EnableScheduling -@SpringBootApplication +@SpringBootApplication(exclude = { EmbeddedMongoAutoConfiguration.class }) @ComponentScan("org.devgateway") @PropertySource("classpath:/org/devgateway/toolkit/forms/application.properties") @EnableCaching diff --git a/persistence-mongodb/pom.xml b/persistence-mongodb/pom.xml index db0cca020..94cdb766c 100644 --- a/persistence-mongodb/pom.xml +++ b/persistence-mongodb/pom.xml @@ -112,7 +112,6 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - test diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java index 739d08d15..df060ee46 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoPersistenceApplication.java @@ -17,12 +17,12 @@ import java.util.List; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; import org.springframework.core.convert.converter.Converter; -import org.springframework.data.mongodb.config.EnableMongoAuditing; import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.util.Assert; @@ -34,10 +34,9 @@ * * @author mpostelnicu */ -@SpringBootApplication +@SpringBootApplication(exclude = { EmbeddedMongoAutoConfiguration.class }) @ComponentScan("org.devgateway") @PropertySource("classpath:/org/devgateway/toolkit/persistence/mongo/application.properties") -@EnableMongoAuditing @EnableCaching public class MongoPersistenceApplication { diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java index b46be66f5..3e7554f81 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateConfig.java @@ -19,6 +19,8 @@ @Profile("!integration") public class MongoTemplateConfig { + public static final String SHADOW_POSTFIX = "-shadow"; + @Autowired private MongoProperties properties; @@ -42,10 +44,9 @@ public MongoTemplate mongoTemplate() throws Exception { @Bean(autowire = Autowire.BY_NAME, name = "shadowMongoTemplate") public MongoTemplate shadowMongoTemplate() throws Exception { MongoTemplate template = new - MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + "-shadow"))); + MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI(properties.getUri() + SHADOW_POSTFIX))); ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); return template; } - } diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateTestConfig.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateTestConfig.java new file mode 100644 index 000000000..3e488f314 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/MongoTemplateTestConfig.java @@ -0,0 +1,113 @@ +package org.devgateway.toolkit.persistence.mongo.spring; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientOptions; +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodProcess; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongoCmdOptionsBuilder; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Version; +import java.io.IOException; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.core.convert.CustomConversions; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; + +/** + * Created by mpostelnicu on 6/12/17. + */ +@Configuration +@Profile("integration") +public class MongoTemplateTestConfig { + + @Autowired + private MongoProperties properties; + + @Autowired + private CustomConversions customConversions; + + @Autowired + private Environment environment; + + @Autowired(required = false) + private MongoClientOptions options; + + private String originalUri; + + @Bean(destroyMethod = "stop") + public MongodProcess mongodProcess(MongodExecutable embeddedMongoServer) throws IOException { + return embeddedMongoServer.start(); + } + + @Bean(destroyMethod = "stop") + public MongodExecutable embeddedMongoServer(MongodStarter mongodStarter, IMongodConfig iMongodConfig) + throws IOException { + return mongodStarter.prepare(iMongodConfig); + } + + @Bean + public IMongodConfig mongodConfig() throws IOException { + return new MongodConfigBuilder().version(Version.Main.PRODUCTION) + .cmdOptions(new MongoCmdOptionsBuilder().useNoJournal(true) + .build()) + .build(); + } + + @Bean + public MongodStarter mongodStarter() { + return MongodStarter.getDefaultInstance(); + } + + + @Bean(name = "mongoTemplate") + public MongoTemplate mongoTemplate(MongodProcess mongodProcess) throws Exception { + Net net = mongodProcess.getConfig().net(); + properties.setHost(net.getServerAddress().getHostName()); + properties.setPort(net.getPort()); + properties.setDatabase(originalUri); + properties.setUri(null); + + MongoTemplate template = new MongoTemplate( + new SimpleMongoDbFactory(properties.createMongoClient(this.options, environment), + properties.getDatabase())); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } + + @PostConstruct + public void postConstruct() { + //set uri string + originalUri = new ConnectionString(properties.getUri()).getDatabase(); + } + + /** + * Creates a shadow template configuration by adding "-shadow" as postfix of database name. + * This is used to replicate the entire database structure in a shadow/temporary database location + * + * @return + * @throws Exception + */ + @Bean(name = "shadowMongoTemplate") + public MongoTemplate shadowMongoTemplate(MongodProcess mongodProcess) throws Exception { + Net net = mongodProcess.getConfig().net(); + properties.setHost(net.getServerAddress().getHostName()); + properties.setPort(net.getPort()); + properties.setDatabase(originalUri + MongoTemplateConfig.SHADOW_POSTFIX); + properties.setUri(null); + MongoTemplate template = new MongoTemplate( + new SimpleMongoDbFactory(properties.createMongoClient(this.options, environment), + properties.getDatabase())); + ((MappingMongoConverter) template.getConverter()).setCustomConversions(customConversions); + return template; + } +} diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java index 866166764..019f20bf3 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/ShadowMongoDatabaseConfiguration.java @@ -4,7 +4,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @@ -16,7 +15,6 @@ basePackages = {"org.devgateway.ocds.persistence.mongo.repository.shadow"}, mongoTemplateRef = "shadowMongoTemplate" ) -@Profile("!integration") public class ShadowMongoDatabaseConfiguration extends AbstractMongoDatabaseConfiguration { protected final Logger logger = LoggerFactory.getLogger(ShadowMongoDatabaseConfiguration.class); diff --git a/ui/src/main/java/org/devgateway/toolkit/ui/UIWebApplication.java b/ui/src/main/java/org/devgateway/toolkit/ui/UIWebApplication.java index 2e3130aef..ec6017bd2 100644 --- a/ui/src/main/java/org/devgateway/toolkit/ui/UIWebApplication.java +++ b/ui/src/main/java/org/devgateway/toolkit/ui/UIWebApplication.java @@ -2,6 +2,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.context.annotation.PropertySource; /** @@ -9,7 +10,7 @@ * */ -@SpringBootApplication +@SpringBootApplication(exclude = { EmbeddedMongoAutoConfiguration.class }) @PropertySource("classpath:/org/devgateway/toolkit/ui/application.properties") public class UIWebApplication { diff --git a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java index c7195eb4a..b00da68b9 100644 --- a/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java +++ b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java @@ -6,7 +6,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; /** @@ -14,7 +13,6 @@ * @since 6/22/16 */ @Service -@Profile("!integration") public class SettingsUtils { protected static Logger logger = LoggerFactory.getLogger(SettingsUtils.class); diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/WebApplication.java b/web/src/main/java/org/devgateway/toolkit/web/spring/WebApplication.java index d8675d626..fa61f6c12 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/WebApplication.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/WebApplication.java @@ -13,6 +13,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; @@ -21,7 +22,7 @@ * */ -@SpringBootApplication +@SpringBootApplication(exclude = { EmbeddedMongoAutoConfiguration.class }) @PropertySource("classpath:/org/devgateway/toolkit/web/application.properties") @ComponentScan("org.devgateway.toolkit") public class WebApplication { From ceb625870009292bdf18ba56553804bd24343077 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 12:27:54 +0300 Subject: [PATCH 025/702] OCVN-401 removed integration eviction --- .../java/org/devgateway/ocds/web/spring/SendEmailService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java index 6473f7faa..69e4ef1ea 100644 --- a/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java +++ b/web/src/main/java/org/devgateway/ocds/web/spring/SendEmailService.java @@ -14,7 +14,6 @@ import org.apache.log4j.Logger; import org.devgateway.toolkit.persistence.dao.Person; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Profile; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; @@ -26,7 +25,6 @@ * @author mpostelnicu */ @Component -@Profile("!integration") public class SendEmailService { private static final Logger LOGGER = Logger.getLogger(SendEmailService.class); From f35edbd096d9b9d6a8d7878f62ba6a09027c4cbe Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 12:55:57 +0300 Subject: [PATCH 026/702] OCVN-401 added date of creation --- .../ocds/persistence/mongo/reader/ReleaseRowImporter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java index 9bdeeb93d..43462fb3e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ReleaseRowImporter.java @@ -1,11 +1,11 @@ package org.devgateway.ocds.persistence.mongo.reader; +import java.text.ParseException; +import java.util.Date; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.ImportService; -import java.text.ParseException; - /** * @author mpostelnicu */ @@ -19,6 +19,7 @@ public ReleaseRowImporter(final ReleaseRepository releaseRepository, final Impor @Override public void importRow(final String[] row) throws ParseException { Release release = createReleaseFromReleaseRow(row); + release.setDate(new Date()); if (release.getId() == null) { repository.insert(release); } else { From 4d4ad8f7c4378a28fbf956c6e1fca897dd42e35c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 13:09:02 +0300 Subject: [PATCH 027/702] OCVN-401 added a warning type runtime exception that will not flag the import process as failed --- .../mongo/reader/ImportWarningRuntimeException.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java new file mode 100644 index 000000000..0deb96df9 --- /dev/null +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java @@ -0,0 +1,7 @@ +package org.devgateway.ocds.persistence.mongo.reader; + +/** + * Created by mpostelnicu on 6/23/17. + */ +public class ImportWarningRuntimeException extends RuntimeException { +} From 2c4bbc5521329f4dfb153d6f21d64b6270a79ef7 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 13:10:38 +0300 Subject: [PATCH 028/702] OCVN-401 added a warning type runtime exception that will not flag the import process as failed - string constructor --- .../mongo/reader/ImportWarningRuntimeException.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java index 0deb96df9..cb64e794a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/ImportWarningRuntimeException.java @@ -4,4 +4,8 @@ * Created by mpostelnicu on 6/23/17. */ public class ImportWarningRuntimeException extends RuntimeException { + + public ImportWarningRuntimeException(String var1) { + super(var1); + } } From 1cc473c3ab66a3cfaf78a075c5e41ce3b7edc6b0 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 13:20:37 +0300 Subject: [PATCH 029/702] OCVN-401 log warning instead of error for ImportWarningRuntimeException --- .../persistence/mongo/reader/RowImporter.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java index 37c048288..806980d46 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java @@ -16,13 +16,10 @@ /** * Generic superclass for importing rows from excel data sources * - * @author mpostelnicu - * - * @param - * - the type of OCDS/dervied entity to be imported + * @param - the type of OCDS/dervied entity to be imported * @param the id type - * @param - * - the main repository that is able to save + * @param - the main repository that is able to save + * @author mpostelnicu */ public abstract class RowImporter> { @@ -111,8 +108,14 @@ public boolean importRows(final List rows) throws ParseException { importRow(row); importedRows++; } catch (Exception e) { + if (e instanceof ImportWarningRuntimeException) { + r = true; + } else { + r = false; + } importService.logMessage( - "Error importing row " + cursorRowNo + ". " + e + ""); + "Error importing row " + + cursorRowNo + ". " + e + ""); // throw e; we do not stop r = false; } From 08ec0998239ea4bcfa7584455f4b06296d720cd8 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 13:28:57 +0300 Subject: [PATCH 030/702] OCVN-401 log warning instead of error for ImportWarningRuntimeException - multiple row import bugs --- .../ocds/persistence/mongo/reader/RowImporter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java index 806980d46..c2e980fdf 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java @@ -108,16 +108,15 @@ public boolean importRows(final List rows) throws ParseException { importRow(row); importedRows++; } catch (Exception e) { + boolean criticalError = true; if (e instanceof ImportWarningRuntimeException) { - r = true; + criticalError = false; } else { r = false; } importService.logMessage( - "Error importing row " + "Error importing row " + cursorRowNo + ". " + e + ""); - // throw e; we do not stop - r = false; } } From 523e9a75abf236f9e70ad1d19709f8dbfbd15b75 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 23 Jun 2017 14:36:43 +0300 Subject: [PATCH 031/702] OCVN-401 small renames --- .../devgateway/ocds/persistence/mongo/reader/RowImporter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java index c2e980fdf..84e03b205 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/RowImporter.java @@ -115,7 +115,8 @@ public boolean importRows(final List rows) throws ParseException { r = false; } importService.logMessage( - "Error importing row " + "" + + (criticalError ? "CRITICAL " : "") + "Problem importing row " + cursorRowNo + ". " + e + ""); } } From e9b35b5397b2961e8b0ee1b367d2456b2d9f579a Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 26 Jun 2017 11:44:09 +0300 Subject: [PATCH 032/702] OCVN-401 fixed test --- .../persistence/mongo/spring/json/ReleaseJsonImportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java index 9176c3852..9046860e4 100644 --- a/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java +++ b/persistence-mongodb/src/test/java/org/devgateway/ocds/persistence/mongo/spring/json/ReleaseJsonImportTest.java @@ -37,7 +37,7 @@ public void importObject() throws Exception { + " budget: {\n" + " description: \"budget desc...\",\n" + " amount: {\n" - + " amount: 10000,\n" + + " amount: 10000.0,\n" + " currency: \"USD\"\n" + " }\n" + " }\n" From 72d79b44b8e4f2495427d83f003d76ce27fdc5b1 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 26 Jun 2017 12:10:08 +0300 Subject: [PATCH 033/702] OCVN-401 fixed test --- .../toolkit/web/spring/EmailServiceConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java b/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java index 7eed600fe..13fcfd2d9 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/EmailServiceConfiguration.java @@ -13,11 +13,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.mail.javamail.JavaMailSenderImpl; @Configuration -@Profile("!integration") public class EmailServiceConfiguration { private static final int SMTP_PORT = 25; From e33a6c183d7f8b52f56d49327cb8343133fda5bd Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 27 Jun 2017 15:11:29 +0300 Subject: [PATCH 034/702] #174 Email client not working properly --- forms/pom.xml | 5 ++--- web/pom.xml | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/forms/pom.xml b/forms/pom.xml index 6112473bb..fa507f33c 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -239,9 +239,8 @@ - javax.mail - mail - 1.4.7 + org.springframework.boot + spring-boot-starter-mail diff --git a/web/pom.xml b/web/pom.xml index 3a5451923..022907fa1 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -116,9 +116,8 @@ - javax.mail - mail - 1.4 + org.springframework.boot + spring-boot-starter-mail From ecef29770f516d55351ce01731ca210a03b3f996 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 27 Jun 2017 15:43:06 +0300 Subject: [PATCH 035/702] #174 removed mailapi 1.4.7 --- persistence-mongodb/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/persistence-mongodb/pom.xml b/persistence-mongodb/pom.xml index 1a1d032a9..7de180564 100644 --- a/persistence-mongodb/pom.xml +++ b/persistence-mongodb/pom.xml @@ -90,6 +90,12 @@ com.github.fge json-schema-validator ${json.schema.validator.version} + + + javax.mail + mailapi + + From 7189891529f5e0af1b2186aaaab3a21cec9df8b6 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 4 Jul 2017 19:37:50 +0300 Subject: [PATCH 036/702] OCE-355 new validator maven module , removed derby as global depdendency to keep the executable jar small --- checkstyle-suppressions.xml | 2 +- pom.xml | 9 +-- validator/pom.xml | 128 ++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 validator/pom.xml diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 86fcf6c45..663bfac0e 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -6,7 +6,7 @@ + files="PersistenceApplication\.java|MongoPersistenceApplication\.java|UIWebApplication\.java|WebApplication\.java|ReportingApplication\.java|ValidatorApplication\.java" /> \ No newline at end of file diff --git a/pom.xml b/pom.xml index e25995056..b02e7cfd5 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ ui forms persistence-mongodb + validator 2015 @@ -102,13 +103,7 @@ - - - org.apache.derby - derby - ${derby.version} - - + diff --git a/validator/pom.xml b/validator/pom.xml new file mode 100644 index 000000000..0c19a44c0 --- /dev/null +++ b/validator/pom.xml @@ -0,0 +1,128 @@ + + + + + 4.0.0 + + validator + jar + + OCExplorer Validator + OCExplorer OCDS Validator + + + UTF-8 + org.devgateway.ocds.ValidatorApplication + 1.8 + + + + org.devgateway.ocds + oce + 0.0.1-SNAPSHOT + + + + + junit + junit + test + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-validation + + + + com.fasterxml.jackson.core + jackson-annotations + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + + true + src/main/java + + **/*.properties + **/*.sql + **/*.xml + + + + + true + src/main/resources + + **/*.properties + **/*.sql + **/*.xml + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + ${spring.boot.version} + + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + -proc:none + + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + From 1f0aa0d316bd709111034fcaa1f6ae4317cea067 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 4 Jul 2017 19:38:29 +0300 Subject: [PATCH 037/702] OCE-355 simple parameter reader after spring is initialized, jmx disabled --- .../ocds/validator/ApplicationLoader.java | 22 +++++++++++++++++++ .../ocds/validator/ValidatorApplication.java | 18 +++++++++++++++ .../ocds/validator/application.properties | 12 ++++++++++ 3 files changed, 52 insertions(+) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/ApplicationLoader.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/ValidatorApplication.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/application.properties diff --git a/validator/src/main/java/org/devgateway/ocds/validator/ApplicationLoader.java b/validator/src/main/java/org/devgateway/ocds/validator/ApplicationLoader.java new file mode 100644 index 000000000..11b89772f --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/ApplicationLoader.java @@ -0,0 +1,22 @@ +package org.devgateway.ocds.validator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class ApplicationLoader implements CommandLineRunner { + + private static final Logger logger = LoggerFactory.getLogger(ApplicationLoader.class); + + @Override + public void run(String... strings) throws Exception { + StringBuilder sb = new StringBuilder(); + for (String option : strings) { + sb.append(" ").append(option); + } + sb = sb.length() == 0 ? sb.append("No Options Specified") : sb; + logger.info(String.format("WAR launched with following options: %s", sb.toString())); + } +} \ No newline at end of file diff --git a/validator/src/main/java/org/devgateway/ocds/validator/ValidatorApplication.java b/validator/src/main/java/org/devgateway/ocds/validator/ValidatorApplication.java new file mode 100644 index 000000000..6b5f0be93 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/ValidatorApplication.java @@ -0,0 +1,18 @@ +package org.devgateway.ocds.validator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; + +/** + * Created by mpostelnicu on 7/4/17. + */ +@SpringBootApplication +@PropertySource("classpath:/org/devgateway/ocds/validator/application.properties") +@ComponentScan("org.devgateway.ocds.validator") +public class ValidatorApplication { + public static void main(final String[] args) { + SpringApplication.run(ValidatorApplication.class, args); + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/application.properties b/validator/src/main/java/org/devgateway/ocds/validator/application.properties new file mode 100644 index 000000000..ff438d37e --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/application.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +spring.jmx.enabled=false From 04afd1d974eb716112820caa12a294684770a1c1 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 4 Jul 2017 19:38:42 +0300 Subject: [PATCH 038/702] OCE-355 moved derby to persistence module --- persistence/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/persistence/pom.xml b/persistence/pom.xml index c095dc27e..d9358ec19 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -155,6 +155,12 @@ ${derby.version} + + org.apache.derby + derby + ${derby.version} + + javax.validation validation-api From c2139495f02d2a882fd1cb921effca577ee03b9c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 5 Jul 2017 20:42:47 +0300 Subject: [PATCH 039/702] OCE-355 added all release-package schemas and release schemas up to 1.1, plus bid and enquiry extensions --- persistence-mongodb/pom.xml | 2 +- .../OcdsSchemaValidationConfiguration.java | 1 + .../spring/OcdsSchemaValidatorService.java | 1 + validator/pom.xml | 25 +- .../validator/OcdsValidatorConstants.java | 17 + .../ocds/validator/OcdsValidatorRequest.java | 43 + .../ocds/validator/OcdsValidatorService.java | 29 + .../ocds_bid_extension/v1.1/extension.json | 6 + .../v1.1/release-schema.json | 175 ++ .../v1.1/extension.json | 6 + .../v1.1/release-schema.json | 70 + .../release-package-schema-1.0.0.json | 60 + .../release-package-schema-1.0.1.json | 65 + .../release-package-schema-1.0.2.json | 65 + .../release-package-schema-1.1.0.json | 110 + .../schema/release/release-schema-1.0.0.json | 989 +++++++++ .../schema/release/release-schema-1.0.1.json | 988 +++++++++ .../schema/release/release-schema-1.0.2.json | 1097 ++++++++++ .../schema/release/release-schema-1.1.0.json | 1883 +++++++++++++++++ 19 files changed, 5630 insertions(+), 2 deletions(-) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json diff --git a/persistence-mongodb/pom.xml b/persistence-mongodb/pom.xml index d251c8b80..b3b72fbb0 100644 --- a/persistence-mongodb/pom.xml +++ b/persistence-mongodb/pom.xml @@ -27,8 +27,8 @@ 2.7 5.0.0.GA 2.6.11 - 2.2.6 1.9 + 2.2.6 diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java index 55ed0ea21..817544c96 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidationConfiguration.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; @Configuration +@Deprecated public class OcdsSchemaValidationConfiguration { @Autowired diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java index 395825a3d..63725876f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/spring/OcdsSchemaValidatorService.java @@ -26,6 +26,7 @@ /** * @author mpostelnicu */ +@Deprecated public class OcdsSchemaValidatorService { private final Logger logger = LoggerFactory.getLogger(OcdsSchemaValidatorService.class); private String schemaLocation = null; diff --git a/validator/pom.xml b/validator/pom.xml index 0c19a44c0..d9cde561d 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -18,6 +18,8 @@ UTF-8 org.devgateway.ocds.ValidatorApplication 1.8 + 1.9 + 2.2.6 @@ -48,6 +50,24 @@ jackson-annotations + + com.github.fge + json-patch + ${json.patch.version} + + + + com.github.fge + json-schema-validator + ${json.schema.validator.version} + + + javax.mail + mailapi + + + + org.springframework.boot @@ -55,7 +75,10 @@ test - + + com.fasterxml.jackson.core + jackson-annotations + diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java new file mode 100644 index 000000000..e1d2ba28e --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -0,0 +1,17 @@ +package org.devgateway.ocds.validator; + +/** + * Created by mpostelnicu on 7/5/17. + */ +public final class OcdsValidatorConstants { + + public static final class Versions { + public static final String OCDS_1_0_0 = "1.0.0"; + public static final String OCDS_1_0_1 = "1.0.1"; + public static final String OCDS_1_0_2 = "1.0.2"; + public static final String OCDS_1_1_0 = "1.1.0"; + } + + + +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java new file mode 100644 index 000000000..93fdf5f65 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -0,0 +1,43 @@ +package org.devgateway.ocds.validator; + +import java.util.List; +import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.NotEmpty; + +/** + * Created by mpostelnicu on 7/5/17. + */ +public class OcdsValidatorRequest { + + private String ocdsVersion; + + private List extensions; + + @NotNull(message = "Json content cannot be null!") + @NotEmpty(message = "Json content cannot be empty!") + private String json; + + public String getOcdsVersion() { + return ocdsVersion; + } + + public void setOcdsVersion(String ocdsVersion) { + this.ocdsVersion = ocdsVersion; + } + + public List getExtensions() { + return extensions; + } + + public void setExtensions(List extensions) { + this.extensions = extensions; + } + + public String getJson() { + return json; + } + + public void setJson(String json) { + this.json = json; + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java new file mode 100644 index 000000000..7818e5e4c --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -0,0 +1,29 @@ +package org.devgateway.ocds.validator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.fge.jsonschema.core.report.ProcessingReport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Created by mpostelnicu on 7/5/17. + */ +@Service +public class OcdsValidatorService { + + @Autowired + private ObjectMapper jacksonObjectMapper; + + + + public ProcessingReport validateReleases(OcdsValidatorRequest request) { + return null; + } + + + public ProcessingReport validateRelease(OcdsValidatorRequest request) { + return null; + } + + +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json new file mode 100644 index 000000000..ec856886b --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Bid statistics and details", + "description": "Allowing bid statistics, and detailed bid information to be represented.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json new file mode 100644 index 000000000..4c8853351 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json @@ -0,0 +1,175 @@ +{ + "properties": { + "bids": { + "title": "Bids", + "description": "The bid section is used to publish summary statistics, and where applicable, individual bid information.", + "$ref": "#/definitions/Bids" + } + }, + "definitions": { + "BidsStatistic": { + "title": "Bid Statistic", + "description": "For reporting aggregate statistics about the bids related to a tender. Where lots are in use, statistics may optionally be broken down by lot. ", + "type": [ + "object", + "null" + ], + "required": [ + "id", + "measure", + "value" + ], + "properties": { + "id": { + "title": "ID", + "description": "An internal identifier for this statistical item.", + "type": [ + "string", + "null" + ] + }, + "measure": { + "title": "Measure", + "description": "An item from the bidStatistics codelist for the statisic reported in value. This is an open codelist, and other statistics may also be included.", + "type": [ + "string", + "null" + ], + "codelist": "bidStatistics.csv", + "openCodelist": true + }, + "date": { + "title": "Date", + "description": "The date when this statistic was last updated. This is often the closing date of the tender process. This field can be left blank unless either (a) the same statistic is provided from multiple points in time, or (b) there is a specific local requirement for the data when statistics were calculated to be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "title": "Value", + "description": "The value for the measure in question. Total counts should be provided as an integer. Percentages should be presented as a proportion of 1 (e.g. 10% = 0.1)", + "type": [ + "number", + "null" + ] + }, + "notes": { + "title": "Notes", + "description": "Any notes required to understand or interpret the given statistic.", + "type": [ + "string", + "null" + ] + }, + "relatedLot": { + "title": "Related Lot", + "description": "Where lots are in use, if this statistic relates to bids on a particular lot, provide the lot identifier here. If left blank, the statistic will be interpreted as applying to the whole tender.", + "type": [ + "string", + "null" + ] + } + } + }, + "Bids": { + "title": "Bids", + "description": "Summary and detailed information about bids received and evaluated as part of this contracting process.", + "type": "object", + "properties": { + "statistics": { + "title": "Statistics", + "description": "Summary statistics on the number and nature of bids received. Where information is provided on individual bids, these statistics should match those that can be calculated from the bid details array.", + "type": [ + "array" + ], + "items": { + "$ref": "#/definitions/BidsStatistic" + } + }, + "details": { + "title": "Bid details", + "description": "An array of bids, providing information on the bidders, and where applicable, bid status, bid values and related documents. The extent to which this information can be disclosed varies from jurisdiction to jurisdiction.", + "type": "array", + "required": [ + "id" + ], + "items": { + "$ref": "#/definitions/Bid" + } + } + } + }, + "Bid": { + "title": "Bid", + "description": "For representing a bid in response to the tender or qualification stage in this contracting process.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier for this bid", + "type": [ + "string" + ] + }, + "date": { + "title": "Date", + "description": "The date when this bid was received.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "status": { + "title": "Status", + "description": "The status of the bid, drawn from the bidStatus codelist", + "type": [ + "string", + "null" + ], + "codelist": "bidStatus.csv", + "openCodelist": false + }, + "tenderers": { + "title": "Tenderer", + "description": "The party, or parties, responsible for this bid. This should provide a name and identifier, cross-referenced to an entry in the parties array at the top level of the release.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/OrganizationReference" + } + }, + "value": { + "title": "Value", + "description": "The total value of the bid.", + "$ref": "#/definitions/Value" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the bid and its evaluation.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "requirementResponses": { + "title": "Requirement responses", + "description": "If the requirements extension is also in use, the detailed responses of this bid to the tender requirements can be specified here.", + "type": "array", + "items": { + "$ref": "#/definitions/RequirementResponse" + }, + "unqiueItems": true + } + } + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json new file mode 100644 index 000000000..c857c4aaa --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Enquiries", + "description": "The enquiries extension can be used to record questions raised during a contracting process, and the answers provided.", + "compatibility": ">=1.0.0", + "dependencies": [""] +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json new file mode 100644 index 000000000..492a89e6d --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json @@ -0,0 +1,70 @@ +{ + "definitions": { + "Tender": { + "properties": { + "enquiries": { + "title":"Enquiries", + "type": ["array","null"], + "description": "Questions sent to the procuring entity, and the answers given", + "items": { + "$ref": "#/definitions/Enquiry" + } + } + } + }, + "Enquiry": { + "type": "object", + "title":"Enquiry", + "description": "A question related to this contracting process, generally sent during the enquiry period.", + "properties": { + "id": { + "title":"Identifier", + "description": "A unique identifier for the enquiry.", + "type": ["string","null"] + }, + "date": { + "type": "string", + "title":"Date", + "description": "The date the enquiry was received or processed.", + "format":"date-time" + }, + "author": { + "title":"Question author", + "description": "The identifier and name of the party asking this question. Questioners may be listed in the parties array with a role of 'enquirer'. Procurement policies vary on whether or not the identity of those asking questions should be disclosed, or at which stage this information may be disclosed.", + "$ref": "#/definitions/Organization" + }, + "title": { + "title":"Question title", + "description": "The subject line of the question.", + "type": ["string","null"] + }, + "description": { + "title":"Description", + "description": "The body of the question.", + "type": ["string","null"] + }, + "answer": { + "title":"Answer", + "description": "The answer to this question, when available.", + "type": ["string","null"] + }, + "dateAnswered": { + "title":"Date answered", + "description": "The date the answer to the question was provided.", + "type": ["string","null"], + "format":"date-time" + }, + "relatedItem":{ + "title":"Related item", + "description":"If this question relates to a specific line-item, this field contains the line-item identifier.", + "type": ["string","null"] + }, + "relatedLot":{ + "title":"Related lot", + "description":"Where lots are used, if this question relates to a specific lot, this field contains the lot identifier.", + "type": ["string","null"] + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json new file mode 100644 index 000000000..63a1fdc69 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json @@ -0,0 +1,60 @@ +{ + "id": "http://ocds.open-contracting.org/standard/r/1__0__0/release-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release Package", + "description": "Note that all releases within a release package must have a unique releaseID within this release package.", + "type": "object", + "required": ["uri", "publisher", "publishedDate", "releases"], + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published. Ideally this should be the latest date that there is release information in this package.", + "type": "string", + "format": "date-time" + }, + "releases": { + "type": "array", + "minItems": 1, + "items": { "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" }, + "uniqueItems": true + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "scheme": { + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"], + "format": "uri" + }, + "uid": { + "description": "The unique ID for this entity under the given ID scheme.", + "type": ["string", "null"] + }, + "uri": { + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this datapackage. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers [publication policy](http://ocds.open-contracting.org/standard/r/1__0__0/en/implementation/publication_patterns/#publication-policy).", + "type": ["string", "null"], + "format": "uri" + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json new file mode 100644 index 000000000..9a6be76b1 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json @@ -0,0 +1,65 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__1/release-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release Package", + "description": "Note that all releases within a release package must have a unique releaseID within this release package.", + "type": "object", + "required": ["uri", "publisher", "publishedDate", "releases"], + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published. Ideally this should be the latest date that there is release information in this package.", + "type": "string", + "format": "date-time" + }, + "releases": { + "type": "array", + "minItems": 1, + "items": { "$ref": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json" }, + "uniqueItems": true + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title":"Name", + "description":"The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title":"Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"] + }, + "uid": { + "title":"uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": ["string", "null"] + }, + "uri": { + "title":"URI", + "description":"A URI to identify the publisher.", + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers [publication policy](http://standard.open-contracting.org/latest/en/implementation/publication_policy/).", + "type": ["string", "null"], + "format": "uri" + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json new file mode 100644 index 000000000..783f9bb7b --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json @@ -0,0 +1,65 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__2/release-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release Package", + "description": "Note that all releases within a release package must have a unique releaseID within this release package.", + "type": "object", + "required": ["uri", "publisher", "publishedDate", "releases"], + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published. Ideally this should be the latest date that there is release information in this package.", + "type": "string", + "format": "date-time" + }, + "releases": { + "type": "array", + "minItems": 1, + "items": { "$ref": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json" }, + "uniqueItems": true + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title":"Name", + "description":"The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title":"Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"] + }, + "uid": { + "title":"uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": ["string", "null"] + }, + "uri": { + "title":"URI", + "description":"A URI to identify the publisher.", + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers [publication policy](http://standard.open-contracting.org/latest/en/implementation/publication_policy/).", + "type": ["string", "null"], + "format": "uri" + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json new file mode 100644 index 000000000..120535476 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json @@ -0,0 +1,110 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__1__0/release-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release Package", + "description": "Note that all releases within a release package must have a unique releaseID within this release package.", + "type": "object", + "required": [ + "uri", + "publisher", + "publishedDate", + "releases", + "version" + ], + "properties": { + "uri": { + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world. Recommended practice is to use a deferenceable URI, where a persistent copy of this package is available.", + "type": "string", + "format": "uri" + }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a url to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "publishedDate": { + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", + "type": "string", + "format": "date-time" + }, + "releases": { + "title": "Releases", + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json" + }, + "uniqueItems": true + }, + "publisher": { + "title": "Publisher", + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": [ + "string", + "null" + ] + }, + "uid": { + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "name" + ] + }, + "license": { + "title": "License", + "description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publicationPolicy": { + "title": "Publication policy", + "description": "A link to a document describing the publishers [publication policy](http://standard.open-contracting.org/latest/en/implementation/publication_policy/).", + "type": [ + "string", + "null" + ], + "format": "uri" + } + } +} \ No newline at end of file diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json new file mode 100644 index 000000000..72c13945d --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json @@ -0,0 +1,989 @@ +{ + "id": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#ocid)", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "id": { + "title": "Release ID", + "description": "A unique identifier that identifies this release. A releaseID must be unique within a release-package and must not contain the # character.", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "date": { + "title": "Release Date", + "description": "The date this information is released, it may well be the same as the parent publishedDate, it must not be later than the publishedDate from the parent package. It is used to determine merge order.", + "type": "string", + "format": "date-time", + "mergeStrategy": "ocdsOmit" + }, + "tag": { + "title": "Release Tag", + "description": "A value from the [releaseTag codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#release-tag) that identifies the nature of the release being made. Tags may be used to filter release, or, in future, for for advanced validation when certain kinds of releases should contain certain fields.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + }, + "mergeStrategy": "ocdsOmit" + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": ["tender"], + "mergeStrategy": "ocdsVersion" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract for, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", + "$ref": "#/definitions/Organization" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": { "$ref": "#/definitions/Award" }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": {"$ref": "#/definitions/Contract" }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. The use of two-letter codes from ISO 639-1 is strongly recommended.", + "type": ["string", "null"], + "default": "en", + "mergeStrategy": "ocdsVersion" + } + }, + "required": ["ocid", "id", "date", "tag", "initiationType"], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "budget": { "$ref": "#/definitions/Budget" }, + "rationale": { + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "A list of documents related to the planning process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "Tender title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Tender description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Tender Status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status)", + "type": ["string", "null"], + "enum": ["planned", "active", "cancelled", "unsuccessful", "complete", null], + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "minValue": { + "description": "The minimum estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "value": { + "description": "The total upper estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "description": "Specify tendering method against the [method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#method) as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited", + "type": ["string", "null"], + "enum": ["open", "selective", "limited", null], + "mergeStrategy": "ocdsVersion" + }, + "procurementMethodRationale": { + "description": "Rationale of procurement method, especially in the case of Limited tendering.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteria": { + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#award-criteria)", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteriaDetails": { + "description": "Any detailed or further information on the award or selection criteria.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "submissionMethod": { + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#submission-method)", + "type": ["array", "null"], + "items": { + "type": "string" + }, + "mergeStrategy": "ocdsVersion" + }, + "submissionMethodDetails" : { + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderPeriod": { + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "description": "The period during which enquiries may be made and answered.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "description": "A Yes/No field to indicate whether enquiries were part of tender process.", + "type": ["boolean", "null"], + "mergeStrategy": "ocdsVersion" + }, + "eligibilityCriteria": { + "description": "A description of any eligibility criteria for potential suppliers.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardPeriod": { + "description": "The date or period on which an award is anticipated to be made.", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "definition": "The number of entities who submit a tender.", + "type": ["integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderers": { + "description": "All entities who submit a tender.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "procuringEntity": { + "description": "The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.", + "$ref": "#/definitions/Organization" + }, + "documents": { + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type) for details of potential documents to include.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + }, + "milestones": { + "description": "A list of milestones associated with the tender.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" } + }, + "amendment": { "$ref": "#/definitions/Amendment" } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "description": "Award title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Award description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Award Status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#award-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "unsuccessful"], + "mergeStrategy": "ocdsVersion" + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "value": { + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement.", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items Awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "contractPeriod": { + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": ["id", "awardID"], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "Contract title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Contract description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Contract Status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#contract-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "terminated"], + "mergeStrategy": "ocdsVersion" + }, + "period": { + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "description": "The total value of this contract.", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items Contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "dateSigned": { + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "description": "A list of the spending transactions made against this contract", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Transaction" }, + "uniqueItems": true + }, + "milestones": { + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" }, + "uniqueItems": true + }, + "documents":{ + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + } + }, + "Milestone": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "description": "Milestone title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A description of the milestone.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "dueDate": { + "description": "The date the milestone is due.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified" : { + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "status": { + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#milestone-status).", + "type": ["string", "null"], + "enum": ["met", "notMet", "partiallyMet", null], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "List of documents associated with this milestone.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": ["id"], + "properties": { + "id": { + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "documentType": { + "description": "A classification of the document described taken from the [documentType codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "The document title.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "url": { + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": ["string", "null"], + "format": "uri", + "mergeStrategy": "ocdsVersion" + }, + "datePublished": { + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified": { + "description": "Date that the document was last modified", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "format": { + "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "language": { + "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget Information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "mergeStrategy": "ocdsVersion", + "properties": { + "source": { + "title": "Data Source", + "description": "Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type":["string", "null"], + "mergeStrategy": "ocdsVersion", + "format": "uri" + }, + "id":{ + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "mergeStrategy": "ocdsVersion", + "type":["string", "integer", "null"] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "amount": { + "description": "The value of the budget line item.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project Title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "projectID": { + "title": "Project Identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "integer", "null"] + }, + "uri":{ + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the related budget or projects for this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction Information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": ["id"], + "properties": { + "id":{ + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For the budget data package this is the id, for IATI, the transaction reference.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "source": { + "title": "Data Source", + "description": "Used to point either to a corresponding Budget Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + }, + "date": { + "description": "The date of the transaction", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "date-time" + }, + "amount": { + "description": "The value of the transaction.", + "$ref": "#/definitions/Value" + }, + "providerOrganization":{ + "description": "The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "receiverOrganization":{ + "description": "The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "uri":{ + "title":"Linked spending information", + "description":"A URI pointing directly to a machine-readable record about this spending transaction.", + "mergeStrategy": "ocdsVersion", + "type":["string", "null"], + "format":"uri" + } + } + }, + "Organization": { + "title": "Organization", + "description": "An organization.", + "type": "object", + "properties": { + "identifier": { + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" + }, + "additionalIdentifiers": { + "description": "A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/identifiers/#organization-identifiers). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Identifier" }, + "uniqueItems": true + }, + "name": { + "description": "The common name of the organization. The ID property provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization is known. This field could also include details of the department or sub-unit involved in this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "address": { "$ref": "#/definitions/Address" }, + "contactPoint": { "$ref": "#/definitions/ContactPoint" } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Item": { + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": ["id"], + "properties": { + "id": { + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "description": { + "description": "A description of the goods, services to be provided.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "classification": { + "description": "The primary classification for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" + }, + "additionalClassifications": { + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Classification" }, + "uniqueItems": true + }, + "quantity": { + "description": "The number of units required", + "mergeStrategy": "ocdsVersion", + "minimum": 0, + "type": ["integer", "null"] + }, + "unit": { + "description": "Description of the unit which the good comes in e.g. hours, kilograms. Made up of a unit name, and the value of a single unit.", + "type": "object", + "properties": { + "name": { + "description": "Name of the unit", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "value": { + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Amendment": { + "type": "object", + "title": "Amendment information", + "properties": { + "date": { + "title": "Amendment Date", + "description":"The data of this amendment.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "changes": { + "title": "Amended fields", + "description": "Comma-seperated list of affected fields.", + "mergeStrategy": "ocdsVersion", + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. ", + "type": "string" + }, + "former_value": { + "description": "The previous value of the changed property, in whatever type the property is.", + "type": ["string", "number", "integer", "array", "object", "null"] + } + } + } + }, + "rationale": { + "description": "An explanation for the amendment.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Classification": { + "type": "object", + "properties": { + "scheme": { + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#item-classification-scheme) wherever possible.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "description": "The classification code drawn from the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A textual description or title for the code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Identifier": { + "type": "object", + "properties": { + "scheme": { + "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists/#organization-identifier-scheme).", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "description": "The identifier of the organization in the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "legalName": { + "description": "The legally registered name of the organization.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Address": { + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "type": ["string", "null"], + "description": "The street address. For example, 1600 Amphitheatre Pkwy.", + "mergeStrategy": "ocdsVersion" + }, + "locality":{ + "type": ["string", "null"], + "description": "The locality. For example, Mountain View.", + "mergeStrategy": "ocdsVersion" + }, + "region": { + "type": ["string", "null"], + "description":"The region. For example, CA.", + "mergeStrategy": "ocdsVersion" + }, + "postalCode": { + "type": ["string", "null"], + "description":"The postal code. For example, 94043.", + "mergeStrategy": "ocdsVersion" + }, + "countryName": { + "type": ["string", "null"], + "description":"The country name. For example, United States.", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "ContactPoint": { + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "type": ["string", "null"], + "description":"The name of the contact person, department, or contact point, for correspondence relating to this contracting process.", + "mergeStrategy": "ocdsVersion" + }, + "email":{ + "type": ["string", "null"], + "description":"The e-mail address of the contact point/person.", + "mergeStrategy": "ocdsVersion" + }, + "telephone": { + "type": ["string", "null"], + "description":"The telephone number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "faxNumber": { + "type": ["string", "null"], + "description":"The fax number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "url": { + "type": ["string", "null"], + "description":"A web address for the contact point/person.", + "format": "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Value": { + "type": "object", + "properties": { + "amount": { + "description": "Amount as a number.", + "type": ["number", "null"], + "minimum": 0, + "mergeStrategy": "ocdsVersion" + }, + "currency": { + "description": "The currency in 3-letter ISO 4217 format.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + } + }, + "Period": { + "type": "object", + "title": "Period", + "properties": { + "startDate": { + "description": "The start date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "endDate": { + "description": "The end date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + } + } + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json new file mode 100644 index 000000000..ccb60b643 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json @@ -0,0 +1,988 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/)", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "id": { + "title": "Release ID", + "description": "A unique identifier that identifies this release. A release ID must be unique within a release-package and must not contain the # character.", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "date": { + "title": "Release Date", + "description": "The date this information is released, it may well be the same as the parent publishedDate, it must not be later than the publishedDate from the parent package. It is used to determine merge order.", + "type": "string", + "format": "date-time", + "mergeStrategy": "ocdsOmit" + }, + "tag": { + "title": "Release Tag", + "description": "A value from the [releaseTag codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#release-tag) that identifies the nature of the release being made. Tags may be used to filter release, or, in future, for for advanced validation when certain kinds of releases should contain certain fields.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + }, + "mergeStrategy": "ocdsOmit" + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": ["tender"], + "mergeStrategy": "ocdsVersion" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract for, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", + "$ref": "#/definitions/Organization" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": { "$ref": "#/definitions/Award" }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": {"$ref": "#/definitions/Contract" }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. The use of two-letter codes from ISO 639-1 is strongly recommended.", + "type": ["string", "null"], + "default": "en", + "mergeStrategy": "ocdsVersion" + } + }, + "required": ["ocid", "id", "date", "tag", "initiationType"], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "budget": { "$ref": "#/definitions/Budget" }, + "rationale": { + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "A list of documents related to the planning process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "Tender title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Tender description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Tender Status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#tender-status)", + "type": ["string", "null"], + "enum": ["planned", "active", "cancelled", "unsuccessful", "complete", null], + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "minValue": { + "description": "The minimum estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "value": { + "description": "The total upper estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "description": "Specify tendering method against the [method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#method) as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited", + "type": ["string", "null"], + "enum": ["open", "selective", "limited", null], + "mergeStrategy": "ocdsVersion" + }, + "procurementMethodRationale": { + "description": "Rationale of procurement method, especially in the case of Limited tendering.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteria": { + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-criteria)", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteriaDetails": { + "description": "Any detailed or further information on the award or selection criteria.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "submissionMethod": { + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method)", + "type": ["array", "null"], + "items": { + "type": "string" + }, + "mergeStrategy": "ocdsVersion" + }, + "submissionMethodDetails" : { + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderPeriod": { + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "description": "The period during which enquiries may be made and answered.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "description": "A Yes/No field to indicate whether enquiries were part of tender process.", + "type": ["boolean", "null"], + "mergeStrategy": "ocdsVersion" + }, + "eligibilityCriteria": { + "description": "A description of any eligibility criteria for potential suppliers.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardPeriod": { + "description": "The date or period on which an award is anticipated to be made.", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "definition": "The number of entities who submit a tender.", + "type": ["integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderers": { + "description": "All entities who submit a tender.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "procuringEntity": { + "description": "The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.", + "$ref": "#/definitions/Organization" + }, + "documents": { + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential documents to include.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + }, + "milestones": { + "description": "A list of milestones associated with the tender.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" } + }, + "amendment": { "$ref": "#/definitions/Amendment" } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "description": "Award title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Award description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Award Status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "unsuccessful", null], + "mergeStrategy": "ocdsVersion" + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "value": { + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement.", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items Awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "contractPeriod": { + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": ["id", "awardID"], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "Contract title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "Contract description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Contract Status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#contract-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "terminated", null], + "mergeStrategy": "ocdsVersion" + }, + "period": { + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "description": "The total value of this contract.", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items Contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "dateSigned": { + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "description": "A list of the spending transactions made against this contract", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Transaction" }, + "uniqueItems": true + }, + "milestones": { + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" }, + "uniqueItems": true + }, + "documents":{ + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + } + }, + "Milestone": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "description": "Milestone title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A description of the milestone.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "dueDate": { + "description": "The date the milestone is due.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified" : { + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "status": { + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status).", + "type": ["string", "null"], + "enum": ["met", "notMet", "partiallyMet", null], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "description": "List of documents associated with this milestone.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": ["id"], + "properties": { + "id": { + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "documentType": { + "description": "A classification of the document described taken from the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "The document title.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "url": { + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": ["string", "null"], + "format": "uri", + "mergeStrategy": "ocdsVersion" + }, + "datePublished": { + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified": { + "description": "Date that the document was last modified", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "format": { + "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "language": { + "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget Information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "properties": { + "source": { + "title": "Data Source", + "description": "Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type":["string", "null"], + "mergeStrategy": "ocdsVersion", + "format": "uri" + }, + "id":{ + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "mergeStrategy": "ocdsVersion", + "type":["string", "integer", "null"] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "amount": { + "description": "The value of the budget line item.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project Title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "projectID": { + "title": "Project Identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "integer", "null"] + }, + "uri":{ + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the related budget or projects for this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction Information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Budget Data Package](https://github.com/openspending/budget-data-package/blob/master/specification.md) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": ["id"], + "properties": { + "id":{ + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For the budget data package this is the id, for IATI, the transaction reference.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "source": { + "title": "Data Source", + "description": "Used to point either to a corresponding Budget Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + }, + "date": { + "description": "The date of the transaction", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "date-time" + }, + "amount": { + "description": "The value of the transaction.", + "$ref": "#/definitions/Value" + }, + "providerOrganization":{ + "description": "The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "receiverOrganization":{ + "description": "The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "uri":{ + "title":"Linked spending information", + "description":"A URI pointing directly to a machine-readable record about this spending transaction.", + "mergeStrategy": "ocdsVersion", + "type":["string", "null"], + "format":"uri" + } + } + }, + "Organization": { + "title": "Organization", + "description": "An organization.", + "type": "object", + "properties": { + "identifier": { + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" + }, + "additionalIdentifiers": { + "description": "A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Identifier" }, + "uniqueItems": true + }, + "name": { + "description": "The common name of the organization. The ID property provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization is known. This field could also include details of the department or sub-unit involved in this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "address": { "$ref": "#/definitions/Address" }, + "contactPoint": { "$ref": "#/definitions/ContactPoint" } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Item": { + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": ["id"], + "properties": { + "id": { + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "description": { + "description": "A description of the goods, services to be provided.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "classification": { + "description": "The primary classification for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" + }, + "additionalClassifications": { + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Classification" }, + "uniqueItems": true + }, + "quantity": { + "description": "The number of units required", + "mergeStrategy": "ocdsVersion", + "minimum": 0, + "type": ["integer", "null"] + }, + "unit": { + "description": "Description of the unit which the good comes in e.g. hours, kilograms. Made up of a unit name, and the value of a single unit.", + "type": "object", + "properties": { + "name": { + "description": "Name of the unit", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "value": { + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Amendment": { + "type": "object", + "title": "Amendment information", + "properties": { + "date": { + "title": "Amendment Date", + "description":"The data of this amendment.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "changes": { + "title": "Amended fields", + "description": "Comma-seperated list of affected fields.", + "mergeStrategy": "ocdsVersion", + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. ", + "type": "string" + }, + "former_value": { + "description": "The previous value of the changed property, in whatever type the property is.", + "type": ["string", "number", "integer", "array", "object", "null"] + } + } + } + }, + "rationale": { + "description": "An explanation for the amendment.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Classification": { + "type": "object", + "properties": { + "scheme": { + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) wherever possible.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "description": "The classification code drawn from the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "description": "A textual description or title for the code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Identifier": { + "type": "object", + "properties": { + "scheme": { + "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#organization-identifier-scheme).", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "description": "The identifier of the organization in the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "legalName": { + "description": "The legally registered name of the organization.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Address": { + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "type": ["string", "null"], + "description": "The street address. For example, 1600 Amphitheatre Pkwy.", + "mergeStrategy": "ocdsVersion" + }, + "locality":{ + "type": ["string", "null"], + "description": "The locality. For example, Mountain View.", + "mergeStrategy": "ocdsVersion" + }, + "region": { + "type": ["string", "null"], + "description":"The region. For example, CA.", + "mergeStrategy": "ocdsVersion" + }, + "postalCode": { + "type": ["string", "null"], + "description":"The postal code. For example, 94043.", + "mergeStrategy": "ocdsVersion" + }, + "countryName": { + "type": ["string", "null"], + "description":"The country name. For example, United States.", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "ContactPoint": { + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "type": ["string", "null"], + "description":"The name of the contact person, department, or contact point, for correspondence relating to this contracting process.", + "mergeStrategy": "ocdsVersion" + }, + "email":{ + "type": ["string", "null"], + "description":"The e-mail address of the contact point/person.", + "mergeStrategy": "ocdsVersion" + }, + "telephone": { + "type": ["string", "null"], + "description":"The telephone number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "faxNumber": { + "type": ["string", "null"], + "description":"The fax number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "url": { + "type": ["string", "null"], + "description":"A web address for the contact point/person.", + "format": "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Value": { + "type": "object", + "properties": { + "amount": { + "description": "Amount as a number.", + "type": ["number", "null"], + "minimum": 0, + "mergeStrategy": "ocdsVersion" + }, + "currency": { + "description": "The currency in 3-letter ISO 4217 format.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + } + }, + "Period": { + "type": "object", + "title": "Period", + "properties": { + "startDate": { + "description": "The start date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "endDate": { + "description": "The end date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + } + } + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json new file mode 100644 index 000000000..f6afc46cc --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json @@ -0,0 +1,1097 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/)", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "id": { + "title": "Release ID", + "description": "A unique identifier that identifies this release. A release ID must be unique within a release-package and must not contain the # character.", + "type": "string", + "mergeStrategy": "ocdsOmit" + }, + "date": { + "title": "Release Date", + "description": "The date this information is released, it may well be the same as the parent publishedDate, it must not be later than the publishedDate from the parent package. It is used to determine merge order.", + "type": "string", + "format": "date-time", + "mergeStrategy": "ocdsOmit" + }, + "tag": { + "title": "Release Tag", + "description": "A value from the [releaseTag codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#release-tag) that identifies the nature of the release being made. Tags may be used to filter release, or, in future, for advanced validation when certain kinds of releases should contain certain fields.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + }, + "mergeStrategy": "ocdsOmit" + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://standard.open-contracting.org/latest/en/schema/codelists/#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": ["tender"], + "mergeStrategy": "ocdsVersion" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract for, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", + "$ref": "#/definitions/Organization" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": { "$ref": "#/definitions/Award" }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": {"idRef": "id"}, + "items": {"$ref": "#/definitions/Contract" }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-digit ISO 639-1, or extended BCP47 language tags. The use of two-letter codes from ISO 639-1 is strongly recommended.", + "type": ["string", "null"], + "default": "en", + "mergeStrategy": "ocdsVersion" + } + }, + "required": ["ocid", "id", "date", "tag", "initiationType"], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "budget": { "$ref": "#/definitions/Budget" }, + "rationale": { + "title":"Rationale", + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "title":"Documents", + "description": "A list of documents related to the planning process.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "title":"Tender Title", + "description": "Tender title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Tender description", + "description": "Tender description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Tender status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#tender-status)", + "type": ["string", "null"], + "enum": ["planned", "active", "cancelled", "unsuccessful", "complete", null], + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "minValue": { + "title":"Minimum value", + "description": "The minimum estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "value": { + "title":"Value", + "description": "The total upper estimated value of the procurement.", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "title":"Procurement method", + "description": "Specify tendering method against the [method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#method) as per [GPA definitions](http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm) of Open, Selective, Limited", + "type": ["string", "null"], + "enum": ["open", "selective", "limited", null], + "mergeStrategy": "ocdsVersion" + }, + "procurementMethodDetails": { + "title":"Procurement method details", + "description": "Additional detail on the procurement method used. This field may be used to provide the local name of the particular procurement method used.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "procurementMethodRationale": { + "title":"Procurement method rationale", + "description": "Rationale of procurement method, especially in the case of Limited tendering.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteria": { + "title":"Award criteria", + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-criteria)", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardCriteriaDetails": { + "title":"Award criteria details", + "description": "Any detailed or further information on the award or selection criteria.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "submissionMethod": { + "title":"Submission method", + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#submission-method)", + "type": ["array", "null"], + "items": { + "type": "string" + }, + "mergeStrategy": "ocdsVersion" + }, + "submissionMethodDetails" : { + "title":"Submission method details", + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderPeriod": { + "title":"Tender period", + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "title":"Enquiry period", + "description": "The period during which enquiries may be made and answered.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "title":"Has enquiries?", + "description": "A Yes/No field to indicate whether enquiries were part of tender process.", + "type": ["boolean", "null"], + "mergeStrategy": "ocdsVersion" + }, + "eligibilityCriteria": { + "title":"Eligibility criteria", + "description": "A description of any eligibility criteria for potential suppliers.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "awardPeriod": { + "title":"Award period", + "description": "The date or period on which an award is anticipated to be made.", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "title":"Number of tenderers", + "description": "The number of entities who submit a tender.", + "type": ["integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "tenderers": { + "title":"Tenderers", + "description": "All entities who submit a tender.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "procuringEntity": { + "title":"Procuring entity", + "description": "The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.", + "$ref": "#/definitions/Organization" + }, + "documents": { + "title":"Documents", + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type) for details of potential documents to include.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" } + }, + "milestones": { + "title":"Milestones", + "description": "A list of milestones associated with the tender.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" } + }, + "amendment": { "$ref": "#/definitions/Amendment" } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "title":"Title", + "description": "Award title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Description", + "description": "Award description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Award status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#award-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "unsuccessful", null], + "mergeStrategy": "ocdsVersion" + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "value": { + "title":"Value", + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement.", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "title":"Suppliers", + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { "$ref": "#/definitions/Organization" }, + "uniqueItems": true, + "mergeStrategy": "ocdsVersion" + }, + "items": { + "title": "Items awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "contractPeriod": { + "title":"Contract period", + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "title":"Documents", + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": ["id", "awardID"], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for further details.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": ["string", "integer"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "description": "Contract title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Contract description", + "description": "Contract description", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title": "Contract status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#contract-status)", + "type": ["string", "null"], + "enum": ["pending", "active", "cancelled", "terminated", null], + "mergeStrategy": "ocdsVersion" + }, + "period": { + "title":"Period", + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "title":"Value", + "description": "The total value of this contract.", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Item" }, + "uniqueItems": true + }, + "dateSigned": { + "title":"Date signed", + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "title":"Documents", + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + }, + "amendment": { + "$ref": "#/definitions/Amendment" + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "title":"Transactions", + "description": "A list of the spending transactions made against this contract", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Transaction" }, + "uniqueItems": true + }, + "milestones": { + "title":"Milestones", + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Milestone" }, + "uniqueItems": true + }, + "documents":{ + "title":"Documents", + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + } + }, + "Milestone": { + "title":"Milestone", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "title":"ID", + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "title": { + "title":"Title", + "description": "Milestone title", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Description", + "description": "A description of the milestone.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "dueDate": { + "title":"Due date", + "description": "The date the milestone is due.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified" : { + "title":"Date modified", + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "status": { + "title":"Status", + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#milestone-status).", + "type": ["string", "null"], + "enum": ["met", "notMet", "partiallyMet", null], + "mergeStrategy": "ocdsVersion" + }, + "documents": { + "title":"Documents", + "description": "List of documents associated with this milestone.", + "type": "array", + "mergeStrategy": "arrayMergeById", + "mergeOptions": { "idRef": "id" }, + "items": { "$ref": "#/definitions/Document" }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": ["id"], + "properties": { + "id": { + "title":"ID", + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "documentType": { + "title":"Document type", + "description": "A classification of the document described taken from the [documentType codelist](http://standard.open-contracting.org/latest/en/schema/codelists/#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "title": { + "title":"Title", + "description": "The document title.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Description", + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "url": { + "title":"URL", + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": ["string", "null"], + "format": "uri", + "mergeStrategy": "ocdsVersion" + }, + "datePublished": { + "title":"Date published", + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "dateModified": { + "title":"Date modified", + "description": "Date that the document was last modified", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "format": { + "title":"Format", + "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "language": { + "title":"Language", + "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal.dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "properties": { + "source": { + "title": "Data Source", + "description": "Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type":["string", "null"], + "mergeStrategy": "ocdsVersion", + "format": "uri" + }, + "id":{ + "title":"ID", + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "mergeStrategy": "ocdsVersion", + "type":["string", "integer", "null"] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "amount": { + "title":"Amount", + "description": "The value of the budget line item.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "projectID": { + "title": "Project identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "integer", "null"] + }, + "uri":{ + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the related budget or projects for this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Fiscal Data Package](http://fiscal.dataprotocols.org/) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Budget Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": ["id"], + "properties": { + "id":{ + "title":"ID", + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For the budget data package this is the id, for IATI, the transaction reference.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "source": { + "title": "Data source", + "description": "Used to point either to a corresponding Budget Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "uri" + }, + "date": { + "title":"Date", + "description": "The date of the transaction", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"], + "format": "date-time" + }, + "amount": { + "title":"Amount", + "description": "The value of the transaction.", + "$ref": "#/definitions/Value" + }, + "providerOrganization":{ + "title":"Provider organization", + "description": "The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "receiverOrganization":{ + "title":"Receiver organization", + "description": "The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier" + }, + "uri":{ + "title":"Linked spending information", + "description":"A URI pointing directly to a machine-readable record about this spending transaction.", + "mergeStrategy": "ocdsVersion", + "type":["string", "null"], + "format":"uri" + } + } + }, + "Organization": { + "title": "Organization", + "description": "An organization.", + "type": "object", + "properties": { + "identifier": { + "title":"Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" + }, + "additionalIdentifiers": { + "title":"Additional identifiers", + "description": "A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/latest/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Identifier" }, + "uniqueItems": true + }, + "name": { + "title":"Common name", + "description": "The common name of the organization. The ID property provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization is known. This field could also include details of the department or sub-unit involved in this contracting process.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "address": { "$ref": "#/definitions/Address" }, + "contactPoint": { "$ref": "#/definitions/ContactPoint" } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Item": { + "title":"Item", + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": ["id"], + "properties": { + "id": { + "title":"ID", + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": ["string", "integer"], + "mergeStrategy": "overwrite" + }, + "description": { + "title":"Description", + "description": "A description of the goods, services to be provided.", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "classification": { + "title":"Classification", + "description": "The primary classification for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" + }, + "additionalClassifications": { + "title":"Additional classifications", + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { "$ref": "#/definitions/Classification" }, + "uniqueItems": true + }, + "quantity": { + "title":"Quantity", + "description": "The number of units required", + "mergeStrategy": "ocdsVersion", + "minimum": 0, + "type": ["integer", "null"] + }, + "unit": { + "title":"Unit", + "description": "Description of the unit which the good comes in e.g. hours, kilograms. Made up of a unit name, and the value of a single unit.", + "type": "object", + "properties": { + "name": { + "title":"Name", + "description": "Name of the unit", + "mergeStrategy": "ocdsVersion", + "type": ["string", "null"] + }, + "value": { + "title":"Value", + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Amendment": { + "title":"Amendment", + "type": "object", + "title": "Amendment information", + "properties": { + "date": { + "title": "Amendment date", + "description":"The data of this amendment.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "changes": { + "title": "Amended fields", + "description": "An array change objects describing the fields changed, and their former values.", + "mergeStrategy": "ocdsVersion", + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "title":"Property", + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. ", + "type": "string" + }, + "former_value": { + "title":"Former Value", + "description": "The previous value of the changed property, in whatever type the property is.", + "type": ["string", "number", "integer", "array", "object", "null"] + } + } + } + }, + "rationale": { + "title":"Rationale", + "description": "An explanation for the amendment.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Classification": { + "title":"Classification", + "type": "object", + "properties": { + "scheme": { + "title":"Scheme", + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#item-classification-scheme) wherever possible.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "title":"ID", + "description": "The classification code drawn from the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "description": { + "title":"Description", + "description": "A textual description or title for the code.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "title":"URI", + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Identifier": { + "title":"Identifier", + "type": "object", + "properties": { + "scheme": { + "title":"Scheme", + "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting.org/latest/en/schema/codelists/#organization-identifier-scheme).", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "id": { + "title":"ID", + "description": "The identifier of the organization in the selected scheme.", + "type": ["string", "integer", "null"], + "mergeStrategy": "ocdsVersion" + }, + "legalName": { + "title":"Legal Name", + "description": "The legally registered name of the organization.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + }, + "uri": { + "title":"URI", + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", + "type": ["string", "null"], + "format" : "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Address": { + "title":"Address", + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "title":"Street address", + "type": ["string", "null"], + "description": "The street address. For example, 1600 Amphitheatre Pkwy.", + "mergeStrategy": "ocdsVersion" + }, + "locality":{ + "title":"Locality", + "type": ["string", "null"], + "description": "The locality. For example, Mountain View.", + "mergeStrategy": "ocdsVersion" + }, + "region": { + "title":"Region", + "type": ["string", "null"], + "description":"The region. For example, CA.", + "mergeStrategy": "ocdsVersion" + }, + "postalCode": { + "title":"Postalcode", + "type": ["string", "null"], + "description":"The postal code. For example, 94043.", + "mergeStrategy": "ocdsVersion" + }, + "countryName": { + "title":"Country name", + "type": ["string", "null"], + "description":"The country name. For example, United States.", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "ContactPoint": { + "title":"Contact point", + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "title":"Name", + "type": ["string", "null"], + "description":"The name of the contact person, department, or contact point, for correspondence relating to this contracting process.", + "mergeStrategy": "ocdsVersion" + }, + "email":{ + "title":"Email", + "type": ["string", "null"], + "description":"The e-mail address of the contact point/person.", + "mergeStrategy": "ocdsVersion" + }, + "telephone": { + "title":"Telephone", + "type": ["string", "null"], + "description":"The telephone number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "faxNumber": { + "title":"Fax number", + "type": ["string", "null"], + "description":"The fax number of the contact point/person. This should include the international dialling code.", + "mergeStrategy": "ocdsVersion" + }, + "url": { + "title":"URL", + "type": ["string", "null"], + "description":"A web address for the contact point/person.", + "format": "uri", + "mergeStrategy": "ocdsVersion" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": ["string", "null"] + } + } + }, + "Value": { + "title":"Value", + "type": "object", + "properties": { + "amount": { + "title":"Amount", + "description": "Amount as a number.", + "type": ["number", "null"], + "minimum": 0, + "mergeStrategy": "ocdsVersion" + }, + "currency": { + "title":"Currency", + "description": "The currency in 3-letter ISO 4217 format.", + "type": ["string", "null"], + "mergeStrategy": "ocdsVersion" + } + } + }, + "Period": { + "title":"Period", + "type": "object", + "properties": { + "startDate": { + "title":"Start date", + "description": "The start date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + }, + "endDate": { + "title":"End date", + "description": "The end date for the period.", + "type": ["string", "null"], + "format": "date-time", + "mergeStrategy": "ocdsVersion" + } + } + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json new file mode 100644 index 000000000..4e3c2573e --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json @@ -0,0 +1,1883 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/)", + "type": "string", + "minLength": 1 + }, + "id": { + "title": "Release ID", + "description": "An identifier for this particular release of information. A release identifier must be unique within the scope of it's related contracting process (defined by a common ocid), and unique within any release package it appears in. A release identifier must not contain the # character.", + "type": "string", + "minLength": 1, + "omitWhenMerged": true + }, + "date": { + "title": "Release Date", + "description": "The date this information was first released, or published.", + "type": "string", + "format": "date-time", + "omitWhenMerged": true + }, + "tag": { + "title": "Release Tag", + "description": "One or more values from the [releaseTag codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#release-tag). Tags may be used to filter release and the understand the kind of information that a release might contain.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + }, + "codelist": "releaseTag.csv", + "openCodelist": false, + "minItems": 1 + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://standard.open-contracting.org/1.1/en/schema/codelists/#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": [ + "tender" + ], + "codelist": "initiationType.csv", + "openCodelist": false + }, + "parties": { + "title": "Parties", + "description": "Information on the parties (organisations, economic operators and other participants) who are involved in the contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references elsewhere in the schema are used to refer back to this entries in this list.", + "type": "array", + "items": { + "$ref": "#/definitions/Organization" + }, + "uniqueItems": true + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring agency who may be specified in the tender data.", + "$ref": "#/definitions/OrganizationReference" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "array", + "items": { + "$ref": "#/definitions/Award" + }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "items": { + "$ref": "#/definitions/Contract" + }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended.", + "type": [ + "string", + "null" + ], + "default": "en" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process follows on from one or more prior process, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to relate mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders to a broad planning process.", + "title": "Related processes", + "type": "array" + } + }, + "required": [ + "ocid", + "id", + "date", + "tag", + "initiationType" + ], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "rationale": { + "title": "Rationale", + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": [ + "string", + "null" + ] + }, + "budget": { + "$ref": "#/definitions/Budget" + }, + "documents": { + "title": "Documents", + "description": "A list of documents related to the planning process.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } + }, + "milestones": { + "title": "Planning milestones", + "description": "A list of milestones associated with the planning stage.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": [ + "string", + "integer" + ], + "minLength": 1, + "versionId": true + }, + "title": { + "title": "Tender title", + "description": "A title for this tender. This will often be used by applications as a headline to attract interest, and to help analysts understand the nature of this procurement.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Tender description", + "description": "A summary description of the tender. This should complement structured information provided using the items array. Descriptions should be short and easy to read. Avoid using ALL CAPS. ", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Tender status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#tender-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "planned", + "active", + "cancelled", + "unsuccessful", + "complete", + null + ], + "codelist": "tenderStatus.csv", + "openCodelist": false + }, + "procuringEntity": { + "title": "Procuring entity", + "description": "The entity managing the procurement. This may be different from the buyer who pays for, or uses, the items being procured.", + "$ref": "#/definitions/OrganizationReference" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "value": { + "title": "Value", + "description": "The total upper estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "title": "Procurement method", + "description": "Specify tendering method using the [method codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "method.csv", + "openCodelist": false + }, + "procurementMethodDetails": { + "title": "Procurement method details", + "description": "Additional detail on the procurement method used. This field may be used to provide the local name of the particular procurement method used.", + "type": [ + "string", + "null" + ] + }, + "procurementMethodRationale": { + "title": "Procurement method rationale", + "description": "Rationale for the chosen procurement method. This is especially important to provide a justification in the case of limited tenders or direct awards.", + "type": [ + "string", + "null" + ] + }, + "mainProcurementCategory": { + "title": "Main procurement category", + "description": "The primary category describing the main object of this contracting process from the [procurementCategory](http://standard.open-contracting.org/1.1/en/schema/codelists/#procurement-category) codelist. This is a closed codelist. Local classifications should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "procurementCategory.csv", + "openCodelist": false + }, + "additionalProcurementCategories": { + "title": "Additional procurement categories", + "description": "Any additional categories which describe the objects of this contracting process, from the [extendedProcurementCategory](http://standard.open-contracting.org/1.1/en/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local categories can be included in this list.", + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "codelist": "extendedProcurementCategory.csv", + "openCodelist": true + }, + "awardCriteria": { + "title": "Award criteria", + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#award-criteria)", + "type": [ + "string", + "null" + ], + "codelist": "awardCriteria.csv", + "openCodelist": true + }, + "awardCriteriaDetails": { + "title": "Award criteria details", + "description": "Any detailed or further information on the award or selection criteria.", + "type": [ + "string", + "null" + ] + }, + "submissionMethod": { + "title": "Submission method", + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#submission-method)", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "codelist": "submissionMethod.csv", + "openCodelist": true + }, + "submissionMethodDetails": { + "title": "Submission method details", + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": [ + "string", + "null" + ] + }, + "tenderPeriod": { + "title": "Tender period", + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "title": "Enquiry period", + "description": "The period during which potential bidders may submit questions and requests for clarification to the entity managing procurement. Details of how to submit enquiries should be provided in attached notices, or in submissionMethodDetails. Structured dates for when responses to questions will be made can be provided using tender milestones.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "title": "Has enquiries?", + "description": "A true/false field to indicate whether any enquiries were received during the tender process. Structured information on enquiries that were received, and responses to them, can be provided using the enquiries extension.", + "type": [ + "boolean", + "null" + ] + }, + "eligibilityCriteria": { + "title": "Eligibility criteria", + "description": "A description of any eligibility criteria for potential suppliers.", + "type": [ + "string", + "null" + ] + }, + "awardPeriod": { + "title": "Evaluation and award period", + "description": "The period for decision making regarding the contract award. The end date should be the date on which an award decision is due to be finalised. The start date is optional, and may be used to indicate the start of an evaluation period.", + "$ref": "#/definitions/Period" + }, + "contractPeriod": { + "description": "The period over which the contract is estimated or required to be active. If the tender does not specify explicit dates, the duration field may be used.", + "title": "Contract period", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "title": "Number of tenderers", + "description": "The number of parties who submit a bid.", + "type": [ + "integer", + "null" + ] + }, + "tenderers": { + "title": "Tenderers", + "description": "All parties who submit a bid on a tender. More detailed information on bids and the bidding organisation can be provided using the optional bid extension.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" + }, + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#document-type) for details of potential documents to include. Common documents include official legal notices of tender, technical specifications, evaluation criteria, and, as a tender process progresses, clarifications and replies to queries.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } + }, + "milestones": { + "title": "Milestones", + "description": "A list of milestones associated with the tender.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A tender amendment is a formal change to the tender, and generally involves the publication of a new tender notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the ammendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an ammendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split amongst different providers, or because it is a standing offer.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Award title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "Award description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Award status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#award-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "unsuccessful", + null + ], + "codelist": "awardStatus.csv", + "openCodelist": false + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "title": "Value", + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement. A negative value indicates that the award may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "title": "Suppliers", + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" + }, + "uniqueItems": true + }, + "items": { + "title": "Items awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "contractPeriod": { + "title": "Contract period", + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "amendments": { + "description": "An award amendment is a formal change to the details of the award, and generally involves the publication of a new award notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the ammendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an ammendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": [ + "id", + "awardID" + ], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Contract title", + "description": "Contract title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Contract description", + "description": "Contract description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Contract status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#contract-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "terminated", + null + ], + "codelist": "contractStatus.csv", + "openCodelist": false + }, + "period": { + "title": "Period", + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "title": "Value", + "description": "The total value of this contract. A negative value indicates that the contract will involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "dateSigned": { + "title": "Date signed", + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process is followed by one or more contracting processes, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to point to subcontracts, or to renewal and replacement processes for this contract.", + "title": "Related processes", + "type": "array" + }, + "milestones": { + "title": "Contract milestones", + "description": "A list of milestones associated with the finalisation of this contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A contract amendment is a formal change to, or extension of, a contract, and generally involves the publication of a new contract notice/release, or some other documents detailing the change. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the ammendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an ammendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "title": "Transactions", + "description": "A list of the spending transactions made against this contract", + "type": "array", + "items": { + "$ref": "#/definitions/Transaction" + }, + "uniqueItems": true + }, + "milestones": { + "title": "Milestones", + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + }, + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + } + } + }, + "Milestone": { + "title": "Milestone", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Milestone title", + "type": [ + "string", + "null" + ] + }, + "type": { + "title": "Milestone type", + "description": "The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#milestone-type).", + "type": [ + "string", + "null" + ], + "enum": [ + "preProcurement", + "engagement", + "approval", + "assessment", + "delivery", + "reporting", + "financing", + null + ], + "codelist": "milestoneType.csv", + "opencodelist": true + }, + "description": { + "title": "Description", + "description": "A description of the milestone.", + "type": [ + "string", + "null" + ] + }, + "code": { + "title": "Milestone code", + "description": "Milestone codes can be used to track specific events that take place for a particular kind of contracting process. For example, a code of 'approvalLetter' could be used to allow applications to understand this milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and codes should be agreed amoungst data producers and the applications using that data.", + "type": [ + "string", + "null" + ] + }, + "dueDate": { + "title": "Due date", + "description": "The date the milestone is due.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateMet": { + "format": "date-time", + "title": "Date met", + "description": "The date on which the milestone was met.", + "type": [ + "string", + "null" + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "status": { + "title": "Status", + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#milestone-status).", + "type": [ + "string", + "null" + ], + "enum": [ + "scheduled", + "met", + "notMet", + "partiallyMet", + null + ], + "codelist": "milestoneStatus.csv", + "openCodelist": false + }, + "documents": { + "title": "Documents", + "description": "List of documents associated with this milestone (Deprecated in 1.1).", + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "Inclusion of documents at the milestone level is now deprecated. Documentation should be attached in the tender, award, contract or implementation sections, and titles and descriptions used to highlight the related milestone. Publishers who wish to continue to provide documents at the milestone level should explicitly declare this by using the milestone documents extension." + }, + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "documentType": { + "title": "Document type", + "description": "A classification of the document described taken from the [documentType codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": [ + "string", + "null" + ], + "codelist": "documentType.csv", + "openCodelist": true + }, + "title": { + "title": "Title", + "description": "The document title.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": [ + "string", + "null" + ] + }, + "url": { + "title": "URL", + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "datePublished": { + "title": "Date published", + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateModified": { + "title": "Date modified", + "description": "Date that the document was last modified", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "format": { + "title": "Format", + "description": "The format of the document taken from the [IANA Media Types code list](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": [ + "string", + "null" + ] + }, + "language": { + "title": "Language", + "description": "Specifies the language of the linked document using either two-digit [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": [ + "string", + "null" + ] + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal.dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "properties": { + "id": { + "title": "ID", + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "type": [ + "string", + "null" + ] + }, + "amount": { + "title": "Amount", + "description": "The value of the budget line item. A negative value indicates anticipated income to the budget as a result of this contracting process, rather than expenditure.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "type": [ + "string", + "null" + ] + }, + "projectID": { + "title": "Project identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "type": [ + "string", + "integer", + "null" + ] + }, + "uri": { + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal Data Standard or any other standard which provides structured data on budget sources. Human readable documents can be included using the planning.documents block.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "source": { + "title": "Data Source", + "description": "(Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "deprecated": { + "deprecatedVersion": "1.1", + "description": "The budget data source field was intended to link to machine-readable data about the budget for a contracting process, but has been widely mis-used to provide free-text descriptions of budget providers. As a result, it has been removed from version 1.1. budget/uri can be used to provide a link to machine-readable budget information, and budget/description can be used to provide human-readable information on the budget source." + }, + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Fiscal Data Package](http://fiscal.dataprotocols.org/) and the [International Aid Transpareny Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Fiscal Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For IATI this is the transaction reference.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "source": { + "title": "Data source", + "description": "Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Date", + "description": "The date of the transaction", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "$ref": "#/definitions/Value", + "title": "Value", + "description": "The value of the transaction." + }, + "payer": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payer", + "description": "An organization reference for the organization from which the funds in this transaction originate." + }, + "payee": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payee", + "description": "An organization reference for the organization which receives the funds in this transaction." + }, + "uri": { + "title": "Linked spending information", + "description": "A URI pointing directly to a machine-readable record about this spending transaction.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "amount": { + "title": "Amount", + "description": "(Deprecated in 1.1. Use transaction.value instead) The value of the transaction. A negative value indicates a refund or correction.", + "$ref": "#/definitions/Value", + "deprecated": { + "description": "This field has been replaced by the ```transaction.value``` field for consistency with the use of value and amount elsewhere in the standard.", + "deprecatedVersion": "1.1" + } + }, + "providerOrganization": { + "title": "Provider organization", + "description": "(Deprecated in 1.1. Use transaction.payer instead.) The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the ```transaction.payer``` field to resolve ambiguity arising from 'provider' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" + } + }, + "receiverOrganization": { + "title": "Receiver organization", + "description": "(Deprecated in 1.1. Use transaction.payee instead). The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the ```transaction.payee``` field to resolve ambiguity arising from 'receiver' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" + } + } + } + }, + "OrganizationReference": { + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the party being referenced. This must match the name of an entry in the parties section.", + "title": "Organization name", + "minLength": 1 + }, + "id": { + "type": [ + "string", + "integer" + ], + "description": "The id of the party being referenced. This must match the id of an entry in the parties section.", + "title": "Organization ID", + "minLength": 1 + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and detailed legal identifier information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Identifier" + }, + "address": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and address information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Address", + "description": "(Deprecated outside the parties section)", + "title": "Address" + }, + "additionalIdentifiers": { + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and additional identifiers for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "items": { + "$ref": "#/definitions/Identifier" + }, + "title": "Additional identifiers", + "uniqueItems": true, + "wholeListMerge": true, + "description": "(Deprecated outside the parties section) A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier." + }, + "contactPoint": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organisations should be referenced by their identifier and name in a document, and contact point information for an organisation should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/ContactPoint", + "description": "(Deprecated outside the parties section)", + "title": "Contact point" + } + }, + "required": [ + "id", + "name" + ], + "type": "object", + "description": "The id and name of the party being referenced. Used to cross-reference to the parties section", + "title": "Organization reference" + }, + "Organization": { + "title": "Organization", + "description": "A party (organization)", + "type": "object", + "properties": { + "name": { + "title": "Common name", + "description": "A common name for this organization or other participant in the contracting process. The identifier object provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization or entity is known. This field may also include details of the department or sub-unit involved in this contracting process.", + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string" + ], + "description": "The ID used for cross-referencing to this party from other sections of the release. This field may be built with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}).", + "title": "Entity ID" + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization or participant. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" + }, + "additionalIdentifiers": { + "title": "Additional identifiers", + "description": "A list of additional / supplemental identifiers for the organization or participant, using the [organization identifier guidance](http://standard.open-contracting.org/1.1/en/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "items": { + "$ref": "#/definitions/Identifier" + }, + "uniqueItems": true, + "wholeListMerge": true + }, + "address": { + "$ref": "#/definitions/Address" + }, + "contactPoint": { + "$ref": "#/definitions/ContactPoint" + }, + "roles": { + "title": "Party roles", + "description": "The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#party-role). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": "array", + "items": { + "type": "string" + }, + "codelist": "partyRole.csv", + "openCodelist": true + }, + "details": { + "type": [ + "object", + "null" + ], + "description": "Additional classification information about parties can be provided using partyDetail extensions that define particular properties and classification schemes. ", + "title": "Details" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Item": { + "title": "Item", + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "description": { + "title": "Description", + "description": "A description of the goods, services to be provided.", + "type": [ + "string", + "null" + ] + }, + "classification": { + "title": "Classification", + "description": "The primary classification for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/1.1/en/schema/codelists/#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" + }, + "additionalClassifications": { + "title": "Additional classifications", + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/1.1/en/schema/codelists/#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "items": { + "$ref": "#/definitions/Classification" + }, + "uniqueItems": true, + "wholeListMerge": true + }, + "quantity": { + "title": "Quantity", + "description": "The number of units required", + "type": [ + "number", + "null" + ] + }, + "unit": { + "title": "Unit", + "description": "A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and the unit-price. For comparability, an established list of units can be used. ", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "The list from which units of measure identifiers are taken. This should be an entry from the options available in the [unitClassificationScheme](http://standard.open-contracting.org/1.1/en/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, although other options are available.", + "type": [ + "string", + "null" + ], + "codelist": "unitClassificationScheme.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to find and use identifiers from the scheme in use.", + "type": [ + "string", + "null" + ], + "versionId": true + }, + "name": { + "title": "Name", + "description": "Name of the unit.", + "type": [ + "string", + "null" + ] + }, + "value": { + "title": "Value", + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + }, + "uri": { + "title": "URI", + "description": "If the scheme used provide a machine-readable URI for this unit of measure, this can be given.", + "format": "uri", + "type": [ + "string", + "null" + ] + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Amendment": { + "title": "Amendment", + "type": "object", + "description": "Amendment information", + "properties": { + "date": { + "title": "Amendment date", + "description": "The date of this amendment.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "rationale": { + "title": "Rationale", + "description": "An explanation for the amendment.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "An identifier for this amendment: often the amendment number", + "type": [ + "string", + "null" + ], + "title": "ID" + }, + "description": { + "description": "A free text, or semi-structured, description of the changes made in this amendment.", + "type": [ + "string", + "null" + ], + "title": "Description" + }, + "amendsReleaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **before** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amended release (identifier)" + }, + "releaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **after** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amending release (identifier)" + }, + "changes": { + "title": "Amended fields", + "description": "An array change objects describing the fields changed, and their former values. (Deprecated in 1.1)", + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "title": "Property", + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. (Deprecated in 1.1)", + "type": "string" + }, + "former_value": { + "title": "Former Value", + "description": "The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1)", + "type": [ + "string", + "number", + "integer", + "array", + "object", + "null" + ] + } + } + }, + "deprecated": { + "description": "A free-text or semi-structured string describing the changes made in each amendment can be provided in the amendment.description field. To provide structured information on the fields that have changed, publishers should provide releases indicating the state of the contracting process before and after the amendment. ", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Classification": { + "title": "Classification", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://standard.open-contracting.org/1.1/en/schema/codelists/#item-classification-scheme) wherever possible.", + "type": [ + "string", + "null" + ], + "codelist": "itemClassificationScheme.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The classification code drawn from the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A textual description or title for the code.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Identifier": { + "title": "Identifier", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "Organization identifiers be drawn from an existing identification scheme. This field is used to indicate the scheme or codelist in which the identifier will be found. This value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting.org/1.1/en/schema/codelists/#organization-identifier-scheme).", + "type": [ + "string", + "null" + ], + "codelist": "organizationIdentifierRegistrationAgency_iati.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The identifier of the organization in the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "legalName": { + "title": "Legal Name", + "description": "The legally registered name of the organization.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the url field of the Organization contact point.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Address": { + "title": "Address", + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "title": "Street address", + "type": [ + "string", + "null" + ], + "description": "The street address. For example, 1600 Amphitheatre Pkwy." + }, + "locality": { + "title": "Locality", + "type": [ + "string", + "null" + ], + "description": "The locality. For example, Mountain View." + }, + "region": { + "title": "Region", + "type": [ + "string", + "null" + ], + "description": "The region. For example, CA." + }, + "postalCode": { + "title": "Postalcode", + "type": [ + "string", + "null" + ], + "description": "The postal code. For example, 94043." + }, + "countryName": { + "title": "Country name", + "type": [ + "string", + "null" + ], + "description": "The country name. For example, United States." + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "ContactPoint": { + "title": "Contact point", + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "title": "Name", + "type": [ + "string", + "null" + ], + "description": "The name of the contact person, department, or contact point, for correspondence relating to this contracting process." + }, + "email": { + "title": "Email", + "type": [ + "string", + "null" + ], + "description": "The e-mail address of the contact point/person." + }, + "telephone": { + "title": "Telephone", + "type": [ + "string", + "null" + ], + "description": "The telephone number of the contact point/person. This should include the international dialling code." + }, + "faxNumber": { + "title": "Fax number", + "type": [ + "string", + "null" + ], + "description": "The fax number of the contact point/person. This should include the international dialling code." + }, + "url": { + "title": "URL", + "type": [ + "string", + "null" + ], + "description": "A web address for the contact point/person.", + "format": "uri" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Value": { + "title": "Value", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "Amount as a number.", + "type": [ + "number", + "null" + ] + }, + "currency": { + "title": "Currency", + "description": "The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217.", + "type": [ + "string", + "null" + ] + } + } + }, + "Period": { + "title": "Period", + "description": " ", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "The start date for the period. When known, a precise start date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "The end date for the period. When known, a precise end date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "maxExtentDate": { + "description": "The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum available data for extension or renewal of this period.", + "format": "date-time", + "title": "Maximum extent", + "type": [ + "string", + "null" + ] + }, + "durationInDays": { + "description": "The maximum duration of this period in days. A user interface may wish to collect or display this data in months or years as appropriate, but should convert it into days when completing this field. This field can be used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this field is optional, and should reflect the diffence between startDate and maxExtentDate.", + "title": "Duration (days)", + "type": [ + "integer", + "null" + ] + } + } + }, + "RelatedProcess": { + "description": "A reference to a related contracting process: generally one preceeding or following on from the current process.", + "type": "object", + "title": "Related Process", + "properties": { + "id": { + "title": "Relationship ID", + "description": "A local identifier for this relationship, unique within this array.", + "type": [ + "string" + ] + }, + "relationship": { + "items": { + "type": "string" + }, + "description": "Specify the type of relationship using the [related process codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#related-process).", + "title": "Relationship", + "type": [ + "array", + "null" + ], + "codelist": "relatedProcess.csv", + "opencodelist": true + }, + "title": { + "description": "The title of the related process, where referencing an open contracting process, this field should match the tender/title field in the related process.", + "title": "Related process title", + "type": [ + "string", + "null" + ] + }, + "scheme": { + "title": "Scheme", + "description": "The identification scheme used by this cross-reference from the [related process scheme codelist](http://standard.open-contracting.org/1.1/en/schema/codelists/#related-process-scheme) codelist. When cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used.", + "type": [ + "string", + "null" + ], + "codelist": "relatedProcessScheme.csv", + "openCodelist": true + }, + "identifier": { + "description": "The identifier of the related process. When cross-referencing information also published using OCDS, this should be the Open Contracting ID (ocid).", + "title": "Identifier", + "type": [ + "string", + "null" + ] + }, + "uri": { + "format": "uri", + "description": "A URI pointing to a machine-readble document, release or record package containing the identified related process.", + "title": "Related process URI", + "type": [ + "string", + "null" + ] + } + } + } + } +} From a68e50d7f14c7ab614319bffe4396f7f0bab6808 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 6 Jul 2017 17:45:01 +0300 Subject: [PATCH 040/702] OCE-355 added record package schemas --- .../record-package-schema-1.0.0.json | 141 +++++++++++ .../record-package-schema-1.0.1.json | 146 ++++++++++++ .../record-package-schema-1.0.2.json | 146 ++++++++++++ .../record-package-schema-1.1.0.json | 218 ++++++++++++++++++ 4 files changed, 651 insertions(+) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json new file mode 100644 index 000000000..f56b03ae9 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json @@ -0,0 +1,141 @@ +{ + "id": "http://ocds.open-contracting.org/standard/r/1__0__0/record-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing meta data. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "scheme": { + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"], + "format": "uri" + }, + "uid": { + "description": "The unique ID for this entity under the given ID scheme.", + "type": ["string", "null"] + }, + "uri": { + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this datapackage. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers publication policy.", + "type": ["string", "null"], + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published.", + "type": "string", + "format": "date-time" + }, + "packages": { + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/record" }, + "uniqueItems": true + } + }, + "required": ["uri", "publisher", "publishedDate", "packages", "records"], + "definitions": { + "record": { + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://ocds.open-contracting.org/standard/r/1__0__0/en/key_concepts/definitions/#contracting-process", + "type": "string" + }, + "releases": { + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The url of the release which contains the url of the package with the releaseID appended using a fragment identifier e.g. http://ocds.open-contracting.org/demos/releases/12345.json#ocds-a2ef3d01-1594121/1", + "type": ["string", "null"], + "format" : "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + } + } + }, + "required": ["url", "date"] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/versioned-release-validation-schema.json" + } + }, + "required": ["ocid", "releases"] + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json new file mode 100644 index 000000000..faec75059 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json @@ -0,0 +1,146 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__1/record-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing meta data. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title":"Name", + "description":"The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title":"Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"] + }, + "uid": { + "title":"uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": ["string", "null"] + }, + "uri": { + "title":"URI", + "description":"A URI to identify the publisher.", + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this datapackage. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers publication policy.", + "type": ["string", "null"], + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published.", + "type": "string", + "format": "date-time" + }, + "packages": { + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/record" }, + "uniqueItems": true + } + }, + "required": ["uri", "publisher", "publishedDate", "packages", "records"], + "definitions": { + "record": { + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://standard.open-contracting.org/latest/en/getting_started/contracting_process/", + "type": "string" + }, + "releases": { + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The url of the release which contains the url of the package with the releaseID appended using a fragment identifier e.g. http://standard.open-contracting.org/latest/en/examples/tender.json#ocds-213czf-000-00001", + "type": ["string", "null"], + "format" : "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + } + } + }, + "required": ["url", "date"] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://standard.open-contracting.org/schema/1__0__1/versioned-release-validation-schema.json" + } + }, + "required": ["ocid", "releases"] + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json new file mode 100644 index 000000000..b871e9004 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json @@ -0,0 +1,146 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__0__2/record-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing meta data. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package Identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title":"Name", + "description":"The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title":"Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": ["string", "null"] + }, + "uid": { + "title":"uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": ["string", "null"] + }, + "uri": { + "title":"URI", + "description":"A URI to identify the publisher.", + "type": ["string", "null"], + "format" : "uri" + } + }, + "required": ["name"] + }, + "license": { + "description": "A link to the license that applies to the data in this datapackage. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": ["string", "null"], + "format": "uri" + }, + "publicationPolicy": { + "description": "A link to a document describing the publishers publication policy.", + "type": ["string", "null"], + "format": "uri" + }, + "publishedDate": { + "description": "The date that this package was published.", + "type": "string", + "format": "date-time" + }, + "packages": { + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/record" }, + "uniqueItems": true + } + }, + "required": ["uri", "publisher", "publishedDate", "packages", "records"], + "definitions": { + "record": { + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://standard.open-contracting.org/latest/en/getting_started/contracting_process/", + "type": "string" + }, + "releases": { + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The url of the release which contains the url of the package with the releaseID appended using a fragment identifier e.g. http://standard.open-contracting.org/latest/en/examples/tender.json#ocds-213czf-000-00001", + "type": ["string", "null"], + "format" : "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": ["planning", "tender", "tenderAmendment", "tenderUpdate", "tenderCancellation", "award", "awardUpdate", "awardCancellation", "contract", "contractUpdate", "contractAmendment", "implementation", "implementationUpdate", "contractTermination", "compiled"] + } + } + }, + "required": ["url", "date"] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://standard.open-contracting.org/schema/1__0__2/versioned-release-validation-schema.json" + } + }, + "required": ["ocid", "releases"] + } + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json new file mode 100644 index 000000000..76bd499a7 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json @@ -0,0 +1,218 @@ +{ + "id": "http://standard.open-contracting.org/schema/1__1__0/record-package-schema.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing meta data. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a url to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the organisation or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": [ + "string", + "null" + ] + }, + "uid": { + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "name" + ] + }, + "license": { + "title": "License", + "description": "A link to the license that applies to the data in this datapackage. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publicationPolicy": { + "title": "Publication policy", + "description": "A link to a document describing the publishers publication policy.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publishedDate": { + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", + "type": "string", + "format": "date-time" + }, + "packages": { + "title": "Packages", + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "title": "Records", + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/record" + }, + "uniqueItems": true + } + }, + "required": [ + "uri", + "publisher", + "publishedDate", + "releases", + "version" + ], + "definitions": { + "record": { + "title": "Record", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://standard.open-contracting.org/latest/en/getting_started/contracting_process/", + "type": "string" + }, + "releases": { + "title": "Releases", + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The url of the release which contains the url of the package with the releaseID appended using a fragment identifier e.g. http://standard.open-contracting.org/latest/en/examples/tender.json#ocds-213czf-000-00001", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + } + } + }, + "required": [ + "url", + "date" + ] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://standard.open-contracting.org/schema/1__1__0/versioned-release-validation-schema.json" + } + }, + "required": [ + "ocid", + "releases" + ] + } + } +} \ No newline at end of file From dbfc6e49720aa6c78dcbe811afc3fa0ebf90b64d Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 6 Jul 2017 17:45:41 +0300 Subject: [PATCH 041/702] OCE-355 added api and node requests, separate from generic request --- .../validator/OcdsValidatorApiRequest.java | 33 +++++++++++ .../validator/OcdsValidatorNodeRequest.java | 29 ++++++++++ .../ocds/validator/OcdsValidatorRequest.java | 55 +++++++++++++------ 3 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java new file mode 100644 index 000000000..01ff89a51 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java @@ -0,0 +1,33 @@ +package org.devgateway.ocds.validator; + +import java.util.TreeSet; + +/** + * Created by mpostelnicu on 7/6/17. + */ +public class OcdsValidatorApiRequest extends OcdsValidatorRequest { + + private String json; + + private String url; + + public OcdsValidatorApiRequest(String version, TreeSet extensions, String schemaType) { + super(version, extensions, schemaType); + } + + public String getJson() { + return json; + } + + public void setJson(String json) { + this.json = json; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java new file mode 100644 index 000000000..dba1de106 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java @@ -0,0 +1,29 @@ +package org.devgateway.ocds.validator; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.TreeSet; + +/** + * Created by mpostelnicu on 7/6/17. + */ +public class OcdsValidatorNodeRequest extends OcdsValidatorRequest { + + private JsonNode node; + + public OcdsValidatorNodeRequest(String version, TreeSet extensions, String schemaType) { + super(version, extensions, schemaType); + } + + public OcdsValidatorNodeRequest(OcdsValidatorRequest request, JsonNode node) { + super(request); + this.node = node; + } + + public JsonNode getNode() { + return node; + } + + public void setNode(JsonNode node) { + this.node = node; + } +} diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java index 93fdf5f65..8005eadcf 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -1,43 +1,62 @@ package org.devgateway.ocds.validator; -import java.util.List; -import javax.validation.constraints.NotNull; +import java.util.TreeSet; import org.hibernate.validator.constraints.NotEmpty; /** * Created by mpostelnicu on 7/5/17. */ -public class OcdsValidatorRequest { +public abstract class OcdsValidatorRequest { - private String ocdsVersion; + public OcdsValidatorRequest(OcdsValidatorRequest request) { + this.version = request.getVersion(); + this.extensions = request.getExtensions(); + this.schemaType = request.getSchemaType(); + } + + public OcdsValidatorRequest(String version, TreeSet extensions, String schemaType) { + this.version = version; + this.extensions = extensions; + this.schemaType = schemaType; + } - private List extensions; + /** + * This returns a unique key of the validator request based on the set contents , version and schemaType + * @return + */ + public String getKey() { + return schemaType + "-" + version + "-" + extensions; + } + + private String version; - @NotNull(message = "Json content cannot be null!") - @NotEmpty(message = "Json content cannot be empty!") - private String json; + private TreeSet extensions; - public String getOcdsVersion() { - return ocdsVersion; + @NotEmpty(message = "Please provide schemaType!") + private String schemaType; + + public String getVersion() { + return version; } - public void setOcdsVersion(String ocdsVersion) { - this.ocdsVersion = ocdsVersion; + public void setVersion(String version) { + this.version = version; } - public List getExtensions() { + public TreeSet getExtensions() { return extensions; } - public void setExtensions(List extensions) { + public void setExtensions(TreeSet extensions) { this.extensions = extensions; } - public String getJson() { - return json; + + public String getSchemaType() { + return schemaType; } - public void setJson(String json) { - this.json = json; + public void setSchemaType(String schemaType) { + this.schemaType = schemaType; } } From 4ace4930fec9743ff8106f51689115d362ca3585 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 6 Jul 2017 17:47:11 +0300 Subject: [PATCH 042/702] OCE-355 added release prefix and subfixes and paths to classpath resources --- .../ocds/validator/OcdsValidatorConstants.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index e1d2ba28e..0a0b8190e 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -5,6 +5,10 @@ */ public final class OcdsValidatorConstants { + private OcdsValidatorConstants() { + + } + public static final class Versions { public static final String OCDS_1_0_0 = "1.0.0"; public static final String OCDS_1_0_1 = "1.0.1"; @@ -12,6 +16,19 @@ public static final class Versions { public static final String OCDS_1_1_0 = "1.1.0"; } + public static final class Schemas { + public static final String RELEASE = "release"; + public static final String RELEASE_PACKAGE = "release-package"; + public static final String RECORD_PACKAGE = "record-package"; + public static final String[] ALL = {RELEASE, RELEASE_PACKAGE, RECORD_PACKAGE}; + } + + public static final class SchemaPrefixes { + public static final String RELEASE = "release/release-schema-"; + public static final String RELEASE_PACKAGE = "release-package/release-package-schema-"; + public static final String RECORD_PACKAGE = "record-package/record-package-schema-"; + } + public static final String SCHEMA_POSTFIX = ".json"; } From 1a5e73d69b8dc90d1cff47d3eee061537aad6259 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 6 Jul 2017 17:48:10 +0300 Subject: [PATCH 043/702] OCE-355 populate schema keys and nodes map based on request data --- .../ocds/validator/OcdsValidatorService.java | 110 +++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 7818e5e4c..4059aec14 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -1,9 +1,22 @@ package org.devgateway.ocds.validator; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.fge.jackson.JsonLoader; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.report.ListReportProvider; +import com.github.fge.jsonschema.core.report.LogLevel; import com.github.fge.jsonschema.core.report.ProcessingReport; +import com.github.fge.jsonschema.main.JsonSchema; +import com.github.fge.jsonschema.main.JsonSchemaFactory; +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; /** * Created by mpostelnicu on 7/5/17. @@ -11,19 +24,110 @@ @Service public class OcdsValidatorService { + + private Map keySchema = new ConcurrentHashMap<>(); + + private Map schemaNamePrefix = new ConcurrentHashMap<>(); + + @Autowired private ObjectMapper jacksonObjectMapper; + private JsonNode getUnmodifiedSchemaNode(OcdsValidatorRequest request) { + try { + return JsonLoader.fromResource(schemaNamePrefix.get(request.getSchemaType()) + request.getVersion() + + OcdsValidatorConstants.SCHEMA_POSTFIX); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + private JsonSchema getSchema(OcdsValidatorRequest request) { + if (keySchema.containsKey(request.getKey())) { + return keySchema.get(request.getKey()); + } else { + JsonNode schemaNode = getUnmodifiedSchemaNode(request); + //TODO: this is where we should apply extensions ! - public ProcessingReport validateReleases(OcdsValidatorRequest request) { - return null; + try { + JsonSchema schema = JsonSchemaFactory.newBuilder() + .setReportProvider(new ListReportProvider(LogLevel.ERROR, LogLevel.FATAL)).freeze() + .getJsonSchema(schemaNode); + keySchema.put(request.getKey(), schema); + return schema; + } catch (ProcessingException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + } + + private void initSchemaNamePrefix() { + schemaNamePrefix.put(OcdsValidatorConstants.Schemas.RELEASE, OcdsValidatorConstants.SchemaPrefixes.RELEASE); + schemaNamePrefix.put(OcdsValidatorConstants.Schemas.RECORD_PACKAGE, + OcdsValidatorConstants.SchemaPrefixes.RECORD_PACKAGE); + schemaNamePrefix.put(OcdsValidatorConstants.Schemas.RELEASE_PACKAGE, + OcdsValidatorConstants.SchemaPrefixes.RELEASE_PACKAGE); + } + + @PostConstruct + private void init() { + initSchemaNamePrefix(); + } + + + public ProcessingReport validate(OcdsValidatorApiRequest request) { + + OcdsValidatorNodeRequest nodeRequest = convertApiRequestToNodeRequest(request); + + if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { + return validateRelease(nodeRequest); + } + + } + + private JsonNode getJsonNodeFromString(String json) { + try { + return JsonLoader.fromString(json); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } } + private JsonNode getJsonNodeFromUrl(String url) { + try { + return JsonLoader.fromURL(new URL(url)); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } - public ProcessingReport validateRelease(OcdsValidatorRequest request) { + /** + * Validates a release or an array of releases + * + * @param nodeRequest + * @return + */ + private ProcessingReport validateRelease(OcdsValidatorNodeRequest nodeRequest) { return null; } + private OcdsValidatorNodeRequest convertApiRequestToNodeRequest(OcdsValidatorApiRequest request) { + JsonNode node = null; + if (!StringUtils.isEmpty(request.getJson())) { + node = getJsonNodeFromString(request.getJson()); + } + + if (!StringUtils.isEmpty(request.getUrl())) { + node = getJsonNodeFromUrl(request.getUrl()); + } + + return new OcdsValidatorNodeRequest(request, node); + } + } From 3f9ba4c7d52936f5e0cd4e327f48287bbb0be843 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:56:14 +0300 Subject: [PATCH 044/702] OCE-355 moved the resources to resources folder --- .../schema/extensions/ocds_bid_extension/v1.1/extension.json | 0 .../schema/extensions/ocds_bid_extension/v1.1/release-schema.json | 0 .../schema/extensions/ocds_enquiry_extension/v1.1/extension.json | 0 .../extensions/ocds_enquiry_extension/v1.1/release-schema.json | 0 .../schema/record-package/record-package-schema-1.0.0.json | 0 .../schema/record-package/record-package-schema-1.0.1.json | 0 .../schema/record-package/record-package-schema-1.0.2.json | 0 .../schema/record-package/record-package-schema-1.1.0.json | 0 .../schema/release-package/release-package-schema-1.0.0.json | 0 .../schema/release-package/release-package-schema-1.0.1.json | 0 .../schema/release-package/release-package-schema-1.0.2.json | 0 .../schema/release-package/release-package-schema-1.1.0.json | 0 .../schema/release/release-schema-1.0.0.json | 0 .../schema/release/release-schema-1.0.1.json | 0 .../schema/release/release-schema-1.0.2.json | 0 .../schema/release/release-schema-1.1.0.json | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/extensions/ocds_bid_extension/v1.1/extension.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/extensions/ocds_bid_extension/v1.1/release-schema.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/extensions/ocds_enquiry_extension/v1.1/extension.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/record-package/record-package-schema-1.0.0.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/record-package/record-package-schema-1.0.1.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/record-package/record-package-schema-1.0.2.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/record-package/record-package-schema-1.1.0.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release-package/release-package-schema-1.0.0.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release-package/release-package-schema-1.0.1.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release-package/release-package-schema-1.0.2.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release-package/release-package-schema-1.1.0.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release/release-schema-1.0.0.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release/release-schema-1.0.1.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release/release-schema-1.0.2.json (100%) rename validator/src/main/{java/org/devgateway/ocds/validator => resources}/schema/release/release-schema-1.1.0.json (100%) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/extension.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_bid_extension/v1.1/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/extension.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json b/validator/src/main/resources/schema/record-package/record-package-schema-1.0.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.0.json rename to validator/src/main/resources/schema/record-package/record-package-schema-1.0.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json b/validator/src/main/resources/schema/record-package/record-package-schema-1.0.1.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.1.json rename to validator/src/main/resources/schema/record-package/record-package-schema-1.0.1.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json b/validator/src/main/resources/schema/record-package/record-package-schema-1.0.2.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.0.2.json rename to validator/src/main/resources/schema/record-package/record-package-schema-1.0.2.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json b/validator/src/main/resources/schema/record-package/record-package-schema-1.1.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/record-package/record-package-schema-1.1.0.json rename to validator/src/main/resources/schema/record-package/record-package-schema-1.1.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.0.json rename to validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.1.json rename to validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.0.2.json rename to validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release-package/release-package-schema-1.1.0.json rename to validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json b/validator/src/main/resources/schema/release/release-schema-1.0.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.0.json rename to validator/src/main/resources/schema/release/release-schema-1.0.0.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json b/validator/src/main/resources/schema/release/release-schema-1.0.1.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.1.json rename to validator/src/main/resources/schema/release/release-schema-1.0.1.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json b/validator/src/main/resources/schema/release/release-schema-1.0.2.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.0.2.json rename to validator/src/main/resources/schema/release/release-schema-1.0.2.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json b/validator/src/main/resources/schema/release/release-schema-1.1.0.json similarity index 100% rename from validator/src/main/java/org/devgateway/ocds/validator/schema/release/release-schema-1.1.0.json rename to validator/src/main/resources/schema/release/release-schema-1.1.0.json From 9c23d6af1a88959a8f5c3ca975892b70db671814 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:57:09 +0300 Subject: [PATCH 045/702] OCE-355 commons io variable dependency plus upgraded --- persistence-mongodb/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/persistence-mongodb/pom.xml b/persistence-mongodb/pom.xml index b3b72fbb0..65badbe03 100644 --- a/persistence-mongodb/pom.xml +++ b/persistence-mongodb/pom.xml @@ -29,6 +29,7 @@ 2.6.11 1.9 2.2.6 + 2.5 @@ -123,7 +124,7 @@ commons-io commons-io - 2.3 + ${commons.io.version} From 3fb99ed3247daa4bbb27d3a75f591f01d1e63d6b Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:57:49 +0300 Subject: [PATCH 046/702] OCE-355 added simple jackson dependency and obj2mapper builder --- validator/pom.xml | 26 ++++++++++++----- .../validator/ValidatorConfiguration.java | 29 +++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/ValidatorConfiguration.java diff --git a/validator/pom.xml b/validator/pom.xml index d9cde561d..691a4755b 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -40,6 +40,12 @@ spring-boot-starter + + + org.springframework + spring-web + + org.springframework.boot spring-boot-starter-validation @@ -47,7 +53,7 @@ com.fasterxml.jackson.core - jackson-annotations + jackson-databind @@ -60,12 +66,6 @@ com.github.fge json-schema-validator ${json.schema.validator.version} - - - javax.mail - mailapi - - @@ -101,6 +101,18 @@ **/*.properties **/*.sql **/*.xml + **/*.json + + + + + true + src/test/resources + + **/*.properties + **/*.sql + **/*.xml + **/*.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/ValidatorConfiguration.java b/validator/src/main/java/org/devgateway/ocds/validator/ValidatorConfiguration.java new file mode 100644 index 000000000..8f6993e62 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/ValidatorConfiguration.java @@ -0,0 +1,29 @@ +package org.devgateway.ocds.validator; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + * Created by mpostelnicu on 7/7/17. + */ +@Configuration +public class ValidatorConfiguration { + + @Bean + public Jackson2ObjectMapperBuilder objectMapperBuilder() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); + + //builder.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormatGmt.setTimeZone(TimeZone.getTimeZone("GMT")); + builder.serializationInclusion(Include.NON_EMPTY).dateFormat(dateFormatGmt); + builder.defaultViewInclusion(true); + + return builder; + } + +} From 207a50d9e0a683a0adc792736c9d239629450c2b Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:58:19 +0300 Subject: [PATCH 047/702] OCE-355 fixed resource path after moving --- .../devgateway/ocds/validator/OcdsValidatorConstants.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index 0a0b8190e..3dc7f6446 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -24,9 +24,9 @@ public static final class Schemas { } public static final class SchemaPrefixes { - public static final String RELEASE = "release/release-schema-"; - public static final String RELEASE_PACKAGE = "release-package/release-package-schema-"; - public static final String RECORD_PACKAGE = "record-package/record-package-schema-"; + public static final String RELEASE = "/schema/release/release-schema-"; + public static final String RELEASE_PACKAGE = "/schema/release-package/release-package-schema-"; + public static final String RECORD_PACKAGE = "/schema/record-package/record-package-schema-"; } public static final String SCHEMA_POSTFIX = ".json"; From 00b4fc76536bf7a4ddd5832021c7b95367457bc4 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:58:35 +0300 Subject: [PATCH 048/702] OCE-355 added a full release for validation and made it compatible with 1.1.0 --- .../src/test/resources/full-release.json | 504 ++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 validator/src/test/resources/full-release.json diff --git a/validator/src/test/resources/full-release.json b/validator/src/test/resources/full-release.json new file mode 100644 index 000000000..014f784db --- /dev/null +++ b/validator/src/test/resources/full-release.json @@ -0,0 +1,504 @@ +{ + "ocid": "ocds-full-001", + "id": "ocds-full-1234", + "date": "2016-05-10T09:30:00Z", + "tag": ["award"], + "initiationType": "tender", + "planning": { + "budget": { + "source": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984", + "id": "6801ad388f3a38b7740dde20108c58b35984ee91", + "description": "Budget allocation for highway maintenance, aligned with 2015 strategic plan. ", + "amount": { + "amount": 6700000.00, + "currency": "GBP" + }, + "project": "Central Junction Cycle Scheme", + "projectID": "SP001", + "uri": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984ee91" + }, + "rationale": "The 2009 Strategic Plan identifies a need for an improved cycle route in the centre of town.", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + }, + { + "id": "0002", + "documentType": "needsAssessment", + "title": "Cycle provision - Needs Assessment", + "description": "Needs assessment for provision for cyclists in the centre of town.", + "url": "http://example.com/opencontracting/documents/ocds-213czf-000-00001/needsAssessment.pdf", + "datePublished": "2009-01-15T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + }, + "tender": { + "id": "ocds-full-1234-01-tender", + "title": "Planned cycle lane improvements", + "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", + "status": "active", + "items": [ + { + "id": "0001", + "description": "desc 1", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 120000, + "currency": "GBP" + } + } + } + ], + "minValue": { + "amount": 600000, + "currency": "GBP" + }, + "value": { + "amount": 1100000, + "currency": "GBP" + }, + "procurementMethod": "open", + "procurementMethodRationale": "An open competitive tender is required by EU Rules", + "awardCriteria": "bestProposal", + "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", + "submissionMethod": [ "electronicSubmission" ], + "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", + "tenderPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2011-04-01T18:00:00Z" + + }, + "enquiryPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-03-14T17:30:00Z" + }, + "hasEnquiries": false, + "awardPeriod": { + "startDate": "2010-06-01T00:00:00Z", + "endDate": "2011-08-01T23:59:59Z" + }, + "eligibilityCriteria": "criteria-1", + "numberOfTenderers": 10, + "procuringEntity": { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "London Borough of Barnet", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + }, + "tenderers": [ + { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [ + { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + } + ], + "name": "Organization name", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + } + ], + "documents": [ + { + "id": "0005", + "documentType": "notice", + "title": "Tender Notice", + "description": "Official tender notice.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", + "datePublished": "2010-03-01T09:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "text/html", + "language": "en" + } + ], + "milestones": [ + { + "id": "1234", + "title": "milestones 1", + "description": "description 1", + "dueDate":"2010-10-01T00:00:00Z", + "dateModified": "2011-10-01T00:00:00Z", + "status": "met", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + ], + "amendment": { + "date": "2011-08-01T23:59:59Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale-1" + } + }, + "buyer": { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "London Borough of Barnet", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + }, + "awards": [ + { + "id": "ocds-213czf-000-00001-award-01", + "title": "Award of contract to build new cycle lanes in the centre of town.", + "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", + "status": "pending", + "date": "2010-05-10T09:30:00Z", + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "suppliers": [ + { + "id": "GB-COH-1234567844", + "identifier": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "AnyCorp Cycle Provision", + "address": { + "streetAddress": "100 Anytown Lane", + "locality": "Anytown", + "region": "AnyCounty", + "postalCode": "AN1 1TN", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Contracts Team", + "email": "contracts@anycorp.example", + "telephone": "12345 456 343", + "faxNumber": "12345 456 343", + "url": "http://example.com/contact/" + } + } + ], + "items": [ + { + "id": "0001", + "description": "desc", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 137000, + "currency": "GBP" + } + } + } + ], + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-08-01T23:59:00Z" + }, + "documents": [ + { + "id": "0007", + "documentType": "notice", + "title": "Award notice", + "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", + "datePublished": "2010-05-10T09:30:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "text/html", + "language": "en" + } + ], + "amendment": { + "date": "2010-05-10T09:30:00Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale..." + } + } + ], + "contracts": [ + { + "id": "ocds-213czf-000-00001-contract-01", + "awardID": "ocds-213czf-000-00001-award-01", + "title": "Contract to build new cycle lanes in the centre of town.", + "description": "A contract has been signed between the Council and AnyCorp Ltd for construction of new cycle lanes in the centre of town.", + "status": "active", + "period": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-08-01T23:59:00Z" + }, + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "items": [ + { + "id": "0001", + "description": "desc 2", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 137000, + "currency": "GBP" + } + } + } + ], + "dateSigned": "2015-06-10T14:23:12Z", + "documents": [ + { + "id": "0008", + "documentType": "contractSigned", + "title": "Signed Contract", + "description": "The Signed Contract for Cycle Path Construction", + "url": "http://example.com/contracts/ocds-213czf-000-00001", + "datePublished": "2015-06-10T16:43:12Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ], + "amendment": { + "date": "2010-01-05T00:00:00Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale 2" + }, + "implementation": { + "transactions": [ + { + "id": "ocds-213czf-000-00001-1", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-08-01T00:00:00Z", + "amount": { + "amount": 500000, + "currency": "GBP" + }, + "providerOrganization": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "receiverOrganization": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs1059620ywhgai" + }, + { + "id": "ocds-213czf-000-00001-2", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-10-01T00:00:00Z", + "amount": { + "amount": 100000, + "currency": "GBP" + }, + "providerOrganization": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "receiverOrganization": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs105962as0012" + } + ], + "milestones": [ + { + "id": "1234", + "title": "milestones 1", + "description": "description 1", + "dueDate":"2010-10-01T00:00:00Z", + "dateModified": "2011-10-01T00:00:00Z", + "status": "met", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + ], + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + } + ], + "language": "en" +} From 06d6b5b44a625faeed3940ecbdf592f1fcaae60d Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:58:47 +0300 Subject: [PATCH 049/702] OCE-355 added gitignore for validator module --- validator/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 validator/.gitignore diff --git a/validator/.gitignore b/validator/.gitignore new file mode 100644 index 000000000..fca2ac261 --- /dev/null +++ b/validator/.gitignore @@ -0,0 +1,8 @@ +/target/ +/.classpath +/.project +/.springBeans +/org.eclipse.m2e.core.prefs +/.settings/ +/derby.log +/.checkstyle From 4b6f9e6f195965d3c431e684d270f5a852455138 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:59:10 +0300 Subject: [PATCH 050/702] OCE-355 added simple and working validation for releases --- .../ocds/validator/OcdsValidatorService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 4059aec14..09fba2cc2 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -86,6 +86,8 @@ public ProcessingReport validate(OcdsValidatorApiRequest request) { return validateRelease(nodeRequest); } + return null; + } private JsonNode getJsonNodeFromString(String json) { @@ -113,7 +115,13 @@ private JsonNode getJsonNodeFromUrl(String url) { * @return */ private ProcessingReport validateRelease(OcdsValidatorNodeRequest nodeRequest) { - return null; + JsonSchema schema = getSchema(nodeRequest); + try { + return schema.validate(nodeRequest.getNode()); + } catch (ProcessingException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } } private OcdsValidatorNodeRequest convertApiRequestToNodeRequest(OcdsValidatorApiRequest request) { From 24a4c01d37f7c6fdc8c33ec6b6ac83e61a8a86b8 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 7 Jul 2017 15:59:33 +0300 Subject: [PATCH 051/702] OCE-355 added simple test to validate releases that works for all 1.x and 1.1 --- .../test/OcdsValidatorTestRelease.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java diff --git a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java new file mode 100644 index 000000000..d297d5978 --- /dev/null +++ b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java @@ -0,0 +1,61 @@ +package org.devgateway.ocds.validator.test; + +import com.github.fge.jsonschema.core.report.ProcessingReport; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import org.devgateway.ocds.validator.OcdsValidatorApiRequest; +import org.devgateway.ocds.validator.OcdsValidatorConstants; +import org.devgateway.ocds.validator.OcdsValidatorService; +import org.devgateway.ocds.validator.ValidatorApplication; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.StreamUtils; + +/** + * Created by mpostelnicu on 7/7/17. + */ +@RunWith(SpringRunner.class) +@ActiveProfiles("integration") +@SpringBootTest(classes = { ValidatorApplication.class }) +//@TestPropertySource("classpath:test.properties") +public class OcdsValidatorTestRelease { + + @Autowired + private OcdsValidatorService ocdsValidatorService; + + @Test + public void testReleaseValidation() { + + OcdsValidatorApiRequest request=new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, + null, OcdsValidatorConstants.Schemas.RELEASE); + InputStream resourceAsStream = this.getClass().getResourceAsStream("/full-release.json"); + try { + request.setJson(StreamUtils.copyToString(resourceAsStream, Charset.defaultCharset())); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + try { + resourceAsStream.close(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + ProcessingReport processingReport = ocdsValidatorService.validate(request); + if(!processingReport.isSuccess()) { + System.out.println(processingReport); + } + + Assert.assertTrue(processingReport.isSuccess()); + + } + +} From 28869d89c9b63921308ee77f4cf8f0c5b94ccfca Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 10 Jul 2017 00:42:11 +0300 Subject: [PATCH 052/702] OCE-354 Moved tabs to the right --- ui/oce/visualizations/map/style.less | 6 +-- .../map/tender-locations/location.jsx | 38 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index e5a297036..7763906c3 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -1,10 +1,10 @@ .leaflet-popup-content{ - width: 500px!important; + width: 600px!important; } .tender-locations-popup{ .leaflet-popup-content{ - min-height: 500px; + min-height: 400px; } header{ background: #ca3d3f; @@ -25,7 +25,7 @@ padding-top: 7px; } - section.tabs-bar{ + .tabs-bar{ background: #272727; font-size: 1.2em; margin-left: -20px; diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 9efa700a8..0e532ad17 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -23,23 +23,25 @@ class LocationWrapper extends translatable(Component){ let {data, translations, filters, years, styling, monthly, months} = this.props; let CurrentTab = this.constructor.TABS[currentTab]; return ( - - -
-
- {data.name} -
-
+ + +
+
+ {data.name} +
+
+
{this.constructor.TABS.map((Tab, index) => -
this.setState({currentTab: index})} +
this.setState({currentTab: index})} > - {Tab.getName(this.t.bind(this))} -
+ {Tab.getName(this.t.bind(this))} +
)} -
- +
+ + /> +
-
-
+ + + ) } } From cb87007e3cfb6d664f646c5a4dc94767366e1b75 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 10 Jul 2017 02:35:47 +0300 Subject: [PATCH 053/702] OCE-354 Linting --- ui/.eslintrc | 28 +-- .../map/tender-locations/location.jsx | 172 ++++++++++-------- ui/package.json | 10 +- 3 files changed, 117 insertions(+), 93 deletions(-) diff --git a/ui/.eslintrc b/ui/.eslintrc index 466fb61f1..268c69c2e 100644 --- a/ui/.eslintrc +++ b/ui/.eslintrc @@ -1,13 +1,19 @@ { - "extends": "airbnb", - "env": { - "browser": true - }, - "rules": { - "linebreak-style": 0, - "import/no-extraneous-dependencies": [error, { devDependencies: true }] - }, - "settings": { - "import/resolver": "webpack" - } + "extends": "airbnb", + "env": { + "browser": true + }, + "rules": { + "linebreak-style": 0, + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + "import/extensions": 0, + "react/react-in-jsx-scope": 0, + "react/no-find-dom-node": 0, + "no-underscore-dangle": 0, + "jsx-a11y/href-no-hash": "off", + "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }] + }, + "settings": { + "import/resolver": "webpack" + } } diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 0e532ad17..2bbf088f0 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -1,27 +1,29 @@ -import Marker from "../location/marker"; -import Component from "../../../pure-render-component"; -import {Popup} from "react-leaflet"; -import translatable from "../../../translatable"; -import cn from "classnames"; -import OverviewChart from "../../../visualizations/charts/overview"; -import CostEffectiveness from "../../../visualizations/charts/cost-effectiveness"; -import {cacheFn, download} from "../../../tools" +import ReactDOM from 'react-dom'; +import { Popup } from 'react-leaflet'; +import cn from 'classnames'; +import Marker from '../location/marker'; +import Component from '../../../pure-render-component'; +import translatable from '../../../translatable'; +import OverviewChart from '../../../visualizations/charts/overview'; +import CostEffectiveness from '../../../visualizations/charts/cost-effectiveness'; +import { cacheFn, download } from '../../../tools'; import ProcurementMethodChart from '../../../visualizations/charts/procurement-method'; -import ReactDOM from "react-dom"; -import style from "./style.less"; +// eslint-disable-next-line no-unused-vars +import style from './style.less'; -class LocationWrapper extends translatable(Component){ - constructor(props){ +class LocationWrapper extends translatable(Component) { + constructor(props) { super(props); this.state = { - currentTab: 0 - } + currentTab: 0, + }; } - render(){ - let {currentTab} = this.state; - let {data, translations, filters, years, styling, monthly, months} = this.props; - let CurrentTab = this.constructor.TABS[currentTab]; + render() { + const { currentTab } = this.state; + const { data, translations, filters, years, styling, monthly, months } = this.props; + const CurrentTab = this.constructor.TABS[currentTab]; + const t = translationKey => this.t(translationKey); return ( @@ -31,14 +33,17 @@ class LocationWrapper extends translatable(Component){
- {this.constructor.TABS.map((Tab, index) => -
this.setState({currentTab: index})} - > - {Tab.getName(this.t.bind(this))} + {this.constructor.TABS.map((Tab, index) => ( +
this.setState({ currentTab: index })} + role="button" + tabIndex={0} + > + {Tab.getName(t)}
- )} + ))}
- ) + ); } } -class Tab extends translatable(Component){} +class Tab extends translatable(Component) {} -export class OverviewTab extends Tab{ - static getName(t){return t('maps:tenderLocations:tabs:overview:title')} +export class OverviewTab extends Tab { + static getName(t) { return t('maps:tenderLocations:tabs:overview:title'); } - render(){ - let {data} = this.props; - let {count, amount} = data; - return
+ render() { + const { data } = this.props; + const { count, amount } = data; + return (

{this.t('maps:tenderLocations:tabs:overview:nrOfTenders')} {count}

{this.t('maps:tenderLocations:tabs:overview:totalFundingByLocation')} {amount.toLocaleString()}

-
+
); } } -let addTenderDeliveryLocationId = cacheFn( - (filters, id) => filters.set('tenderLoc', id) +const addTenderDeliveryLocationId = cacheFn( + (filters, id) => filters.set('tenderLoc', id), ); -export class ChartTab extends Tab{ - constructor(props){ +export class ChartTab extends Tab { + constructor(props) { super(props); this.state = { - chartData: null - } + chartData: null, + }; } - getMargins(){ + static getMargins() { return { t: 0, l: 50, r: 50, - b: 50 - } + b: 50, + }; } - getChartClass(){return ""} + static getChartClass() { return ''; } - render(){ - let {filters, styling, years, translations, data, monthly, months} = this.props; - let decoratedFilters = addTenderDeliveryLocationId(filters, data._id); - let doExcelExport = e => download({ + render() { + const { filters, styling, years, translations, data, monthly, months } = this.props; + const decoratedFilters = addTenderDeliveryLocationId(filters, data._id); + const doExcelExport = () => download({ ep: this.constructor.Chart.excelEP, filters: decoratedFilters, years, months, - t: this.t.bind(this) + t: translationKey => this.t(translationKey), }); - return
+ return (
this.setState({chartData})} - width={500} - height={350} - margin={this.getMargins()} - legend="h" + filters={decoratedFilters} + styling={styling} + years={years} + monthly={monthly} + months={months} + translations={translations} + data={this.state.chartData} + requestNewData={(_, chartData) => this.setState({ chartData })} + width={500} + height={350} + margin={this.constructor.getMargins()} + legend="h" />
-
+
Export
-
ReactDOM.findDOMNode(this).querySelector(".modebar-btn:first-child").click()}> - +
ReactDOM.findDOMNode(this).querySelector('.modebar-btn:first-child').click()} + role="button" + tabIndex={0} + > + Screenshot
-
+
); } } -export class OverviewChartTab extends ChartTab{ - static getName(t){return t('charts:overview:title')} +export class OverviewChartTab extends ChartTab { + static getName(t) { return t('charts:overview:title'); } - getChartClass(){return "overview";} + static getChartClass() { return 'overview'; } } OverviewChartTab.Chart = OverviewChart; -export class CostEffectivenessTab extends ChartTab{ - static getName(t){return t('charts:costEffectiveness:title')} +export class CostEffectivenessTab extends ChartTab { + static getName(t) { return t('charts:costEffectiveness:title'); } } CostEffectivenessTab.Chart = CostEffectiveness; -export class ProcurementMethodTab extends ChartTab{ - static getName(t){return t('charts:procurementMethod:title')} +export class ProcurementMethodTab extends ChartTab { + static getName(t) { return t('charts:procurementMethod:title'); } - getMargins(){ - let margins = super.getMargins(); + static getMargins() { + const margins = super.getMargins(); margins.r = 100; margins.b = 100; return margins; diff --git a/ui/package.json b/ui/package.json index 5aeb684a5..f663db8cf 100644 --- a/ui/package.json +++ b/ui/package.json @@ -46,10 +46,12 @@ "classnames": "^2.2.3", "css-loader": "^0.17.0", "d3": "^3.5.13", - "eslint": "^3.7.0", - "eslint-config-airbnb": "^12.0.0", - "eslint-import-resolver-webpack": "^0.6.0", - "eslint-plugin-import": "^1.16.0", + "eslint": "^4.2.0", + "eslint-config-airbnb": "^15.0.2", + "eslint-import-resolver-webpack": "^0.8.3", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-react": "^7.1.0", "exports-loader": "^0.6.2", "gulp": "^3.9.0", "gulp-npm-files": "^0.1.2", From 97dd7cea17b9023bd5f47d17717bfbe23b0413bf Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 10 Jul 2017 04:20:47 +0300 Subject: [PATCH 054/702] OCE-354 Stretching the tab bar full height --- ui/oce/visualizations/map/style.less | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index 7763906c3..ad5c52d9e 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -1,22 +1,25 @@ +@headerHeight: 64px; +@windowHeight: 400px; +@borderRadius: 12px; + .leaflet-popup-content{ width: 600px!important; + height: 400px; } .tender-locations-popup{ - .leaflet-popup-content{ - min-height: 400px; - } header{ background: #ca3d3f; margin-top: -14px; margin-left: -20px; margin-right: -20px; - border-top-right-radius: 12px; - border-top-left-radius: 12px; + border-top-right-radius: @borderRadius; + border-top-left-radius: @borderRadius; padding: 15px; color: white; font-size: 2em; font-weight: bolder; + height: @headerHeight; } a.leaflet-popup-close-button{ @@ -28,8 +31,9 @@ .tabs-bar{ background: #272727; font-size: 1.2em; - margin-left: -20px; - margin-right: -20px; + margin-left: -5px; + height: @windowHeight - @headerHeight + 14px;//magic number, leaflet's borders n stuff + border-bottom-left-radius: @borderRadius; div{ padding: 10px; padding-bottom: 7px; From 5c4daf7cc1c4c5c94294a89636b2f22ee3d3f7a0 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 10 Jul 2017 04:59:59 +0300 Subject: [PATCH 055/702] OCE-354 More space for the navbar --- ui/oce/visualizations/map/tender-locations/location.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 2bbf088f0..49c45db45 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -32,7 +32,7 @@ class LocationWrapper extends translatable(Component) { {data.name}
-
+
{this.constructor.TABS.map((Tab, index) => (
))}
-
+
Date: Mon, 10 Jul 2017 05:00:20 +0300 Subject: [PATCH 056/702] OCE-354 Navbar items sizing and positioning --- ui/oce/visualizations/map/style.less | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index ad5c52d9e..df0f597cd 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -3,7 +3,7 @@ @borderRadius: 12px; .leaflet-popup-content{ - width: 600px!important; + width: 750px!important; height: 400px; } @@ -35,11 +35,19 @@ height: @windowHeight - @headerHeight + 14px;//magic number, leaflet's borders n stuff border-bottom-left-radius: @borderRadius; div{ - padding: 10px; - padding-bottom: 7px; - border-bottom: 3px solid transparent; + padding: 10px 10px 10px 25px; + margin-left: -15px; + margin-right: -15px; + border-right: 3px solid transparent; + &:first-child{ + margin-top: 10px; + } + &:focus{ + outline: none; + } &.active{ - border-bottom-color: #ca3d3f; + background: #0f0f0f; + border-right-color: #ca3d3f; a{ color: #d4d4d4; } From 594a30af32ba855bcb2b39c0c234f7eb9aa18006 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 10 Jul 2017 05:00:37 +0300 Subject: [PATCH 057/702] OCE-354 Chart toolbar positioning --- ui/oce/visualizations/map/tender-locations/style.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/visualizations/map/tender-locations/style.less b/ui/oce/visualizations/map/tender-locations/style.less index ce21870ce..e9aad9eb5 100644 --- a/ui/oce/visualizations/map/tender-locations/style.less +++ b/ui/oce/visualizations/map/tender-locations/style.less @@ -1,5 +1,5 @@ .map-chart .chart-toolbar{ position: absolute; - right: 20px; + right: 0; bottom: 102px; -} \ No newline at end of file +} From 0e29dfa40e0c19a20f9c17e1c3ec7c3ce3f00a05 Mon Sep 17 00:00:00 2001 From: Alexei Date: Wed, 12 Jul 2017 15:58:47 +0300 Subject: [PATCH 058/702] OCE-356 Linting --- ui/oce/visualizations/charts/index.jsx | 97 +++++++++++++------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/ui/oce/visualizations/charts/index.jsx b/ui/oce/visualizations/charts/index.jsx index 295017337..f28588e75 100644 --- a/ui/oce/visualizations/charts/index.jsx +++ b/ui/oce/visualizations/charts/index.jsx @@ -1,96 +1,97 @@ -import Visualization from "../../visualization"; -import ReactIgnore from "../../react-ignore"; -import {max} from "../../tools"; -import {Map} from "immutable"; -import cn from "classnames"; -import styles from "./index.less"; -import Plotly from "plotly.js/lib/core"; -Plotly.register([ - require('plotly.js/lib/bar') -]); +import { Map } from 'immutable'; +import Plotly from 'plotly.js/lib/core'; +import PlotlyBar from 'plotly.js/lib/bar'; +import Visualization from '../../visualization'; +import ReactIgnore from '../../react-ignore'; +import { max } from '../../tools'; +// eslint-disable-next-line no-unused-vars +import styles from './index.less'; -class Chart extends Visualization{ - getData(){ +Plotly.register([PlotlyBar]); + +class Chart extends Visualization { + getData() { return super.getData(); } - getDecoratedLayout(){ - const {title, xAxisRange, yAxisRange, styling, width, height, margin, legend} = this.props; + getDecoratedLayout() { + const { title, xAxisRange, yAxisRange, styling, width, height, margin, legend } = this.props; const layout = this.getLayout(); layout.width = width; layout.height = height; layout.margin = margin; - if(title) layout.title = title; - if(xAxisRange) layout.xaxis.range = xAxisRange; - if(yAxisRange) layout.yaxis.range = yAxisRange; - if(styling){ + if (title) layout.title = title; + if (xAxisRange) layout.xaxis.range = xAxisRange; + if (yAxisRange) layout.yaxis.range = yAxisRange; + if (styling) { layout.xaxis.titlefont = { - color: styling.charts.axisLabelColor + color: styling.charts.axisLabelColor, }; layout.yaxis.titlefont = { - color: styling.charts.axisLabelColor - } + color: styling.charts.axisLabelColor, + }; } - if("h" == legend){ + if (legend === 'h') { layout.legend = layout.legend || {}; - layout.legend.orientation="h"; - layout.legend.y=1.1; + layout.legend.orientation = 'h'; + layout.legend.y = 1.1; } return layout; } - componentDidMount(){ + componentDidMount() { super.componentDidMount(); - Plotly.newPlot(this.refs.chartContainer, this.getData(), this.getDecoratedLayout()); + Plotly.newPlot(this.chartContainer, this.getData(), this.getDecoratedLayout()); } - componentWillUnmount(){ - Plotly.Plots.purge(this.refs.chartContainer); + componentWillUnmount() { + Plotly.Plots.purge(this.chartContainer); } - componentDidUpdate(prevProps){ + componentDidUpdate(prevProps) { super.componentDidUpdate(prevProps); - if(this.constructor.UPDATABLE_FIELDS.some(prop => prevProps[prop] != this.props[prop]) || this.props.translations != prevProps.translations){ - this.refs.chartContainer.data = this.getData(); - this.refs.chartContainer.layout = this.getDecoratedLayout(); - setTimeout(() => Plotly.redraw(this.refs.chartContainer)); - } else if(['title', 'width', 'xAxisRange', 'yAxisRange'].some(prop => prevProps[prop] != this.props[prop])){ - setTimeout(() => Plotly.relayout(this.refs.chartContainer, this.getDecoratedLayout())); + if (this.constructor.UPDATABLE_FIELDS.some(prop => + prevProps[prop] !== this.props[prop]) || this.props.translations !== prevProps.translations) { + this.chartContainer.data = this.getData(); + this.chartContainer.layout = this.getDecoratedLayout(); + setTimeout(() => Plotly.redraw(this.chartContainer)); + } else if (['title', 'width', 'xAxisRange', 'yAxisRange'].some(prop => prevProps[prop] !== this.props[prop])) { + setTimeout(() => Plotly.relayout(this.chartContainer, this.getDecoratedLayout())); } } - hasNoData(){ - return 0 == this.getData().length; + hasNoData() { + return this.getData().length === 0; } - render(){ - const {loading} = this.state; - let hasNoData = !loading && this.hasNoData(); - return
+ render() { + const { loading } = this.state; + const hasNoData = !loading && this.hasNoData(); + return (
{hasNoData &&
{this.t('charts:general:noData')}
} {loading &&
- {this.t('general:loading')}
- + {this.t('general:loading')}
+
} -
+
{ this.chartContainer = c; }} /> -
+
); } } Chart.getFillerDatum = seed => Map(seed); -Chart.getMaxField = data => data.flatten().filter((value, key) => value && "year" != key && "month" != key).reduce(max, 0); +Chart.getMaxField = data => data.flatten().filter((value, key) => value && key !== 'year' && key !== 'month').reduce(max, 0); Chart.UPDATABLE_FIELDS = ['data']; Chart.propTypes.styling = React.PropTypes.shape({ charts: React.PropTypes.shape({ axisLabelColor: React.PropTypes.string.isRequired, - traceColors: React.PropTypes.arrayOf(React.PropTypes.string).isRequired - }).isRequired + traceColors: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + }).isRequired, }).isRequired; export default Chart; From d939b354d1f105abf553413e676c14526587f661 Mon Sep 17 00:00:00 2001 From: Alexei Date: Wed, 12 Jul 2017 16:02:31 +0300 Subject: [PATCH 059/702] OCE-356 Using `prop-types` package. --- ui/oce/visualizations/charts/index.jsx | 9 +++++---- ui/package.json | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/oce/visualizations/charts/index.jsx b/ui/oce/visualizations/charts/index.jsx index f28588e75..2f3610e03 100644 --- a/ui/oce/visualizations/charts/index.jsx +++ b/ui/oce/visualizations/charts/index.jsx @@ -1,6 +1,7 @@ import { Map } from 'immutable'; import Plotly from 'plotly.js/lib/core'; import PlotlyBar from 'plotly.js/lib/bar'; +import PropTypes from 'prop-types'; import Visualization from '../../visualization'; import ReactIgnore from '../../react-ignore'; import { max } from '../../tools'; @@ -87,10 +88,10 @@ Chart.getMaxField = data => data.flatten().filter((value, key) => value && key ! Chart.UPDATABLE_FIELDS = ['data']; -Chart.propTypes.styling = React.PropTypes.shape({ - charts: React.PropTypes.shape({ - axisLabelColor: React.PropTypes.string.isRequired, - traceColors: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, +Chart.propTypes.styling = PropTypes.shape({ + charts: PropTypes.shape({ + axisLabelColor: PropTypes.string.isRequired, + traceColors: PropTypes.arrayOf(PropTypes.string).isRequired, }).isRequired, }).isRequired; diff --git a/ui/package.json b/ui/package.json index f663db8cf..7ee8f331f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -65,6 +65,7 @@ "less": "^2.5.1", "less-loader": "^2.2.0", "plotly.js": "^1.5.1", + "prop-types": "^15.5.10", "rc-slider": "^7.0.3", "react": "^15.3.1", "react-addons-test-utils": "^15.1.0", From d0075b371b985561f2590efd9b23fc79897eb83d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 14 Jul 2017 14:39:02 +0300 Subject: [PATCH 060/702] OCE-356 Using `h` legend by default --- ui/oce/visualizations/charts/index.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/oce/visualizations/charts/index.jsx b/ui/oce/visualizations/charts/index.jsx index 2f3610e03..f08f311f7 100644 --- a/ui/oce/visualizations/charts/index.jsx +++ b/ui/oce/visualizations/charts/index.jsx @@ -36,7 +36,10 @@ class Chart extends Visualization { if (legend === 'h') { layout.legend = layout.legend || {}; layout.legend.orientation = 'h'; - layout.legend.y = 1.1; + layout.legend.xanchor = 'right'; + layout.legend.yanchor = 'bottom'; + layout.legend.x = 1; + layout.legend.y = 1; } return layout; } @@ -95,4 +98,8 @@ Chart.propTypes.styling = PropTypes.shape({ }).isRequired, }).isRequired; +Chart.defaultProps = { + legend: 'h' +} + export default Chart; From 7d139910034fc0730b5443736fa1996e3bcf2542 Mon Sep 17 00:00:00 2001 From: Alexei Date: Fri, 14 Jul 2017 14:44:21 +0300 Subject: [PATCH 061/702] OCE-356 Trailing comma to make eslint happy --- ui/oce/visualizations/charts/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/visualizations/charts/index.jsx b/ui/oce/visualizations/charts/index.jsx index f08f311f7..08956bdf8 100644 --- a/ui/oce/visualizations/charts/index.jsx +++ b/ui/oce/visualizations/charts/index.jsx @@ -99,7 +99,7 @@ Chart.propTypes.styling = PropTypes.shape({ }).isRequired; Chart.defaultProps = { - legend: 'h' -} + legend: 'h', +}; export default Chart; From a5448af2d94c255b1f4c193e66c882661ee2a818 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 17 Jul 2017 15:25:59 +0300 Subject: [PATCH 062/702] OCE-358 Plotly & React update --- ui/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/package.json b/ui/package.json index 7ee8f331f..992bd2050 100644 --- a/ui/package.json +++ b/ui/package.json @@ -64,13 +64,13 @@ "leaflet.markercluster": "https://github.com/Leaflet/Leaflet.markercluster.git#leaflet-0.7", "less": "^2.5.1", "less-loader": "^2.2.0", - "plotly.js": "^1.5.1", + "plotly.js": "^1.28.3", "prop-types": "^15.5.10", "rc-slider": "^7.0.3", - "react": "^15.3.1", + "react": "^15.6.1", "react-addons-test-utils": "^15.1.0", "react-bootstrap": "^0.30.6", - "react-dom": "^15.3.1", + "react-dom": "^15.6.1", "react-hot-loader": "^1.3.0", "react-immutable-proptypes": "^1.2.0", "react-leaflet": "^0.10.0", From 09f2f9b473a8fe9c0b4ca06974bbadd9e04a03e2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 19 Jul 2017 15:14:06 +0300 Subject: [PATCH 063/702] OCE-354 Smaller location popup header --- ui/oce/visualizations/map/style.less | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index df0f597cd..5207e6bfa 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -1,4 +1,4 @@ -@headerHeight: 64px; +@headerHeight: 48px; @windowHeight: 400px; @borderRadius: 12px; @@ -15,17 +15,16 @@ margin-right: -20px; border-top-right-radius: @borderRadius; border-top-left-radius: @borderRadius; - padding: 15px; + padding: 12px; color: white; - font-size: 2em; + font-size: 1.5em; font-weight: bolder; height: @headerHeight; } a.leaflet-popup-close-button{ - top: 1em; + top: .75em; right: 15px; - padding-top: 7px; } .tabs-bar{ From c232b31c9ebda1af5318a23d4bcb47a701f74f42 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 19 Jul 2017 15:16:09 +0300 Subject: [PATCH 064/702] OCE-354 Fixed procurement method chart margins --- ui/oce/visualizations/map/tender-locations/location.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 49c45db45..bf848b3f2 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -175,13 +175,6 @@ CostEffectivenessTab.Chart = CostEffectiveness; export class ProcurementMethodTab extends ChartTab { static getName(t) { return t('charts:procurementMethod:title'); } - - static getMargins() { - const margins = super.getMargins(); - margins.r = 100; - margins.b = 100; - return margins; - } } ProcurementMethodTab.Chart = ProcurementMethodChart; From 6a638ff370f5ee3d27108c6de2ea3b2e2286cf64 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 27 Jul 2017 21:24:44 +0300 Subject: [PATCH 065/702] OCE-355 extensions moved to non tag, added list --- .../validator/OcdsValidatorConstants.java | 10 ++++++ .../ocds/validator/OcdsValidatorRequest.java | 2 ++ .../ocds/validator/OcdsValidatorService.java | 32 +++++++++++++++++++ .../{v1.1 => }/extension.json | 0 .../{v1.1 => }/release-schema.json | 0 .../{v1.1 => }/extension.json | 0 .../{v1.1 => }/release-schema.json | 0 7 files changed, 44 insertions(+) rename validator/src/main/resources/schema/extensions/ocds_bid_extension/{v1.1 => }/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_bid_extension/{v1.1 => }/release-schema.json (100%) rename validator/src/main/resources/schema/extensions/ocds_enquiry_extension/{v1.1 => }/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_enquiry_extension/{v1.1 => }/release-schema.json (100%) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index 3dc7f6446..a4f578957 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -1,5 +1,10 @@ package org.devgateway.ocds.validator; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + /** * Created by mpostelnicu on 7/5/17. */ @@ -31,4 +36,9 @@ public static final class SchemaPrefixes { public static final String SCHEMA_POSTFIX = ".json"; + public static final Set EXTENSIONS = Collections.unmodifiableSet(new TreeSet<>(Arrays.asList( + new String[]{"ocds_bid_extension", "ocds_enquiry_extension"}))); + + public static final String EXTENSION_META = "extension.json"; + public static final String EXTENSION_RELEASE_JSON = "release-schema.json"; } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java index 8005eadcf..06818b010 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -32,6 +32,8 @@ public String getKey() { private TreeSet extensions; + private String operation; + @NotEmpty(message = "Please provide schemaType!") private String schemaType; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 09fba2cc2..955f2b87d 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -11,11 +11,14 @@ import com.github.fge.jsonschema.main.JsonSchemaFactory; import java.io.IOException; import java.net.URL; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -29,6 +32,10 @@ public class OcdsValidatorService { private Map schemaNamePrefix = new ConcurrentHashMap<>(); + private Map extensionMeta = new ConcurrentHashMap<>(); + + private Map extensionJson = new ConcurrentHashMap<>(); + @Autowired private ObjectMapper jacksonObjectMapper; @@ -43,6 +50,27 @@ private JsonNode getUnmodifiedSchemaNode(OcdsValidatorRequest request) { } } + private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest request) { + if (ObjectUtils.isEmpty(request.getExtensions())) { + return schemaNode; + } + + List unrecognizedExtensions = + request.getExtensions().stream().filter(e -> !OcdsValidatorConstants.EXTENSIONS.contains(e)) + .collect(Collectors.toList()); + + if (!unrecognizedExtensions.isEmpty()) { + throw new RuntimeException("Unknown extensions by name: " + unrecognizedExtensions); + } + + + } + + public JsonNode readExtensionData(OcdsValidatorRequest request, String extensionName) { + //reading meta + JsonLoader.fromResource(); + } + private JsonSchema getSchema(OcdsValidatorRequest request) { if (keySchema.containsKey(request.getKey())) { return keySchema.get(request.getKey()); @@ -72,6 +100,10 @@ private void initSchemaNamePrefix() { OcdsValidatorConstants.SchemaPrefixes.RELEASE_PACKAGE); } + private void initExtensions() { + + } + @PostConstruct private void init() { initSchemaNamePrefix(); diff --git a/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/release-schema.json From 39caaeefea53866892097f50963077097b32b576 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 28 Jul 2017 09:22:50 +0300 Subject: [PATCH 066/702] OCE-355 --- .../validator/OcdsValidatorConstants.java | 2 ++ .../ocds/validator/OcdsValidatorService.java | 30 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index a4f578957..192daa376 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -36,6 +36,8 @@ public static final class SchemaPrefixes { public static final String SCHEMA_POSTFIX = ".json"; + public static final String EXTENSIONS_PREFIX = "/extensions/"; + public static final Set EXTENSIONS = Collections.unmodifiableSet(new TreeSet<>(Arrays.asList( new String[]{"ocds_bid_extension", "ocds_enquiry_extension"}))); diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 955f2b87d..dad6e76d4 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -9,6 +9,8 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; + +import java.io.File; import java.io.IOException; import java.net.URL; import java.util.List; @@ -34,7 +36,7 @@ public class OcdsValidatorService { private Map extensionMeta = new ConcurrentHashMap<>(); - private Map extensionJson = new ConcurrentHashMap<>(); + private Map extensionReleaseJson = new ConcurrentHashMap<>(); @Autowired @@ -66,9 +68,26 @@ private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest reque } - public JsonNode readExtensionData(OcdsValidatorRequest request, String extensionName) { + private JsonNode readExtensionMeta(String extensionName) { //reading meta - JsonLoader.fromResource(); + try { + return JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX+ File + .separator+OcdsValidatorConstants + .EXTENSION_META); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private JsonNode readExtensionReleaseJson(String extensionName) { + //reading meta + try { + return JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX+ File + .separator+OcdsValidatorConstants + .EXTENSION_RELEASE_JSON); + } catch (IOException e) { + throw new RuntimeException(e); + } } private JsonSchema getSchema(OcdsValidatorRequest request) { @@ -102,6 +121,11 @@ private void initSchemaNamePrefix() { private void initExtensions() { + OcdsValidatorConstants.EXTENSIONS.forEach(e -> { + extensionMeta.put(e, readExtensionMeta(e)); + extensionReleaseJson.put(e, readExtensionReleaseJson(e)); + }); + } @PostConstruct From 641a84d75b62c13ea21e8ad505b1d5a0b4a6e8f0 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 28 Jul 2017 14:46:16 +0300 Subject: [PATCH 067/702] OCE-355 complete and functional validation with extensions applied --- .../validator/OcdsValidatorApiRequest.java | 4 +- .../validator/OcdsValidatorConstants.java | 15 +++-- .../ocds/validator/OcdsValidatorRequest.java | 10 ++-- .../ocds/validator/OcdsValidatorService.java | 55 ++++++++++++++----- .../test/OcdsValidatorTestRelease.java | 2 +- 5 files changed, 61 insertions(+), 25 deletions(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java index 01ff89a51..9324db3d2 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java @@ -1,6 +1,6 @@ package org.devgateway.ocds.validator; -import java.util.TreeSet; +import java.util.SortedSet; /** * Created by mpostelnicu on 7/6/17. @@ -11,7 +11,7 @@ public class OcdsValidatorApiRequest extends OcdsValidatorRequest { private String url; - public OcdsValidatorApiRequest(String version, TreeSet extensions, String schemaType) { + public OcdsValidatorApiRequest(String version, SortedSet extensions, String schemaType) { super(version, extensions, schemaType); } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index 192daa376..f3fe07bb9 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -2,7 +2,7 @@ import java.util.Arrays; import java.util.Collections; -import java.util.Set; +import java.util.SortedSet; import java.util.TreeSet; /** @@ -36,10 +36,17 @@ public static final class SchemaPrefixes { public static final String SCHEMA_POSTFIX = ".json"; - public static final String EXTENSIONS_PREFIX = "/extensions/"; + public static final class Extensions { + public static final String OCDS_BID_EXTENSION = "ocds_bid_extension"; + public static final String OCDS_ENQUIRY_EXTENSION = "ocds_enquiry_extension"; + public static final String[] ALL = {OCDS_BID_EXTENSION, OCDS_ENQUIRY_EXTENSION}; + } + + public static final String EXTENSIONS_PREFIX = "/schema/extensions/"; + + public static final SortedSet EXTENSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( + Extensions.ALL))); - public static final Set EXTENSIONS = Collections.unmodifiableSet(new TreeSet<>(Arrays.asList( - new String[]{"ocds_bid_extension", "ocds_enquiry_extension"}))); public static final String EXTENSION_META = "extension.json"; public static final String EXTENSION_RELEASE_JSON = "release-schema.json"; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java index 06818b010..5cd102f78 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -1,6 +1,6 @@ package org.devgateway.ocds.validator; -import java.util.TreeSet; +import java.util.SortedSet; import org.hibernate.validator.constraints.NotEmpty; /** @@ -14,7 +14,7 @@ public OcdsValidatorRequest(OcdsValidatorRequest request) { this.schemaType = request.getSchemaType(); } - public OcdsValidatorRequest(String version, TreeSet extensions, String schemaType) { + public OcdsValidatorRequest(String version, SortedSet extensions, String schemaType) { this.version = version; this.extensions = extensions; this.schemaType = schemaType; @@ -30,7 +30,7 @@ public String getKey() { private String version; - private TreeSet extensions; + private SortedSet extensions; private String operation; @@ -45,11 +45,11 @@ public void setVersion(String version) { this.version = version; } - public TreeSet getExtensions() { + public SortedSet getExtensions() { return extensions; } - public void setExtensions(TreeSet extensions) { + public void setExtensions(SortedSet extensions) { this.extensions = extensions; } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index dad6e76d4..58896f49b 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -3,13 +3,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.fge.jackson.JsonLoader; +import com.github.fge.jsonpatch.JsonPatchException; +import com.github.fge.jsonpatch.mergepatch.JsonMergePatch; import com.github.fge.jsonschema.core.exceptions.ProcessingException; import com.github.fge.jsonschema.core.report.ListReportProvider; import com.github.fge.jsonschema.core.report.LogLevel; import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; - import java.io.File; import java.io.IOException; import java.net.URL; @@ -18,6 +19,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; @@ -29,6 +32,7 @@ @Service public class OcdsValidatorService { + private final Logger logger = LoggerFactory.getLogger(OcdsValidatorService.class); private Map keySchema = new ConcurrentHashMap<>(); @@ -36,7 +40,7 @@ public class OcdsValidatorService { private Map extensionMeta = new ConcurrentHashMap<>(); - private Map extensionReleaseJson = new ConcurrentHashMap<>(); + private Map extensionReleaseJson = new ConcurrentHashMap<>(); @Autowired @@ -44,6 +48,8 @@ public class OcdsValidatorService { private JsonNode getUnmodifiedSchemaNode(OcdsValidatorRequest request) { try { + logger.debug("Loading unmodified schema node of type " + request.getSchemaType() + " version " + + request.getVersion()); return JsonLoader.fromResource(schemaNamePrefix.get(request.getSchemaType()) + request.getVersion() + OcdsValidatorConstants.SCHEMA_POSTFIX); } catch (IOException e) { @@ -54,6 +60,7 @@ private JsonNode getUnmodifiedSchemaNode(OcdsValidatorRequest request) { private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest request) { if (ObjectUtils.isEmpty(request.getExtensions())) { + logger.debug("No schema extensions are requested."); return schemaNode; } @@ -65,42 +72,60 @@ private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest reque throw new RuntimeException("Unknown extensions by name: " + unrecognizedExtensions); } + //TODO: check extension meta and see if they apply for the current standard + + JsonNode schemaResult = null; + + for (String ext : request.getExtensions()) { + try { + logger.debug("Applying schema extension " + ext); + schemaResult = extensionReleaseJson.get(ext).apply(schemaNode); + } catch (JsonPatchException e) { + throw new RuntimeException(e); + } + } + return schemaResult; } private JsonNode readExtensionMeta(String extensionName) { //reading meta try { - return JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX+ File - .separator+OcdsValidatorConstants + logger.debug("Reading extension metadata for extension " + extensionName); + return JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX + extensionName + File + .separator + OcdsValidatorConstants .EXTENSION_META); } catch (IOException e) { throw new RuntimeException(e); } } - private JsonNode readExtensionReleaseJson(String extensionName) { + private JsonMergePatch readExtensionReleaseJson(String extensionName) { //reading meta try { - return JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX+ File - .separator+OcdsValidatorConstants + logger.debug("Reading extension JSON contents for extension " + extensionName); + JsonNode jsonMergePatch = JsonLoader.fromResource(OcdsValidatorConstants.EXTENSIONS_PREFIX + + extensionName + File.separator + OcdsValidatorConstants .EXTENSION_RELEASE_JSON); - } catch (IOException e) { + JsonMergePatch patch = JsonMergePatch.fromJson(jsonMergePatch); + return patch; + } catch (IOException | JsonPatchException e) { throw new RuntimeException(e); } } private JsonSchema getSchema(OcdsValidatorRequest request) { if (keySchema.containsKey(request.getKey())) { + logger.debug("Returning cached schema with extensions " + request.getKey()); return keySchema.get(request.getKey()); } else { JsonNode schemaNode = getUnmodifiedSchemaNode(request); - //TODO: this is where we should apply extensions ! - + schemaNode = applyExtensions(schemaNode, request); try { JsonSchema schema = JsonSchemaFactory.newBuilder() .setReportProvider(new ListReportProvider(LogLevel.ERROR, LogLevel.FATAL)).freeze() .getJsonSchema(schemaNode); + logger.debug("Saving to cache schema with extensions " + request.getKey()); keySchema.put(request.getKey(), schema); return schema; } catch (ProcessingException e) { @@ -112,6 +137,7 @@ private JsonSchema getSchema(OcdsValidatorRequest request) { } private void initSchemaNamePrefix() { + logger.debug("Initializing prefixes for all available schemas"); schemaNamePrefix.put(OcdsValidatorConstants.Schemas.RELEASE, OcdsValidatorConstants.SchemaPrefixes.RELEASE); schemaNamePrefix.put(OcdsValidatorConstants.Schemas.RECORD_PACKAGE, OcdsValidatorConstants.SchemaPrefixes.RECORD_PACKAGE); @@ -120,22 +146,25 @@ private void initSchemaNamePrefix() { } private void initExtensions() { - + logger.debug("Initializing schema extensions"); OcdsValidatorConstants.EXTENSIONS.forEach(e -> { + logger.debug("Initializing schema extension " + e); extensionMeta.put(e, readExtensionMeta(e)); extensionReleaseJson.put(e, readExtensionReleaseJson(e)); }); - } @PostConstruct private void init() { initSchemaNamePrefix(); + initExtensions(); } public ProcessingReport validate(OcdsValidatorApiRequest request) { - + logger.debug("Running validation for api request for schema of type " + request.getSchemaType() + + +" and version " + request.getVersion()); OcdsValidatorNodeRequest nodeRequest = convertApiRequestToNodeRequest(request); if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { diff --git a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java index d297d5978..06c67d145 100644 --- a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java +++ b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java @@ -33,7 +33,7 @@ public class OcdsValidatorTestRelease { public void testReleaseValidation() { OcdsValidatorApiRequest request=new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, - null, OcdsValidatorConstants.Schemas.RELEASE); + OcdsValidatorConstants.EXTENSIONS, OcdsValidatorConstants.Schemas.RELEASE); InputStream resourceAsStream = this.getClass().getResourceAsStream("/full-release.json"); try { request.setJson(StreamUtils.copyToString(resourceAsStream, Charset.defaultCharset())); From 03b708b7c614df416dce77e669ef9385bd08e7ac Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 28 Jul 2017 15:40:39 +0300 Subject: [PATCH 068/702] OCE-355 all core extensions added --- .../validator/OcdsValidatorConstants.java | 10 +- .../ocds_location_extension/extension.json | 4 + .../release-schema.json | 74 ++++++ .../ocds_lots_extension/extension.json | 6 + .../ocds_lots_extension/release-schema.json | 216 ++++++++++++++++++ .../extension.json | 6 + .../release-schema.json | 18 ++ .../extension.json | 4 + .../release-schema.json | 58 +++++ .../extension.json | 6 + .../release-schema.json | 20 ++ 11 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index f3fe07bb9..797b218e0 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -39,7 +39,15 @@ public static final class SchemaPrefixes { public static final class Extensions { public static final String OCDS_BID_EXTENSION = "ocds_bid_extension"; public static final String OCDS_ENQUIRY_EXTENSION = "ocds_enquiry_extension"; - public static final String[] ALL = {OCDS_BID_EXTENSION, OCDS_ENQUIRY_EXTENSION}; + public static final String OCDS_LOCATION_EXTENSION = "ocds_location_extension"; + public static final String OCDS_LOTS_EXTENSION = "ocds_lots_extension"; + public static final String OCDS_MILESTONE_DOCUMENTS_EXTENSION = "ocds_milestone_documents_extension"; + public static final String OCDS_PARTICIPATION_FEE_EXTENSION = "ocds_participationFee_extension"; + public static final String OCDS_PROCESS_TITLE_EXTENSION = "ocds_process_title_extension"; + + public static final String[] ALL = {OCDS_BID_EXTENSION, OCDS_ENQUIRY_EXTENSION, OCDS_LOCATION_EXTENSION, + OCDS_LOTS_EXTENSION, OCDS_MILESTONE_DOCUMENTS_EXTENSION, OCDS_PARTICIPATION_FEE_EXTENSION, + OCDS_PROCESS_TITLE_EXTENSION}; } public static final String EXTENSIONS_PREFIX = "/schema/extensions/"; diff --git a/validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json new file mode 100644 index 000000000..31f4b0c0e --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json @@ -0,0 +1,4 @@ +{ + "name": "Location", + "description": "Communicates the location of proposed or executed contract delivery." +} diff --git a/validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json new file mode 100644 index 000000000..2b879b881 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json @@ -0,0 +1,74 @@ +{ + "definitions": { + "Item": { + "properties": { + "deliveryLocation": { + "title":"Delivery Location", + "$ref": "#/definitions/Location" + }, + "deliveryAddress": { + "title":"Delivery Address", + "$ref": "#/definitions/Address" + } + } + }, + "Location": { + "type": "object", + "title": "Delivery Location", + "description": "The location where activity related to this tender, contract or license will be delivered, or will take place. A location can be described by either a geometry (point location, line or polygon), or a gazetteer entry, or both.", + "properties": { + "description":{ + "title":"Description", + "description":"A name or description of this location. This might include the name(s) of the location(s), or might provide a human readable description of the location to be covered. This description may be used in a user-interface.", + "type": ["string","null"] + }, + "geometry": { + "type":"object", + "title":"Geometry", + "description":"We follow the [GeoJSON standard](http://geojson.org/) to express basic location information, using latitude and longitude values in the [WGS84](https://en.wikipedia.org/wiki/World_Geodetic_System) (EPSG:4326) projection. A point location can be identified by geocoding a delivery address. For concession licenses, or other contracts covering a polygon location which is not contained in a known gazetteer, polygon and multi-polygon can be used. ", + "notes_for_guidance":"The guidance notes should describe when to use MultiPoint, and when to use multiple line-items each with their own locations", + "properties": { + "type": { + "title":"Type", + "description": "The type of [GeoJSON Geometry Objects](http://geojson.org/geojson-spec.html#geometry-objects) being provided. To provide latitude and longitude use 'point', and enter an array of [latitude,longitude] as the value of coordinates field: e.g. [37.42,-122.085]. Note the capitalization of type values - set in order to maintain compatibility with GeoJSON.", + "type": ["string", "null"], + "enum": ["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon"] + }, + "coordinates": { + "title":"Coordinates", + "description": "The relevant array of points, e.g. [latitude,longitude], or nested array, for the GeoJSON geometry being described. The longitude and latitude MUST be expressed in decimal degrees in the WGS84 (EPSG:4326) projection", + "type": ["array","null"], + "items": {"type": ["number","array"]} + } + } + }, + "gazetteer": { + "type": "object", + "properties": { + "scheme": { + "title":"Gazetteer scheme", + "description": "The entry of the selected gazetteer in the gazetteers codelist. The codelist provides details of services, where available, that can resolve a gazetteer entry to provide location names.", + "type": ["string", "null"], + "codelist":"locationGazeteers.csv", + "openCodelist":true + }, + "identifiers": { + "title":"Identifiers", + "description": "An array of one or more codes drawn from the gazetteer indicated in scheme.", + "type": ["array", "null"], + "items": { + "type": ["string","null"] + } + } + } + }, + "uri": { + "type":["string","null"], + "title":"URI", + "description":"A URI to a further description of the activity location. This may be a human readable document with information on the location, or a machine-readable description of the location." + } + } + + } + } +} diff --git a/validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json new file mode 100644 index 000000000..ec4a7d731 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Lots", + "description": "A tender process may be divided into lots, where bidders can bid on one or more lots. Details of each lot can be provided here. Items, documents and other features can then reference the lot they are related to using relatedLot. Where no relatedLot identifier is given, the values should be interpreted as applicable to the whole tender.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json new file mode 100644 index 000000000..19864b4df --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json @@ -0,0 +1,216 @@ +{ + "definitions": { + "Tender": { + "properties": { + "lots": { + "title": "Lots", + "description": "A tender process may be divided into lots, where bidders can bid on one or more lots. Details of each lot can be provided here. Items, documents and other features can then reference the lot they are related to using relatedLot. Where no relatedLots identifier is given, the values should be interpreted as applicable to the whole tender. Properties of tender can be overridden for a given Lot through their inclusion in the Lot object.", + "type": "array", + "items": { + "$ref": "#/definitions/Lot" + } + }, + "lotDetails": { + "$ref": "#/definitions/LotDetails" + }, + "lotGroups": { + "title": "Lot groups", + "description": "Where the buyer reserves the right to combine lots, or wishes to specify the total value for a group of lots, a lot group is used to capture this information.", + "type":"array", + "items":{ + "$ref": "#/definitions/LotGroup" + } + } + } + }, + "Document": { + "properties": { + "relatedLots": { + "title": "Related lot(s)", + "description": "If this document relates to a particular lot, provide the identifier(s) of the related lot(s) here.", + "type": [ + "array" + ], + "items": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "Item": { + "properties": { + "relatedLot": { + "title": "Related lot", + "description": "If this item belongs to a lot, provide the identifier of the related lot here. Each item may only belong to a single lot.", + "type": [ + "string", + "null" + ] + } + } + }, + "Milestone": { + "properties": { + "relatedLots": { + "title": "Related lot(s)", + "description": "If this milestone relates to a particular lot, provide the identifier(s) of the related lot(s) here.", + "type": [ + "array" + ], + "items": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "Award": { + "properties": { + "relatedLots": { + "title": "Related lot(s)", + "description": "If this award relates to one or more specific lots, provide the identifier(s) of the related lot(s) here.", + "type": [ + "array" + ], + "items": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "Bid": { + "properties": { + "relatedLots": { + "title": "Related lot(s)", + "description": "If this bid relates to one or more specific lots, provide the identifier(s) of the related lot(s) here.", + "type": [ + "array" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "NOTE": "Compare againt lotValues approach in open procument API - http://api-docs.openprocurement.org/en/latest/lots.html" + } + } + }, + "Lot": { + "title": "Lots", + "description": "A lot is a grouping of items within a tender that can be bid on or awarded together.", + "type": "object", + "properties": { + "id": { + "title": "Lot ID", + "type": "string", + "description": "A local identifier for this lot, such as a lot number. This is used in relatedLots references at the item, document and award level." + }, + "title": { + "title": "Title", + "type": [ + "string", + "null" + ], + "description": "A title for this lot." + }, + "description": { + "title": "Description", + "description": "A description of this lot.", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Lot Status", + "description": "The current status of the process related to this lot based on the [tenderStatus codelist](http://ocds.open-contracting.org/standard/r/1__0__0/en/schema/codelists#tender-status)", + "type": [ + "string", + "null" + ], + "codelist": "tenderStatus.csv", + "openCodelist": false + }, + "value": { + "title": "Lot value", + "$ref": "#/definitions/Value", + "description": "The maximum estimated value of this lot." + } + } + }, + "LotDetails": { + "title": "Lot Details", + "description": "If this tender is divided into lots, details can be provided here of any criteria that apply to bidding on these lots. This extended property is currently focussed on fields required by the EU TED data standard", + "type": [ + "object", + "null" + ], + "properties": { + "maximumLotsBidPerSupplier": { + "title": "Maximum lots per supplier", + "description": "The maximum number of lots that one supplier may bid for as part of this contracting process.", + "type": [ + "integer", + "null" + ] + }, + "maximumLotsAwardedPerSupplier": { + "title": "Maximum lots per supplier", + "description": "The maximum number of lots that may be awarded to one supplier as part of this contracting process.", + "type": [ + "integer", + "null" + ] + } + } + }, + "LotGroup": { + "title":"Lot group", + "description":"Where the buyer reserves the right to combine lots, or wishes to specify the total value for a group of lots, a lot group is used to capture this information.", + "type":"object", + "properties":{ + "id":{ + "title": "Lot group identifier", + "type": "string", + "description": "A local identifier for this group of lots." + }, + "relatedLots": { + "title": "Related lot(s)", + "description": "A list of the identifiers of the lots that form this group. Lots may appear in more than one group.", + "type": [ + "array" + ], + "items": { + "type": [ + "string", + "null" + ] + } + }, + "optionToCombine":{ + "title":"Option to combine", + "description":"The buyer reserves the right to combine the lots in this group when awarding a contract.", + "type":[ + "boolean", + "null" + ] + }, + "maximumValue":{ + "title": "Maximum value", + "description": "The maximum estimated value of the lots in this group. This may be lower than the sum total of lot values", + "$ref": "#/definitions/Value" + } + } + + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json new file mode 100644 index 000000000..13b05a138 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Milestone documents", + "description": "Documents at the milestone level were deprecated in OCDS 1.1. This extension re-introduces the ability to attach documents to each individual milestone.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json new file mode 100644 index 000000000..b16404e86 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json @@ -0,0 +1,18 @@ +{ + "definitions": { + "Milestone": { + "properties": { + "documents": { + "title": "Documents", + "description": "A list of documents specifically associated with this milestone.", + "type": "array", + "deprecated": null, + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json new file mode 100644 index 000000000..98632abe0 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json @@ -0,0 +1,4 @@ +{ + "name": "Participation Fees", + "description": "Where a tender process involves payment of fees to access documents, submit a proposal, or be awarded a contract, this extension can be used to provide fee details." +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json new file mode 100644 index 000000000..4b41b3a01 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json @@ -0,0 +1,58 @@ +{ + "definitions": { + "ParticipationFee": { + "title": "Participation fee", + "type": "object", + "properties": { + "type": { + "title": "Fee type", + "description": "A fees applicable to bidders wishing to participate in the tender process. Fees may apply for access to bidding documents, for the submission of bids or there may be a win fee payable by the successful bidder.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string", + "enum": [ + "document", + "deposit", + "submission", + "win", + null + ] + } + }, + "value": { + "$ref": "#/definitions/Value" + }, + "description": { + "title": "Description", + "description": "Optional information about the way in which fees are levied, or the exact nature of the fees.", + "type": "string" + }, + "methodOfPayment":{ + "title":"Method(s) of payment", + "description":"Optional information about the way in which fees can be paid.", + "type":["array","null"], + "items":{ + "type":"string" + } + } + } + }, + "Tender": { + "properties": { + "participationFees": { + "title": "Participation fees", + "description": "Any fees applicable to bidders wishing to participate in the tender process. Fees may apply for access to bidding documents, for the submission of bids or there may be a win fee payable by the successful bidder.", + "type": "array", + "mergeStrategy": "ocdsVersion", + "items": { + "$ref": "#/definitions/ParticipationFee" + }, + "uniqueItems": true + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json new file mode 100644 index 000000000..42626627a --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Process level title and description", + "description": "For providing overall process titles and descriptions, often to give a free-text summary of the contracting process as a whole.", + "compatibility": ">=1.1", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json new file mode 100644 index 000000000..cb027dd8e --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json @@ -0,0 +1,20 @@ +{ + "properties": { + "title": { + "title": "Title", + "type": [ + "string", + "null" + ], + "description": "A overall title for this contracting process or release." + }, + "description": { + "title": "Description", + "type": [ + "string", + "null" + ], + "description": "An overall description for this contracting process or release. This should not replace provision of a detailed breakdown of the objects of the contracting process in the planning, tender, award or contracts section." + } + } +} \ No newline at end of file From 5663315a8acfdc8c7e89c265ddcb7bdcf8e41e42 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 28 Jul 2017 18:20:44 +0300 Subject: [PATCH 069/702] OCE-355 fixed loop schema apply --- .../devgateway/ocds/validator/OcdsValidatorService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 58896f49b..e6159100f 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -74,12 +74,12 @@ private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest reque //TODO: check extension meta and see if they apply for the current standard - JsonNode schemaResult = null; + JsonNode schemaResult = schemaNode; for (String ext : request.getExtensions()) { try { logger.debug("Applying schema extension " + ext); - schemaResult = extensionReleaseJson.get(ext).apply(schemaNode); + schemaResult = extensionReleaseJson.get(ext).apply(schemaResult); } catch (JsonPatchException e) { throw new RuntimeException(e); } @@ -163,8 +163,7 @@ private void init() { public ProcessingReport validate(OcdsValidatorApiRequest request) { logger.debug("Running validation for api request for schema of type " + request.getSchemaType() - + -" and version " + request.getVersion()); + + " and version " + request.getVersion()); OcdsValidatorNodeRequest nodeRequest = convertApiRequestToNodeRequest(request); if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { From 567d04ada8b88a22a3d48dc0938ddfaf177749fb Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Sat, 29 Jul 2017 00:22:08 +0300 Subject: [PATCH 070/702] OCE-336 Added DLL dramatically speeding up webpack dev build --- ui/package.json | 3 ++- ui/webpack.config.js | 3 ++- ui/webpack.dev.config.js | 7 ++++++- ui/webpack.dll.config.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 ui/webpack.dll.config.js diff --git a/ui/package.json b/ui/package.json index 992bd2050..2f24c412f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -8,7 +8,8 @@ "test": "jest", "build": "gulp", "extract-translatables": "extract-gettext -o languages/en_US.json components/", - "lint": "eslint index.jsx" + "lint": "eslint index.jsx", + "build-dll": "webpack --config webpack.dll.config.js" }, "jest": { "scriptPreprocessor": "./node_modules/babel-jest", diff --git a/ui/webpack.config.js b/ui/webpack.config.js index c3e9d47b1..690ada000 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -5,7 +5,8 @@ config.output.filename = "index.min.js"; delete config.output.publicPath; delete config.devtool; config.plugins = config.plugins.filter(function(plugin){ - return !(plugin instanceof webpack.HotModuleReplacementPlugin); + return !(plugin instanceof webpack.HotModuleReplacementPlugin) && + !(plugin instanceof webpack.DllReferencePlugin); }).concat([ new webpack.DefinePlugin({ "process.env": { diff --git a/ui/webpack.dev.config.js b/ui/webpack.dev.config.js index b3e3aab5e..c04ec5751 100644 --- a/ui/webpack.dev.config.js +++ b/ui/webpack.dev.config.js @@ -31,10 +31,15 @@ module.exports = { }, devtool: 'source-map', plugins: [ + new webpack.DllReferencePlugin({ + context: __dirname, + name: 'lib', + manifest: require('./dll/manifest.json') + }), new webpack.HotModuleReplacementPlugin(), new webpack.ProvidePlugin({ fetch: 'imports?this=>global!exports?global.fetch!whatwg-fetch', - React: "react" + React: "react", }) ], eslint:{ diff --git a/ui/webpack.dll.config.js b/ui/webpack.dll.config.js new file mode 100644 index 000000000..dc29288b1 --- /dev/null +++ b/ui/webpack.dll.config.js @@ -0,0 +1,32 @@ +var webpack = require('webpack'); +var path = require('path'); + +module.exports = { + entry: { + lib: [ + 'react', + 'react-dom', + 'react-bootstrap', + 'plotly.js', + 'react-leaflet', + 'plotly.js/lib/core', + 'plotly.js/lib/bar', + 'plotly.js/lib/pie.js' + ] + }, + + output: { + path: path.join(__dirname, 'dll'), + filename: '[name].js', + library: '[name]', + libraryTarget: 'var' + }, + + plugins: [ + new webpack.DllPlugin({ + path: './dll/manifest.json', + name: '[name]', + context: __dirname + }) + ] +} From 3959f9eeb0b6be1755c059b40a76fb1dce7dfb87 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 31 Jul 2017 03:04:58 +0300 Subject: [PATCH 071/702] OCE-336 Linting --- ui/oce/visualizations/map/index.jsx | 69 +++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/ui/oce/visualizations/map/index.jsx b/ui/oce/visualizations/map/index.jsx index 6b3fc076b..cc8d79074 100644 --- a/ui/oce/visualizations/map/index.jsx +++ b/ui/oce/visualizations/map/index.jsx @@ -1,46 +1,49 @@ -import frontendDateFilterable from "../frontend-date-filterable"; import { Map, TileLayer } from 'react-leaflet'; -import {pluck} from "../../tools"; -import Cluster from "./cluster"; -import Location from "./location"; -import Visualization from "../../visualization"; -import style from "./style.less"; +import frontendDateFilterable from '../frontend-date-filterable'; +import { pluck } from '../../tools'; +import Cluster from './cluster'; +import Location from './location'; +import Visualization from '../../visualization'; +// eslint-disable-next-line no-unused-vars +import style from './style.less'; -class MapVisual extends frontendDateFilterable(Visualization){ - getMaxAmount(){ +class MapVisual extends frontendDateFilterable(Visualization) { + getMaxAmount() { return Math.max(0, ...this.getData().map(pluck('amount'))); } - getTiles(){ + getTiles () { return ( - - ) + + ); } - render(){ - let {translations, filters, years, styling, monthly, months, center, zoom} = this.props; - return - {this.getTiles()} - - {this.getData().map(location => ( + render() { + const { translations, filters, years, styling, monthly, months, center, zoom } = this.props; + return ( + + {this.getTiles()} + + {this.getData().map(location => ( - ))} - - + ))} + + + ); } } From 93f85750f04bb8f339a7ef2043a89730ee25225a Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 31 Jul 2017 03:36:40 +0300 Subject: [PATCH 072/702] OCE-336 Computing the map bounds dynamically --- ui/oce/visualizations/map/index.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/oce/visualizations/map/index.jsx b/ui/oce/visualizations/map/index.jsx index cc8d79074..35a25f806 100644 --- a/ui/oce/visualizations/map/index.jsx +++ b/ui/oce/visualizations/map/index.jsx @@ -7,6 +7,8 @@ import Visualization from '../../visualization'; // eslint-disable-next-line no-unused-vars import style from './style.less'; +const swap = ([a, b]) => [b, a]; + class MapVisual extends frontendDateFilterable(Visualization) { getMaxAmount() { return Math.max(0, ...this.getData().map(pluck('amount'))); @@ -22,9 +24,13 @@ class MapVisual extends frontendDateFilterable(Visualization) { } render() { - const { translations, filters, years, styling, monthly, months, center, zoom } = this.props; + const { translations, filters, years, styling, monthly, months, zoom } = this.props; + const center = this.props.data ? + L.latLngBounds(this.getData().map(pluck('coords')).map(swap)).getCenter() : + [0, 0]; + return ( - + {this.getTiles()} {this.getData().map(location => ( From f5db313a7be24b4124888359103b98f84b79a161 Mon Sep 17 00:00:00 2001 From: Alexei Date: Mon, 31 Jul 2017 03:39:20 +0300 Subject: [PATCH 073/702] OCE-336 Linting --- ui/oce/visualizations/map/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/oce/visualizations/map/index.jsx b/ui/oce/visualizations/map/index.jsx index 35a25f806..09e31c35d 100644 --- a/ui/oce/visualizations/map/index.jsx +++ b/ui/oce/visualizations/map/index.jsx @@ -26,11 +26,12 @@ class MapVisual extends frontendDateFilterable(Visualization) { render() { const { translations, filters, years, styling, monthly, months, zoom } = this.props; const center = this.props.data ? + // eslint-disable-next-line no-undef L.latLngBounds(this.getData().map(pluck('coords')).map(swap)).getCenter() : [0, 0]; return ( - + {this.getTiles()} {this.getData().map(location => ( From 6a84cf621b83edca45ed53a3ae98d2047fb5ff72 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 31 Jul 2017 03:44:09 +0300 Subject: [PATCH 074/702] OCE-336 Added empty manifest.json to prevent require error during build --- ui/dll/manifest.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 ui/dll/manifest.json diff --git a/ui/dll/manifest.json b/ui/dll/manifest.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/ui/dll/manifest.json @@ -0,0 +1 @@ +{} From 1733bd3ead5ab29a2ecf15b564fe2d07d4deb905 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 31 Jul 2017 15:39:16 +0300 Subject: [PATCH 075/702] OCE-355 removed id from schema to prevenit downloading the schema json --- .../src/main/resources/schema/release/release-schema-1.0.0.json | 1 - .../src/main/resources/schema/release/release-schema-1.0.1.json | 1 - .../src/main/resources/schema/release/release-schema-1.0.2.json | 1 - .../src/main/resources/schema/release/release-schema-1.1.0.json | 1 - 4 files changed, 4 deletions(-) diff --git a/validator/src/main/resources/schema/release/release-schema-1.0.0.json b/validator/src/main/resources/schema/release/release-schema-1.0.0.json index 72c13945d..4e2497dc3 100644 --- a/validator/src/main/resources/schema/release/release-schema-1.0.0.json +++ b/validator/src/main/resources/schema/release/release-schema-1.0.0.json @@ -1,5 +1,4 @@ { - "id": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json", "$schema": "http://json-schema.org/draft-04/schema#", "title": "Schema for an Open Contracting Release", "type": "object", diff --git a/validator/src/main/resources/schema/release/release-schema-1.0.1.json b/validator/src/main/resources/schema/release/release-schema-1.0.1.json index ccb60b643..3be398b46 100644 --- a/validator/src/main/resources/schema/release/release-schema-1.0.1.json +++ b/validator/src/main/resources/schema/release/release-schema-1.0.1.json @@ -1,5 +1,4 @@ { - "id": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json", "$schema": "http://json-schema.org/draft-04/schema#", "title": "Schema for an Open Contracting Release", "type": "object", diff --git a/validator/src/main/resources/schema/release/release-schema-1.0.2.json b/validator/src/main/resources/schema/release/release-schema-1.0.2.json index f6afc46cc..ae90741f5 100644 --- a/validator/src/main/resources/schema/release/release-schema-1.0.2.json +++ b/validator/src/main/resources/schema/release/release-schema-1.0.2.json @@ -1,5 +1,4 @@ { - "id": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json", "$schema": "http://json-schema.org/draft-04/schema#", "title": "Schema for an Open Contracting Release", "type": "object", diff --git a/validator/src/main/resources/schema/release/release-schema-1.1.0.json b/validator/src/main/resources/schema/release/release-schema-1.1.0.json index 4e3c2573e..74435b867 100644 --- a/validator/src/main/resources/schema/release/release-schema-1.1.0.json +++ b/validator/src/main/resources/schema/release/release-schema-1.1.0.json @@ -1,5 +1,4 @@ { - "id": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json", "$schema": "http://json-schema.org/draft-04/schema#", "title": "Schema for an Open Contracting Release", "type": "object", From 7d4fc64078041720439b05cdc4f1f59e676f69c8 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 1 Aug 2017 13:26:53 +0300 Subject: [PATCH 076/702] OCE-355 moved official extensions to v1.1 folder for future versioning plus added half of community extensions for offline use --- .../validator/OcdsValidatorConstants.java | 36 ++++-- .../extension.json | 6 + .../release-schema.json | 36 ++++++ .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../extension.json | 5 + .../release-schema.json | 65 +++++++++++ .../versioned-release-validation-schema.json | 105 ++++++++++++++++++ .../extension.json | 9 ++ .../release-schema.json | 96 ++++++++++++++++ .../ocds_charges_extension/extension.json | 6 + .../release-schema.json | 65 +++++++++++ .../extension.json | 6 + .../release-schema.json | 17 +++ .../extension.json | 1 + .../release-schema.json | 52 +++++++++ .../versioned-release-validation-schema.json | 48 ++++++++ .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../extension.json | 6 + .../release-schema.json | 15 +++ .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../extension.json | 5 + .../release-schema.json | 13 +++ .../versioned-release-validation-schema.json | 18 +++ .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 .../{ => v1.1}/extension.json | 0 .../{ => v1.1}/release-schema.json | 0 34 files changed, 599 insertions(+), 11 deletions(-) create mode 100644 validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/release-schema.json rename validator/src/main/resources/schema/extensions/ocds_bid_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_bid_extension/{ => v1.1}/release-schema.json (100%) create mode 100644 validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/versioned-release-validation-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_charges_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_charges_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_documentation_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_documentation_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_documentation_extension/versioned-release-validation-schema.json rename validator/src/main/resources/schema/extensions/ocds_enquiry_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_enquiry_extension/{ => v1.1}/release-schema.json (100%) create mode 100644 validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/release-schema.json rename validator/src/main/resources/schema/extensions/ocds_location_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_location_extension/{ => v1.1}/release-schema.json (100%) rename validator/src/main/resources/schema/extensions/ocds_lots_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_lots_extension/{ => v1.1}/release-schema.json (100%) rename validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/{ => v1.1}/release-schema.json (100%) create mode 100644 validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/extension.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/release-schema.json create mode 100644 validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/versioned-release-validation-schema.json rename validator/src/main/resources/schema/extensions/ocds_participationFee_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_participationFee_extension/{ => v1.1}/release-schema.json (100%) rename validator/src/main/resources/schema/extensions/ocds_process_title_extension/{ => v1.1}/extension.json (100%) rename validator/src/main/resources/schema/extensions/ocds_process_title_extension/{ => v1.1}/release-schema.json (100%) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index 797b218e0..04be72c8f 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -37,17 +37,31 @@ public static final class SchemaPrefixes { public static final String SCHEMA_POSTFIX = ".json"; public static final class Extensions { - public static final String OCDS_BID_EXTENSION = "ocds_bid_extension"; - public static final String OCDS_ENQUIRY_EXTENSION = "ocds_enquiry_extension"; - public static final String OCDS_LOCATION_EXTENSION = "ocds_location_extension"; - public static final String OCDS_LOTS_EXTENSION = "ocds_lots_extension"; - public static final String OCDS_MILESTONE_DOCUMENTS_EXTENSION = "ocds_milestone_documents_extension"; - public static final String OCDS_PARTICIPATION_FEE_EXTENSION = "ocds_participationFee_extension"; - public static final String OCDS_PROCESS_TITLE_EXTENSION = "ocds_process_title_extension"; - - public static final String[] ALL = {OCDS_BID_EXTENSION, OCDS_ENQUIRY_EXTENSION, OCDS_LOCATION_EXTENSION, - OCDS_LOTS_EXTENSION, OCDS_MILESTONE_DOCUMENTS_EXTENSION, OCDS_PARTICIPATION_FEE_EXTENSION, - OCDS_PROCESS_TITLE_EXTENSION}; + //OFFICIAL + public static final String OCDS_BID_EXTENSION_V1_1 = "ocds_bid_extension/v1.1"; + public static final String OCDS_ENQUIRY_EXTENSION_V1_1 = "ocds_enquiry_extension/v1.1"; + public static final String OCDS_LOCATION_EXTENSION_V1_1 = "ocds_location_extension/v1.1"; + public static final String OCDS_LOTS_EXTENSION_V1_1 = "ocds_lots_extension/v1.1"; + public static final String OCDS_MILESTONE_DOCUMENTS_EXTENSION_V1_1 = "ocds_milestone_documents_extension/v1.1"; + public static final String OCDS_PARTICIPATION_FEE_EXTENSION_V1_1 = "ocds_participationFee_extension/v1.1"; + public static final String OCDS_PROCESS_TITLE_EXTENSION_V1_1 = "ocds_process_title_extension/v1.1"; + + //COMMUNITY + public static final String OCDS_ADDITIONAl_CONTACT_POINTS_EXTENSION = "ocds_additionalContactPoints_extension"; + public static final String OCDS_BUDGET_BREAKDOWN_EXTENSION = "ocds_budget_breakdown_extension"; + public static final String OCDS_BUDGET_PROJECTS_EXTENSION = "ocds_budget_projects_extension"; + public static final String OCDS_CHARGES_EXTENSION = "ocds_charges_extension"; + public static final String OCDS_CONTRACT_SUPPLIERS_EXTENSION = "ocds_contract_suppliers_extension"; + public static final String OCDS_DOCUMENTATION_EXTENSION = "ocds_documentation_extension"; + public static final String OCDS_EXTENDS_CONTRACTID_EXTENSION = "ocds_extendsContractID_extension"; + + public static final String[] ALL = {OCDS_BID_EXTENSION_V1_1, OCDS_ENQUIRY_EXTENSION_V1_1, + OCDS_LOCATION_EXTENSION_V1_1, OCDS_LOTS_EXTENSION_V1_1, OCDS_MILESTONE_DOCUMENTS_EXTENSION_V1_1, + OCDS_PARTICIPATION_FEE_EXTENSION_V1_1, OCDS_PROCESS_TITLE_EXTENSION_V1_1, + + OCDS_ADDITIONAl_CONTACT_POINTS_EXTENSION, OCDS_BUDGET_BREAKDOWN_EXTENSION, + OCDS_BUDGET_PROJECTS_EXTENSION, OCDS_CHARGES_EXTENSION, OCDS_CONTRACT_SUPPLIERS_EXTENSION, + OCDS_DOCUMENTATION_EXTENSION, OCDS_EXTENDS_CONTRACTID_EXTENSION}; } public static final String EXTENSIONS_PREFIX = "/schema/extensions/"; diff --git a/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/extension.json new file mode 100644 index 000000000..ac2cf329a --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Additional Contact Points", + "description": "For including extra contact points on each organization. Particularly used when each contact point deals only in a particular language or group of languages.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/release-schema.json new file mode 100644 index 000000000..ae513004b --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_additionalContactPoints_extension/release-schema.json @@ -0,0 +1,36 @@ +{ + "definitions": { + "Organization": { + "properties": { + "additionalContactPoints": { + "title": "Additional Contact Points", + "description": "An array of additional contact points that may be consulted for information. Additional contact points should each list the languages they operate in their name, and as structured data in the availableLanguage field. ", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/ContactPoint" + } + } + } + }, + "ContactPoint": { + "properties": { + "availableLanguage": { + "title": "Available Language(s)", + "type": [ + "array", + "null" + ], + "description": "An array of language codes (e.g. en, es, uk)", + "items": { + "type": "string", + "name": "Language code", + "description": "ISO language code" + } + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_bid_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_bid_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_bid_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_bid_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_bid_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/extension.json new file mode 100644 index 000000000..b3074c46c --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/extension.json @@ -0,0 +1,5 @@ +{"name":"Budget Breakdown", +"description":"Providing a breakdown of budgets by year and source", +"compatibility": ">=1.0.0", +"dependencies": ["https://raw.githubusercontent.com/open-contracting/ocds_extension_parties/master/"] +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/release-schema.json new file mode 100644 index 000000000..ddc1f97d2 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/release-schema.json @@ -0,0 +1,65 @@ +{ + "definitions": { + "BudgetBreakdown": { + "type": "object", + "title": "Detailed budget breakdown", + "description": "This section allows a detailed budget breakdown to be expressed, covering multiple budget sources and multiple periods", + "properties": { + "id": { + "description": "An identifier for this particular budget entry.", + "mergeStrategy": "ocdsVersion", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A short free text description of this budget entry.", + "mergeStrategy": "ocdsVersion", + "type": [ + "string", + "null" + ] + }, + "amount": { + "description": "The value of the budget line item.", + "$ref": "#/definitions/Value" + }, + "uri": { + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable information about this budget entry.", + "mergeStrategy": "ocdsVersion", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "period": { + "$ref": "#/definitions/Period", + "title": "Budget period", + "description": "The period covered by this budget entry." + }, + "sourceParty": { + "$ref": "#/definitions/OrganizationReference", + "title": "Source party", + "description": "The organization or other party related to this budget entry. If the budget amount is positive, this indicates a flow of resources from the party to the contracting process. If the budget amount is negative, it indicates a payment from the contracting process to this party." + } + } + }, + "Budget": { + "properties": { + "budgetBreakdown": { + "type": "array", + "title": "Budget breakdown", + "description": "A detailed breakdown of the budget by period and/or participating funders.", + "items": { + "$ref": "#/definitions/BudgetBreakdown" + } + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/versioned-release-validation-schema.json b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/versioned-release-validation-schema.json new file mode 100644 index 000000000..b8e0be948 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_budget_breakdown_extension/versioned-release-validation-schema.json @@ -0,0 +1,105 @@ +{ + "definitions": { + "BudgetBreakdown": { + "type": "object", + "properties": { + "id": { + "type": "array", + "items": { + "properties": { + "releaseDate": { + "format": "date-time", + "type": "string" + }, + "releaseID": { + "type": "string" + }, + "value": { + "type": [ + "string", + "integer", + "null" + ] + }, + "releaseTag": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "description": { + "$ref": "#/definitions/StringNullVersioned" + }, + "amount": { + "$ref": "#/definitions/Value" + }, + "uri": { + "$ref": "#/definitions/StringNullUriVersioned" + }, + "period": { + "$ref": "#/definitions/Period" + }, + "sourceParty": { + "$ref": "#/definitions/OrganizationReference" + } + } + }, + "BudgetBreakdownUnversioned": { + "type": "object", + "properties": { + "id": { + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "amount": { + "$ref": "#/definitions/ValueUnversioned" + }, + "uri": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "period": { + "$ref": "#/definitions/PeriodUnversioned" + }, + "sourceParty": { + "$ref": "#/definitions/OrganizationReferenceUnversioned" + } + } + }, + "Budget": { + "properties": { + "budgetBreakdown": { + "type": "array", + "items": { + "$ref": "#/definitions/BudgetBreakdown" + } + } + } + }, + "BudgetUnversioned": { + "properties": { + "budgetBreakdown": { + "type": "array", + "items": { + "$ref": "#/definitions/BudgetBreakdownUnversioned" + } + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/extension.json new file mode 100644 index 000000000..dce1eb4fc --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/extension.json @@ -0,0 +1,9 @@ +{ + "name": "Budget and Project Updates", + "description": "This extension introduces a projects block in 'planning' which can be used to give total project value, and provide project-level information, alongside existing fields in the planning/budget section for details of the budget allocated to this specific contracting process.", + "compatibility": ">=1.1.0", + "documentationUrl": { + "en": "https://github.com/open-contracting/ocds_budget_projects_extension/" + }, + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/release-schema.json new file mode 100644 index 000000000..2142be23b --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_budget_projects_extension/release-schema.json @@ -0,0 +1,96 @@ +{ + "definitions": { + "Planning": { + "properties": { + "project": { + "$ref": "#/definitions/Project" + } + } + }, + "Project": { + "type": "object", + "title": "Project Information", + "description": "The project section can be used to describe the relationship between this contracting process and a project or programme of work.", + "properties": { + "id": { + "description": "An externally provided identifier for the project. This might be drawn from a projects register, or may be based on the canonical version of a project name. Project IDs should be unique to a publisher. URIs can be used. ", + "type": [ + "string", + "null" + ] + }, + "title": { + "title": "Project Title", + "description": "The name of the project to which this contracting process relates. Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. ", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Project description", + "description": "A short free text description of the project.", + "type": [ + "string", + "null" + ] + }, + "totalValue": { + "description": "The total anticipated value of the project over it's lifetime. ", + "$ref": "#/definitions/Value" + }, + "uri": { + "title": "Linked project information", + "description": "A URI pointing to further information about this project.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Budget": { + "description": "This section contains basic information about the budget estimated for, or allocated to, this contracting process at the present time. Further documentation and data about how budgets have been allocated to a contracting process should be published outside of OCDS data, according to the best available standards. ", + "properties": { + "source": null, + "id": { + "description": "An identifier for the budget line item which provides funds for this contracting process. Wherever possible, this identifier should be possible to cross-reference against formal budget documents.", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A short free text description of the budget allocation for this contracting process. This may be used to provide human readable information on the budget category allocated to this contracting process, and/or, information about the nature and source of the allocation (e.g. conditional, confirmed; any official authorisations given to the budget allocation). " + }, + "amount": { + "description": "The total value of budget allocations for this contracting process. The budget breakdown extension can be used to provide data for multiple budget sources, or to split the budget by years or other periods. " + }, + "project": { + "description": "The name of the project that through which this contracting process is funded. Detailed information should be provided in the extended projectDetail section." + }, + "projectID": { + "description": "An external identifier for the project that this contracting process forms part of. Required for legacy compatibility with OCDS core." + }, + "uri": { + "description": "A URI pointing directly to records about the related budget for this contracting process. Where possible this URI should return machine and human-readable representations of the data." + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_charges_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_charges_extension/extension.json new file mode 100644 index 000000000..f5186c940 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_charges_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Charges", + "description": "The charges extension is used to record details of the **total** charges that are estimated or applied to users or government during the operation of a Public Private Partnership contract.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_charges_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_charges_extension/release-schema.json new file mode 100644 index 000000000..5fda7c55f --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_charges_extension/release-schema.json @@ -0,0 +1,65 @@ +{ + "definitions": { + "Implementation": { + "properties": { + "charges": { + "type":"array", + "title":"Charges", + "description":"Information on the revenue to be raised through charges, as set out in the contract or as subsequently revised during the life of the project.", + "items": { + "$ref": "#/definitions/Charge" + } + } + } + }, + "Charge": { + "type": "object", + "title": "Charge", + "description": "A charge to be incurred by users or government over the course of the contract. Charge information can be broken down by period. Information on the unit prices upon which total values are based can be provided in the tariffs section. ", + "properties": { + "id": { + "title": "Charge identifier", + "description": "A local identifier for this specific charge. This field is used to keep track of revisions of a charge across multiple OCDS releases.", + "type": "string" + }, + "title": { + "title": "Charge title", + "description": "A descriptive title for this charge.", + "type": [ + "string", + "null" + ] + }, + "paidBy":{ + "title":"Paid by", + "description":"Is this a user charge (paid by businesses or citizens using the facilities provided by the contract), or a charge paid by the government?", + "type":["string","null"], + "enum":["government","user"] + }, + "period": { + "title": "Period", + "description": "The period to which this charge applies.", + "$ref": "#/definitions/Period" + }, + "estimatedValue":{ + "title":"Estimated value", + "description":"What is the estimated total value to be raised from this charge during this period.", + "$ref": "#/definitions/Value" + }, + "actualValue":{ + "title":"Actual value", + "description":"In the implementation section, this field may be updated with the total revenue raised from this charge during this period.", + "$ref": "#/definitions/Value" + }, + "notes": { + "title": "Notes", + "description": "Any notes on this charge. This may include clarifying information.", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/extension.json new file mode 100644 index 000000000..7a737313a --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Contract suppliers", + "description": "To allow explicit declaration of suppliers within the contracts block. Used when a single award to multiple suppliers results in multiple contracts to a sub-set of those awarded suppliers.", + "compatibility": ">=1.1", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/release-schema.json new file mode 100644 index 000000000..e43e10512 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_contract_suppliers_extension/release-schema.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "Contract": { + "properties": { + "suppliers": { + "title": "Suppliers", + "description": "The suppliers explicitly named in this contract.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" + }, + "uniqueItems": true + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_documentation_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/extension.json new file mode 100644 index 000000000..e2873c1ac --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/extension.json @@ -0,0 +1 @@ +{"name":"Document Details","description":"This extension captures additional details about documentation, as identified in consultation for the OCDS PPP extension. "} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_documentation_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/release-schema.json new file mode 100644 index 000000000..5b120d19e --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/release-schema.json @@ -0,0 +1,52 @@ +{ + "definitions": { + "Document": { + "title": "Documentation", + "description": "Documentation can be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part an open contracting process. The documentType field can be used to indicate the nature of each document. The description can be used to provide a summary of the document contents where appropriate, and the URL fields used to link directly to downloadable copies of the document.", + "properties": { + "description":{ + "description":"A short summary of the document." + }, + "pageStart": { + "title":"Page start", + "description": "When the information referenced exists within a large document, indicate the first page on which it can be found. This should refer to the printed page number, not the page number reported by software applications. ", + "type": [ + "string", + "null" + ], + "mergeStrategy": "ocdsVersion" + }, + "pageEnd": { + "title":"Page end", + "description": "When the information referenced exists within a large document, indicate the last page on which it can be found. This should refer to the printed page number, not the page number reported by software applications. ", + "type": [ + "string", + "null" + ], + "mergeStrategy": "ocdsVersion" + }, + "accessDetails": { + "title":"Access details", + "description": "A description of any special arrangements required to access this document. This may include a requirement to register for document access, a fee to be paid, or the location where documents can be inspected. ", + "type": [ + "string", + "null" + ], + "mergeStrategy": "ocdsVersion" + }, + "author": { + "title":"Author", + "description": "The name of the author of these documents. Include the name of individuals and/or organisations responsible. For example, when a technical specification was written by a consultant, include the name and organisation of the consultant. ", + "type": [ + "string", + "null" + ], + "mergeStrategy": "ocdsVersion" + }, + "url": { + "description": "A direct link to the documentation. The server providing access to this document should be configured to correctly report the document mime type. Where the information for this documentType is found within a larger document, the page number or section should be included [using a fragment identifier](https://helpx.adobe.com/acrobat/kb/link-html-pdf-page-acrobat.html). E.g. #page=32." + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_documentation_extension/versioned-release-validation-schema.json b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/versioned-release-validation-schema.json new file mode 100644 index 000000000..8233b3544 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_documentation_extension/versioned-release-validation-schema.json @@ -0,0 +1,48 @@ +{ + "definitions": { + "Document": { + "properties": { + "pageStart": { + "$ref": "#/definitions/StringNullVersioned" + }, + "pageEnd": { + "$ref": "#/definitions/StringNullVersioned" + }, + "accessDetails": { + "$ref": "#/definitions/StringNullVersioned" + }, + "author": { + "$ref": "#/definitions/StringNullVersioned" + } + } + }, + "DocumentUnversioned": { + "properties": { + "pageStart": { + "type": [ + "string", + "null" + ] + }, + "pageEnd": { + "type": [ + "string", + "null" + ] + }, + "accessDetails": { + "type": [ + "string", + "null" + ] + }, + "author": { + "type": [ + "string", + "null" + ] + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_enquiry_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_enquiry_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_enquiry_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/extension.json new file mode 100644 index 000000000..efe31c7a6 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/extension.json @@ -0,0 +1,6 @@ +{ + "name": "Contract extensions as contracts: extendsContractID", + "description": "Under some procurement rules and processes, to extend the duration or value of a contract, or to make other substantial alterations, requires a new contract to be signed. This extension allows these relationships between two or more contracts in the same contracting process to be made explicit.", + "compatibility": ">=1.1.0", + "dependencies": [] +} diff --git a/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/release-schema.json new file mode 100644 index 000000000..4c99600ae --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_extendsContractID_extension/release-schema.json @@ -0,0 +1,15 @@ +{ + "definitions":{ + "Contract":{ + "properties":{ + "extendsContractID": { + "title": "Extends contract ID", + "description": "If this contract extends or amends a previously issued contract, then the contract.id value for the extended/amended contract can be provided here.", + "type": [ + "string" + ] + } + } + } + } +} diff --git a/validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_location_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_location_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_location_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_location_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_location_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_location_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_lots_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_lots_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_lots_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_lots_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_lots_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_lots_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_milestone_documents_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/extension.json new file mode 100644 index 000000000..51d9dc575 --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/extension.json @@ -0,0 +1,5 @@ +{ + "description": "In some cases, a single contracting process involves multiple buyers (for example, a framework that different organisations can buy from). In these cases, the multiple buyers extension can be used to specify a distinct buyer for each contract. ", + "name": "Multiple buyers - contract level", + "compatibility": ">=1.1.0" +} diff --git a/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/release-schema.json new file mode 100644 index 000000000..f6a4f8f7d --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/release-schema.json @@ -0,0 +1,13 @@ +{ + "definitions": { + "Contract": { + "properties": { + "buyer": { + "title": "Buyer", + "description": "The entity whose budget will be used to purchase the goods or services specified in this particular contract. When the contract buyer extension is used, a buyer must be specified here **unless** the buyer for this contract is the same as the overall buyer specified at the release level.", + "$ref": "#/definitions/OrganizationReference" + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/versioned-release-validation-schema.json b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/versioned-release-validation-schema.json new file mode 100644 index 000000000..f9ae0269b --- /dev/null +++ b/validator/src/main/resources/schema/extensions/ocds_multiple_buyers_extension/versioned-release-validation-schema.json @@ -0,0 +1,18 @@ +{ + "definitions": { + "Contract": { + "properties": { + "buyer": { + "$ref": "#/definitions/OrganizationReference" + } + } + }, + "ContractUnversioned": { + "properties": { + "buyer": { + "$ref": "#/definitions/OrganizationReferenceUnversioned" + } + } + } + } +} \ No newline at end of file diff --git a/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_participationFee_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_participationFee_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_participationFee_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_participationFee_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_participationFee_extension/v1.1/release-schema.json diff --git a/validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/v1.1/extension.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_process_title_extension/extension.json rename to validator/src/main/resources/schema/extensions/ocds_process_title_extension/v1.1/extension.json diff --git a/validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json b/validator/src/main/resources/schema/extensions/ocds_process_title_extension/v1.1/release-schema.json similarity index 100% rename from validator/src/main/resources/schema/extensions/ocds_process_title_extension/release-schema.json rename to validator/src/main/resources/schema/extensions/ocds_process_title_extension/v1.1/release-schema.json From 60e6a6bf22175bf4e3b2f90b2a009c38fa940f96 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 1 Aug 2017 14:15:17 +0300 Subject: [PATCH 077/702] OCE-331 Linting --- .../tables/frequent-tenderers.jsx | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/ui/oce/visualizations/tables/frequent-tenderers.jsx b/ui/oce/visualizations/tables/frequent-tenderers.jsx index 5c79ac8a2..671092b14 100644 --- a/ui/oce/visualizations/tables/frequent-tenderers.jsx +++ b/ui/oce/visualizations/tables/frequent-tenderers.jsx @@ -1,88 +1,89 @@ import { List } from 'immutable'; -import Table from "./index"; -import orgNamesFetching from "../../orgnames-fetching"; -import {pluckImm, fetchJson} from "../../tools"; +import Table from './index'; +import orgNamesFetching from '../../orgnames-fetching'; +import { fetchJson } from '../../tools'; import translatable from '../../translatable'; -class WinCount extends translatable(React.Component){ - constructor(...args){ +// eslint-disable-next-line no-undef +class WinCount extends translatable(React.Component) { + constructor(...args) { super(...args); this.state = { - cnt: this.t("general:loading") - } + cnt: this.t('general:loading'), + }; } - componentDidMount(){ - const {id} = this.props; + componentDidMount() { + const { id } = this.props; fetchJson(`/api/activeAwardsCount?supplierId=${id}`) .then(response => this.setState({ - cnt: response[0].cnt - })) + cnt: response[0].cnt, + })); } - render(){ - const {id} = this.props; - const {cnt} = this.state; - return {cnt} + render() { + const { cnt } = this.state; + return {cnt}; } } -class FrequentTenderers extends orgNamesFetching(Table){ - constructor(...args){ +const maybeSlice = (flag, list) => (flag ? list.slice(0, 10) : list); + +class FrequentTenderers extends orgNamesFetching(Table) { + constructor(...args) { super(...args); this.state = this.state || {}; this.state.showAll = false; } - row(entry, index){ - const {translations} = this.props; + row(entry, index) { + const { translations } = this.props; const id1 = entry.get('tendererId1'); const id2 = entry.get('tendererId2'); - return + return ( {this.getOrgName(id1)} {this.getOrgName(id2)} {entry.get('pairCount')} - - - - } - - maybeSlice(flag, list){ - return flag ? list.slice(0, 10) : list; + + + ); } - getOrgsWithoutNamesIds(){ - const {data} = this.props; - if(!data) return []; + getOrgsWithoutNamesIds() { + const { data } = this.props; + if (!data) return []; return data.map(datum => List([datum.get('tendererId1'), datum.get('tendererId2')])) .flatten() .filter(id => !this.state.orgNames[id]).toJS(); } - render(){ - if(!this.props.data) return null; - const {showAll} = this.state; - return + render() { + if (!this.props.data) return null; + const { showAll } = this.state; + return (
- - - - - - - + + + + + + + - {this.maybeSlice(!showAll, this.props.data).map(this.row.bind(this))} - {!showAll && this.props.data.count() > 10 && - - } + {maybeSlice(!showAll, this.props.data).map((entry, index) => this.row(entry, index))} + {!showAll && this.props.data.count() > 10 && + + } -
{this.t('tables:frequentTenderers:supplier')} #1{this.t('tables:frequentTenderers:supplier')} #2{this.t('tables:frequentTenderers:nrITB')}{this.t('tables:frequentTenderers:supplier1wins')}{this.t('tables:frequentTenderers:supplier2wins')}
{this.t('tables:frequentTenderers:supplier')} #1{this.t('tables:frequentTenderers:supplier')} #2{this.t('tables:frequentTenderers:nrITB')}{this.t('tables:frequentTenderers:supplier1wins')}{this.t('tables:frequentTenderers:supplier2wins')}
- -
+ +
+ ); } } From 77ed13de660774fab88a0c2f477588a391092fc1 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 1 Aug 2017 15:08:05 +0300 Subject: [PATCH 078/702] OCE-331 Refactored supplier win count fetching --- .../tables/frequent-tenderers.jsx | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/ui/oce/visualizations/tables/frequent-tenderers.jsx b/ui/oce/visualizations/tables/frequent-tenderers.jsx index 671092b14..859d4f18e 100644 --- a/ui/oce/visualizations/tables/frequent-tenderers.jsx +++ b/ui/oce/visualizations/tables/frequent-tenderers.jsx @@ -1,31 +1,8 @@ import { List } from 'immutable'; +import URI from 'urijs'; import Table from './index'; import orgNamesFetching from '../../orgnames-fetching'; -import { fetchJson } from '../../tools'; -import translatable from '../../translatable'; - -// eslint-disable-next-line no-undef -class WinCount extends translatable(React.Component) { - constructor(...args) { - super(...args); - this.state = { - cnt: this.t('general:loading'), - }; - } - - componentDidMount() { - const { id } = this.props; - fetchJson(`/api/activeAwardsCount?supplierId=${id}`) - .then(response => this.setState({ - cnt: response[0].cnt, - })); - } - - render() { - const { cnt } = this.state; - return {cnt}; - } -} +import { send, callFunc } from '../../tools'; const maybeSlice = (flag, list) => (flag ? list.slice(0, 10) : list); @@ -34,18 +11,19 @@ class FrequentTenderers extends orgNamesFetching(Table) { super(...args); this.state = this.state || {}; this.state.showAll = false; + this.state.winCounts = {}; } row(entry, index) { - const { translations } = this.props; + const { winCounts } = this.state; const id1 = entry.get('tendererId1'); const id2 = entry.get('tendererId2'); return ( {this.getOrgName(id1)} {this.getOrgName(id2)} {entry.get('pairCount')} - - + {winCounts[id1]} + {winCounts[id2]} ); } @@ -57,6 +35,40 @@ class FrequentTenderers extends orgNamesFetching(Table) { .filter(id => !this.state.orgNames[id]).toJS(); } + getSuppliersWithoutWinCountIds() { + const { data } = this.props; + if (!data) return []; + return data.map(datum => List([datum.get('tendererId1'), datum.get('tendererId2')])) + .flatten() + .filter(id => !this.state.winCounts[id]).toJS(); + } + + maybeFetchWinCounts() { + const idsWithoutWinCounts = this.getSuppliersWithoutWinCountIds(); + if (!idsWithoutWinCounts.length) return; + send(new URI('/api/activeAwardsCount').addSearch('supplierId', idsWithoutWinCounts)) + .then(callFunc('json')) + .then((counts) => { + const winCounts = {}; + counts.forEach(({ supplierId, cnt }) => { + winCounts[supplierId] = cnt; + }); + this.setState({ winCounts }); + }); + } + + componentDidMount() { + super.componentDidMount(); + this.maybeFetchWinCounts(); + } + + componentDidUpdate(prevProps, ...args) { + super.componentDidUpdate(prevProps, ...args); + if (prevProps.data !== this.props.data) { + this.maybeFetchWinCounts(); + } + } + render() { if (!this.props.data) return null; const { showAll } = this.state; From 5a885d85f18b0ff517c8531494e8e9c162157a53 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 1 Aug 2017 15:10:28 +0300 Subject: [PATCH 079/702] OCE-331 Added 'immutable' to the dll --- ui/webpack.dll.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/webpack.dll.config.js b/ui/webpack.dll.config.js index dc29288b1..259876d38 100644 --- a/ui/webpack.dll.config.js +++ b/ui/webpack.dll.config.js @@ -11,7 +11,8 @@ module.exports = { 'react-leaflet', 'plotly.js/lib/core', 'plotly.js/lib/bar', - 'plotly.js/lib/pie.js' + 'plotly.js/lib/pie.js', + 'immutable' ] }, From f34657a9ded33a32808f87ad3c5bcd5e74eff4f5 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 1 Aug 2017 16:23:15 +0300 Subject: [PATCH 080/702] OCE-363 Linting --- ui/oce/corruption-risk/custom-popup-chart.jsx | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/ui/oce/corruption-risk/custom-popup-chart.jsx b/ui/oce/corruption-risk/custom-popup-chart.jsx index 6e0dcc685..33a79dcaa 100644 --- a/ui/oce/corruption-risk/custom-popup-chart.jsx +++ b/ui/oce/corruption-risk/custom-popup-chart.jsx @@ -1,46 +1,47 @@ -import Chart from "../visualizations/charts/frontend-date-filterable"; -import ReactIgnore from "../react-ignore.jsx"; -import cn from "classnames"; -import {POPUP_HEIGHT, POPUP_WIDTH} from "./constants"; +import cn from 'classnames'; +import Chart from '../visualizations/charts/frontend-date-filterable'; +import ReactIgnore from '../react-ignore.jsx'; +import { POPUP_HEIGHT, POPUP_WIDTH } from './constants'; -class CustomPopupChart extends Chart{ - constructor(...args){ +class CustomPopupChart extends Chart { + constructor(...args) { super(...args); this.state = { popup: { show: false, left: 0, - top: 0 - } - } + top: 0, + }, + }; } - componentDidMount(){ + componentDidMount() { super.componentDidMount(); - const {chartContainer} = this.refs; + const { chartContainer } = this; chartContainer.on('plotly_hover', this.showPopup.bind(this)); - chartContainer.on('plotly_unhover', data => this.hidePopup()); + chartContainer.on('plotly_unhover', () => this.hidePopup()); } - showPopup(data){ + showPopup(data) { const point = data.points[0]; const year = point.x; const traceName = point.data.name; const POPUP_ARROW_SIZE = 8; - const {xaxis, yaxis} = point; + const { xaxis, yaxis } = point; const markerLeft = xaxis.l2p(point.pointNumber) + xaxis._offset; const markerTop = yaxis.l2p(point.y) + yaxis._offset; - const {left: parentLeft} = this.refs.chartContainer.getBoundingClientRect(); + const { left: parentLeft } = this.chartContainer.getBoundingClientRect(); const toTheLeft = (markerLeft + parentLeft + POPUP_WIDTH) >= window.innerWidth; - let top, left; + let top; + let left; - if(toTheLeft){ - top = markerTop - POPUP_HEIGHT / 2; - left = markerLeft - POPUP_WIDTH - POPUP_ARROW_SIZE * 1.5; + if (toTheLeft) { + top = markerTop - (POPUP_HEIGHT / 2); + left = markerLeft - POPUP_WIDTH - (POPUP_ARROW_SIZE * 1.5); } else { - top = markerTop - POPUP_HEIGHT - POPUP_ARROW_SIZE * 1.5; - left = markerLeft - POPUP_WIDTH / 2; + top = markerTop - POPUP_HEIGHT - (POPUP_ARROW_SIZE * 1.5); + left = markerLeft - (POPUP_WIDTH / 2); } this.setState({ @@ -50,37 +51,37 @@ class CustomPopupChart extends Chart{ top, left, year, - traceName - } + traceName, + }, }); } - hidePopup(){ + hidePopup() { this.setState({ popup: { - show: false - } + show: false, + }, }); } - render(){ - const {loading, popup} = this.state; - let hasNoData = !loading && this.hasNoData(); + render() { + const { loading, popup } = this.state; + const hasNoData = !loading && this.hasNoData(); return ( -
- {hasNoData &&
{this.t('charts:general:noData')}
} - {loading &&
- Loading...
- -
} +
+ {hasNoData &&
{this.t('charts:general:noData')}
} + {loading &&
+ Loading...
+ +
} - {popup.show && this.getPopup()} + {popup.show && this.getPopup()} - -
- + +
{ this.chartContainer = c; }} /> +
- ) + ); } } From 69aeb2cb6c840cc0bb78761b72ae2c56cc9a9fb3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 2 Aug 2017 12:55:52 +0300 Subject: [PATCH 081/702] OCE-354 Smaller font size --- ui/oce/visualizations/map/style.less | 4 +- .../map/tender-locations/location.jsx | 52 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index 5207e6bfa..4bc4ad080 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -8,6 +8,7 @@ } .tender-locations-popup{ + font-size: 9pt; header{ background: #ca3d3f; margin-top: -14px; @@ -17,7 +18,7 @@ border-top-left-radius: @borderRadius; padding: 12px; color: white; - font-size: 1.5em; + font-size: 12pt; font-weight: bolder; height: @headerHeight; } @@ -29,7 +30,6 @@ .tabs-bar{ background: #272727; - font-size: 1.2em; margin-left: -5px; height: @windowHeight - @headerHeight + 14px;//magic number, leaflet's borders n stuff border-bottom-left-radius: @borderRadius; diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index bf848b3f2..ed4b8b12c 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -116,8 +116,9 @@ export class ChartTab extends Tab { months, t: translationKey => this.t(translationKey), }); - return (
- + -
-
- Export -
+ /> +
+
+ Export +
-
ReactDOM.findDOMNode(this).querySelector('.modebar-btn:first-child').click()} - role="button" - tabIndex={0} - > - Screenshot +
ReactDOM.findDOMNode(this).querySelector('.modebar-btn:first-child').click()} + role="button" + tabIndex={0} + > + Screenshot +
-
); + ); } } From 1c705b2159dae7dfe9f1d162d8affdf3ae739244 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 2 Aug 2017 14:17:24 +0300 Subject: [PATCH 082/702] OCE-354 Moved the chart legend to the left so that it sits on top of tabs --- ui/oce/visualizations/map/style.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index 4bc4ad080..440a02acf 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -9,6 +9,12 @@ .tender-locations-popup{ font-size: 9pt; + .map-chart{ + margin-left: -37px; + .main-svg{ + background: transparent!important; + } + } header{ background: #ca3d3f; margin-top: -14px; From 04310610d56d68b9d7d34767d553409f3d0c7bd2 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 2 Aug 2017 17:38:39 +0300 Subject: [PATCH 083/702] OCE-355 complete release package validation along with remote extensions loading and applying, works with both remote and enforced extensions --- .../validator/OcdsValidatorConstants.java | 4 + .../ocds/validator/OcdsValidatorService.java | 129 ++++- .../release-package-schema-1.0.0.json | 6 - .../release-package-schema-1.0.1.json | 6 - .../release-package-schema-1.0.2.json | 6 - .../release-package-schema-1.1.0.json | 10 - .../test/OcdsValidatorTestRelease.java | 33 +- .../src/test/resources/release-package.json | 521 ++++++++++++++++++ 8 files changed, 667 insertions(+), 48 deletions(-) create mode 100644 validator/src/test/resources/release-package.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index 04be72c8f..c512c1273 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -65,6 +65,10 @@ public static final class Extensions { } public static final String EXTENSIONS_PREFIX = "/schema/extensions/"; + public static final String REMOTE_EXTENSION_META_POSTFIX = "extension.json"; + + public static final String EXTENSIONS_PROPERTY = "extensions"; + public static final String RELEASES_PROPERTY = "releases"; public static final SortedSet EXTENSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( Extensions.ALL))); diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index e6159100f..06677667e 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -13,11 +13,10 @@ import com.github.fge.jsonschema.main.JsonSchemaFactory; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +41,6 @@ public class OcdsValidatorService { private Map extensionReleaseJson = new ConcurrentHashMap<>(); - @Autowired private ObjectMapper jacksonObjectMapper; @@ -60,26 +58,25 @@ private JsonNode getUnmodifiedSchemaNode(OcdsValidatorRequest request) { private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest request) { if (ObjectUtils.isEmpty(request.getExtensions())) { - logger.debug("No schema extensions are requested."); + logger.debug("No explicit schema extensions were requested."); return schemaNode; } - List unrecognizedExtensions = - request.getExtensions().stream().filter(e -> !OcdsValidatorConstants.EXTENSIONS.contains(e)) - .collect(Collectors.toList()); - - if (!unrecognizedExtensions.isEmpty()) { - throw new RuntimeException("Unknown extensions by name: " + unrecognizedExtensions); - } - - //TODO: check extension meta and see if they apply for the current standard +// List unrecognizedExtensions = +// request.getExtensions().stream().filter(e -> !extensionMeta.containsKey(e)) +// .collect(Collectors.toList()); +// +// if (!unrecognizedExtensions.isEmpty()) { +// throw new RuntimeException("Unknown extensions by name: " + unrecognizedExtensions); +// } JsonNode schemaResult = schemaNode; for (String ext : request.getExtensions()) { try { logger.debug("Applying schema extension " + ext); - schemaResult = extensionReleaseJson.get(ext).apply(schemaResult); + getExtensionMeta(ext); //TODO: check extension meta and see if they apply for the current standard + schemaResult = getExtensionReleaseJson(ext).apply(schemaResult); } catch (JsonPatchException e) { throw new RuntimeException(e); } @@ -88,6 +85,41 @@ private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest reque return schemaResult; } + private JsonNode getExtensionMeta(String id) { + + //check if preloaded as extension + if (extensionMeta.containsKey(id)) { + return extensionMeta.get(id); + } + + //attempt load via URL + try { + JsonNode jsonNode = readExtensionMeta(new URL(id)); + extensionMeta.put(id, jsonNode); + return jsonNode; + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + private JsonMergePatch getExtensionReleaseJson(String id) { + //check if preloaded as extension + if (extensionReleaseJson.containsKey(id)) { + return extensionReleaseJson.get(id); + } + + //attempt load via URL + try { + String releaseUrl = id.replace(OcdsValidatorConstants.REMOTE_EXTENSION_META_POSTFIX, + OcdsValidatorConstants.EXTENSION_RELEASE_JSON); + JsonMergePatch patch = readExtensionReleaseJson(new URL(releaseUrl)); + extensionReleaseJson.put(id, patch); + return patch; + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + private JsonNode readExtensionMeta(String extensionName) { //reading meta try { @@ -100,6 +132,28 @@ private JsonNode readExtensionMeta(String extensionName) { } } + private JsonNode readExtensionMeta(URL url) { + try { + logger.debug("Reading extension metadata from URL " + url); + return JsonLoader.fromURL(url); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private JsonMergePatch readExtensionReleaseJson(URL url) { + //reading meta + try { + logger.debug("Reading extension JSON contents for extension " + url); + JsonNode jsonMergePatch = JsonLoader.fromURL(url); + JsonMergePatch patch = JsonMergePatch.fromJson(jsonMergePatch); + return patch; + } catch (IOException | JsonPatchException e) { + throw new RuntimeException(e); + } + } + + private JsonMergePatch readExtensionReleaseJson(String extensionName) { //reading meta try { @@ -146,7 +200,7 @@ private void initSchemaNamePrefix() { } private void initExtensions() { - logger.debug("Initializing schema extensions"); + logger.debug("Initializing predefined schema extensions"); OcdsValidatorConstants.EXTENSIONS.forEach(e -> { logger.debug("Initializing schema extension " + e); extensionMeta.put(e, readExtensionMeta(e)); @@ -170,6 +224,10 @@ public ProcessingReport validate(OcdsValidatorApiRequest request) { return validateRelease(nodeRequest); } + if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE_PACKAGE)) { + return validateReleasePackage(nodeRequest); + } + return null; } @@ -208,6 +266,47 @@ private ProcessingReport validateRelease(OcdsValidatorNodeRequest nodeRequest) { } } + /** + * Validates a release package + * + * @param nodeRequest + * @return + */ + private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeRequest) { + JsonSchema schema = getSchema(nodeRequest); + try { + ProcessingReport releasePackageReport = schema.validate(nodeRequest.getNode()); + if (!releasePackageReport.isSuccess()) { + return releasePackageReport; + } + + //get release package extensions + if (nodeRequest.getNode().hasNonNull(OcdsValidatorConstants.EXTENSIONS_PROPERTY)) { + for (JsonNode extension : nodeRequest.getNode().get(OcdsValidatorConstants.EXTENSIONS_PROPERTY)) { + nodeRequest.getExtensions().add(extension.asText()); + } + } + + if (nodeRequest.getNode().hasNonNull(OcdsValidatorConstants.RELEASES_PROPERTY)) { + for (JsonNode release : nodeRequest.getNode().get(OcdsValidatorConstants.RELEASES_PROPERTY)) { + OcdsValidatorNodeRequest releaseValidationRequest + = new OcdsValidatorNodeRequest(nodeRequest, release); + releaseValidationRequest.setSchemaType(OcdsValidatorConstants.Schemas.RELEASE); + releasePackageReport.mergeWith(validateRelease(releaseValidationRequest)); + } + } else { + throw new RuntimeException("No releases were found during release package validation!"); + } + + return releasePackageReport; + + } catch (ProcessingException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private OcdsValidatorNodeRequest convertApiRequestToNodeRequest(OcdsValidatorApiRequest request) { JsonNode node = null; if (!StringUtils.isEmpty(request.getJson())) { diff --git a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json index 63a1fdc69..26be2d782 100644 --- a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json +++ b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.0.json @@ -17,12 +17,6 @@ "type": "string", "format": "date-time" }, - "releases": { - "type": "array", - "minItems": 1, - "items": { "$ref": "http://ocds.open-contracting.org/standard/r/1__0__0/release-schema.json" }, - "uniqueItems": true - }, "publisher": { "description": "Information to uniquely identify the publisher of this package.", "type": "object", diff --git a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json index 9a6be76b1..93bd682ac 100644 --- a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json +++ b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.1.json @@ -17,12 +17,6 @@ "type": "string", "format": "date-time" }, - "releases": { - "type": "array", - "minItems": 1, - "items": { "$ref": "http://standard.open-contracting.org/schema/1__0__1/release-schema.json" }, - "uniqueItems": true - }, "publisher": { "description": "Information to uniquely identify the publisher of this package.", "type": "object", diff --git a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json index 783f9bb7b..d93d1a055 100644 --- a/validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json +++ b/validator/src/main/resources/schema/release-package/release-package-schema-1.0.2.json @@ -17,12 +17,6 @@ "type": "string", "format": "date-time" }, - "releases": { - "type": "array", - "minItems": 1, - "items": { "$ref": "http://standard.open-contracting.org/schema/1__0__2/release-schema.json" }, - "uniqueItems": true - }, "publisher": { "description": "Information to uniquely identify the publisher of this package.", "type": "object", diff --git a/validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json index 120535476..aba67d170 100644 --- a/validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json +++ b/validator/src/main/resources/schema/release-package/release-package-schema-1.1.0.json @@ -1,5 +1,4 @@ { - "id": "http://standard.open-contracting.org/schema/1__1__0/release-package-schema.json", "$schema": "http://json-schema.org/draft-04/schema#", "title": "Schema for an Open Contracting Release Package", "description": "Note that all releases within a release package must have a unique releaseID within this release package.", @@ -39,15 +38,6 @@ "type": "string", "format": "date-time" }, - "releases": { - "title": "Releases", - "type": "array", - "minItems": 1, - "items": { - "$ref": "http://standard.open-contracting.org/schema/1__1__0/release-schema.json" - }, - "uniqueItems": true - }, "publisher": { "title": "Publisher", "description": "Information to uniquely identify the publisher of this package.", diff --git a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java index 06c67d145..d5cacd98f 100644 --- a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java +++ b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.util.TreeSet; import org.devgateway.ocds.validator.OcdsValidatorApiRequest; import org.devgateway.ocds.validator.OcdsValidatorConstants; import org.devgateway.ocds.validator.OcdsValidatorService; @@ -22,7 +23,7 @@ */ @RunWith(SpringRunner.class) @ActiveProfiles("integration") -@SpringBootTest(classes = { ValidatorApplication.class }) +@SpringBootTest(classes = {ValidatorApplication.class}) //@TestPropertySource("classpath:test.properties") public class OcdsValidatorTestRelease { @@ -32,11 +33,23 @@ public class OcdsValidatorTestRelease { @Test public void testReleaseValidation() { - OcdsValidatorApiRequest request=new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, + OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, OcdsValidatorConstants.EXTENSIONS, OcdsValidatorConstants.Schemas.RELEASE); - InputStream resourceAsStream = this.getClass().getResourceAsStream("/full-release.json"); + request.setJson(getJsonFromResource("/full-release.json")); + + ProcessingReport processingReport = ocdsValidatorService.validate(request); + if (!processingReport.isSuccess()) { + System.out.println(processingReport); + } + + Assert.assertTrue(processingReport.isSuccess()); + + } + + private String getJsonFromResource(String resourceName) { + InputStream resourceAsStream = this.getClass().getResourceAsStream(resourceName); try { - request.setJson(StreamUtils.copyToString(resourceAsStream, Charset.defaultCharset())); + return StreamUtils.copyToString(resourceAsStream, Charset.defaultCharset()); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); @@ -49,8 +62,18 @@ public void testReleaseValidation() { } } + } + + @Test + public void testReleasePackageValidation() { + + OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, + new TreeSet<>(OcdsValidatorConstants.EXTENSIONS), OcdsValidatorConstants.Schemas.RELEASE_PACKAGE); + + request.setJson(getJsonFromResource("/release-package.json")); + ProcessingReport processingReport = ocdsValidatorService.validate(request); - if(!processingReport.isSuccess()) { + if (!processingReport.isSuccess()) { System.out.println(processingReport); } diff --git a/validator/src/test/resources/release-package.json b/validator/src/test/resources/release-package.json new file mode 100644 index 000000000..89e0c33e8 --- /dev/null +++ b/validator/src/test/resources/release-package.json @@ -0,0 +1,521 @@ +{ + "uri": "http://standard.open-contracting.org/examples/releases/ocds-213czf-000-00001-03-tenderAmendment.json", + "publishedDate": "2010-03-20T09:45:00Z", + "publisher": { + "scheme": "GB-COH", + "uid": "09506232", + "name": "Open Data Services Co-operative Limited", + "uri": "http://standard.open-contracting.org/examples/" + }, + "license": "http://opendatacommons.org/licenses/pddl/1.0/", + "publicationPolicy": "https://github.com/open-contracting/sample-data/", + "version": "1.1", + "extensions": ["https://raw.githubusercontent.com/open-contracting/ocds_bid_extension/v1.1/extension.json"], + "releases": [ + { + "ocid": "ocds-full-001", + "id": "ocds-full-1234", + "date": "2016-05-10T09:30:00Z", + "tag": ["award"], + "initiationType": "tender", + "planning": { + "budget": { + "source": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984", + "id": "6801ad388f3a38b7740dde20108c58b35984ee91", + "description": "Budget allocation for highway maintenance, aligned with 2015 strategic plan. ", + "amount": { + "amount": 6700000.00, + "currency": "GBP" + }, + "project": "Central Junction Cycle Scheme", + "projectID": "SP001", + "uri": "https://openspending.org/uk-barnet-budget/entries/6801ad388f3a38b7740dde20108c58b35984ee91" + }, + "rationale": "The 2009 Strategic Plan identifies a need for an improved cycle route in the centre of town.", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + }, + { + "id": "0002", + "documentType": "needsAssessment", + "title": "Cycle provision - Needs Assessment", + "description": "Needs assessment for provision for cyclists in the centre of town.", + "url": "http://example.com/opencontracting/documents/ocds-213czf-000-00001/needsAssessment.pdf", + "datePublished": "2009-01-15T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + }, + "tender": { + "id": "ocds-full-1234-01-tender", + "title": "Planned cycle lane improvements", + "description": "Tenders solicited for work to build new cycle lanes in the centre of town.", + "status": "active", + "items": [ + { + "id": "0001", + "description": "desc 1", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 120000, + "currency": "GBP" + } + } + } + ], + "minValue": { + "amount": 600000, + "currency": "GBP" + }, + "value": { + "amount": 1100000, + "currency": "GBP" + }, + "procurementMethod": "open", + "procurementMethodRationale": "An open competitive tender is required by EU Rules", + "awardCriteria": "bestProposal", + "awardCriteriaDetails": "The best proposal, subject to value for money requirements, will be accepted.", + "submissionMethod": [ "electronicSubmission" ], + "submissionMethodDetails": "Submit through the online portal at http://example.com/submissions/ocds-213czf-000-00001-01/", + "tenderPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2011-04-01T18:00:00Z" + + }, + "enquiryPeriod": { + "startDate": "2010-03-01T09:00:00Z", + "endDate": "2010-03-14T17:30:00Z" + }, + "hasEnquiries": false, + "awardPeriod": { + "startDate": "2010-06-01T00:00:00Z", + "endDate": "2011-08-01T23:59:59Z" + }, + "eligibilityCriteria": "criteria-1", + "numberOfTenderers": 10, + "procuringEntity": { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "London Borough of Barnet", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + }, + "tenderers": [ + { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [ + { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + } + ], + "name": "Organization name", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + } + ], + "documents": [ + { + "id": "0005", + "documentType": "notice", + "title": "Tender Notice", + "description": "Official tender notice.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-01.html", + "datePublished": "2010-03-01T09:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "text/html", + "language": "en" + } + ], + "milestones": [ + { + "id": "1234", + "title": "milestones 1", + "description": "description 1", + "dueDate":"2010-10-01T00:00:00Z", + "dateModified": "2011-10-01T00:00:00Z", + "status": "met", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + ], + "amendment": { + "date": "2011-08-01T23:59:59Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale-1" + } + }, + "buyer": { + "id": "GB-LAC-E09000003", + "identifier": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "London Borough of Barnet", + "address": { + "streetAddress": "4, North London Business Park, Oakleigh Rd S", + "locality": "London", + "region": "London", + "postalCode": "N11 1NP", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Procurement Team", + "email": "procurement-team@example.com", + "telephone": "01234 345 346", + "faxNumber": "01234 345 345", + "url": "http://example.com/contact/" + } + }, + "awards": [ + { + "id": "ocds-213czf-000-00001-award-01", + "title": "Award of contract to build new cycle lanes in the centre of town.", + "description": "AnyCorp Ltd has been awarded the contract to build new cycle lanes in the centre of town.", + "status": "pending", + "date": "2010-05-10T09:30:00Z", + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "suppliers": [ + { + "id": "GB-COH-1234567844", + "identifier": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "additionalIdentifiers": [{ + "scheme": "additional-GB-LAC", + "id": "additional-E09000003", + "legalName": "additional-London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/additional" + }], + "name": "AnyCorp Cycle Provision", + "address": { + "streetAddress": "100 Anytown Lane", + "locality": "Anytown", + "region": "AnyCounty", + "postalCode": "AN1 1TN", + "countryName": "United Kingdom" + }, + "contactPoint": { + "name": "Contracts Team", + "email": "contracts@anycorp.example", + "telephone": "12345 456 343", + "faxNumber": "12345 456 343", + "url": "http://example.com/contact/" + } + } + ], + "items": [ + { + "id": "0001", + "description": "desc", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 137000, + "currency": "GBP" + } + } + } + ], + "contractPeriod": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-08-01T23:59:00Z" + }, + "documents": [ + { + "id": "0007", + "documentType": "notice", + "title": "Award notice", + "description": "Award of contract to build new cycle lanes in the centre of town to AnyCorp Ltd.", + "url": "http://example.com/tender-notices/ocds-213czf-000-00001-04.html", + "datePublished": "2010-05-10T09:30:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "text/html", + "language": "en" + } + ], + "amendment": { + "date": "2010-05-10T09:30:00Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale..." + } + } + ], + "contracts": [ + { + "id": "ocds-213czf-000-00001-contract-01", + "awardID": "ocds-213czf-000-00001-award-01", + "title": "Contract to build new cycle lanes in the centre of town.", + "description": "A contract has been signed between the Council and AnyCorp Ltd for construction of new cycle lanes in the centre of town.", + "status": "active", + "period": { + "startDate": "2010-07-01T00:00:00Z", + "endDate": "2011-08-01T23:59:00Z" + }, + "value": { + "amount": 11000000, + "currency": "GBP" + }, + "items": [ + { + "id": "0001", + "description": "desc 2", + "classification": { + "scheme": "CPV", + "id": "45233130", + "description": "Construction work for highways", + "uri": "http://cpv.data.ac.uk/code-45233130" + }, + "additionalClassifications": [ + { + "scheme": "CPV", + "id": "45233162-2", + "description": "Cycle path construction work", + "uri": "http://cpv.data.ac.uk/code-45233162.html" + } + ], + "quantity": 8, + "unit": { + "name": "Miles", + "value": { + "amount": 137000, + "currency": "GBP" + } + } + } + ], + "dateSigned": "2015-06-10T14:23:12Z", + "documents": [ + { + "id": "0008", + "documentType": "contractSigned", + "title": "Signed Contract", + "description": "The Signed Contract for Cycle Path Construction", + "url": "http://example.com/contracts/ocds-213czf-000-00001", + "datePublished": "2015-06-10T16:43:12Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ], + "amendment": { + "date": "2010-01-05T00:00:00Z", + "changes": [ + { + "property": "prop", + "former_value": "1234" + } + ], + "rationale": "rationale 2" + }, + "implementation": { + "transactions": [ + { + "id": "ocds-213czf-000-00001-1", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-08-01T00:00:00Z", + "amount": { + "amount": 500000, + "currency": "GBP" + }, + "providerOrganization": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "receiverOrganization": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs1059620ywhgai" + }, + { + "id": "ocds-213czf-000-00001-2", + "source": "https://openspending.org/uk-barnet-spending/", + "date": "2010-10-01T00:00:00Z", + "amount": { + "amount": 100000, + "currency": "GBP" + }, + "providerOrganization": { + "scheme": "GB-LAC", + "id": "E09000003", + "legalName": "London Borough of Barnet", + "uri": "http://www.barnet.gov.uk/" + }, + "receiverOrganization": { + "scheme": "GB-COH", + "id": "1234567844", + "legalName": "AnyCorp Ltd", + "uri": "http://www.anycorp.example" + }, + "uri": "https://openspending.org/uk-barnet-spending/transaction/asd9235qaghvs105962as0012" + } + ], + "milestones": [ + { + "id": "1234", + "title": "milestones 1", + "description": "description 1", + "dueDate":"2010-10-01T00:00:00Z", + "dateModified": "2011-10-01T00:00:00Z", + "status": "met", + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + ], + "documents": [ + { + "id": "0001", + "documentType": "procurementPlan", + "title": "Area Wide Cycle Improvements - Procurement Plan", + "description": "The overall strategic framework for procurement to enhance cycle provision.", + "url": "http://example.com/opencontracting/documents/planning/highways/procurementPlan.pdf", + "datePublished": "2009-01-05T00:00:00Z", + "dateModified": "2010-01-05T00:00:00Z", + "format": "application/pdf", + "language": "en" + } + ] + } + } + ], + "language": "en" + } + + ] +} \ No newline at end of file From 9cda18e2b6a4db7a5c723c757a13dfe37647fb6d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 2 Aug 2017 18:00:04 +0300 Subject: [PATCH 084/702] OCE-354 Moved the chart toolbar to the window header --- ui/oce/visualizations/map/style.less | 4 +-- .../map/tender-locations/location.jsx | 35 +++++-------------- .../map/tender-locations/style.less | 5 --- 3 files changed, 10 insertions(+), 34 deletions(-) delete mode 100644 ui/oce/visualizations/map/tender-locations/style.less diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index 440a02acf..fb9e2e2f2 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -3,14 +3,14 @@ @borderRadius: 12px; .leaflet-popup-content{ - width: 750px!important; + width: 650px!important; height: 400px; } .tender-locations-popup{ font-size: 9pt; .map-chart{ - margin-left: -37px; + margin-left: -35px; .main-svg{ background: transparent!important; } diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index ed4b8b12c..0064ea4f9 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -8,8 +8,6 @@ import OverviewChart from '../../../visualizations/charts/overview'; import CostEffectiveness from '../../../visualizations/charts/cost-effectiveness'; import { cacheFn, download } from '../../../tools'; import ProcurementMethodChart from '../../../visualizations/charts/procurement-method'; -// eslint-disable-next-line no-unused-vars -import style from './style.less'; class LocationWrapper extends translatable(Component) { constructor(props) { @@ -30,6 +28,13 @@ class LocationWrapper extends translatable(Component) {
{data.name} + Export + Screenshot
@@ -40,7 +45,7 @@ class LocationWrapper extends translatable(Component) { onClick={() => this.setState({ currentTab: index })} role="button" tabIndex={0} - > + > {Tab.getName(t)}
))} @@ -132,30 +137,6 @@ export class ChartTab extends Tab { margin={this.constructor.getMargins()} legend="h" /> -
-
- Export -
- -
ReactDOM.findDOMNode(this).querySelector('.modebar-btn:first-child').click()} - role="button" - tabIndex={0} - > - Screenshot -
-
); } diff --git a/ui/oce/visualizations/map/tender-locations/style.less b/ui/oce/visualizations/map/tender-locations/style.less deleted file mode 100644 index e9aad9eb5..000000000 --- a/ui/oce/visualizations/map/tender-locations/style.less +++ /dev/null @@ -1,5 +0,0 @@ -.map-chart .chart-toolbar{ - position: absolute; - right: 0; - bottom: 102px; -} From 62aa07d45d12ed00b3b53225f7f5f4e43941fb4e Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 3 Aug 2017 14:25:43 +0300 Subject: [PATCH 085/702] fixes #177 --- forms/pom.xml | 8 ++++---- pom.xml | 2 +- ui/pom.xml | 4 ++-- web/pom.xml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/forms/pom.xml b/forms/pom.xml index 6112473bb..fdc93d003 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -29,10 +29,10 @@ UTF-8 1.8 - 7.7.0 - 0.10.14 - 1.11 - 0.5.5 + 7.8.0 + 0.10.16 + 1.12 + 0.5.6 v20160822 diff --git a/pom.xml b/pom.xml index 521e08e12..27428a5be 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ UTF-8 1.8 3.5.3 - 1.5.3.RELEASE + 1.5.6.RELEASE 10.13.1.1 3.14 3.12 diff --git a/ui/pom.xml b/ui/pom.xml index 885388516..5b258eb00 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -84,7 +84,7 @@ com.github.eirslett frontend-maven-plugin - 1.4 + 1.5 @@ -94,7 +94,7 @@ generate-resources - v6.10.3 + v6.11.2 3.10.10 diff --git a/web/pom.xml b/web/pom.xml index 3a5451923..8c8f140cb 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -18,7 +18,7 @@ org.devgateway.toolkit.web.spring.WebApplication 1.8 1.3 - 2.6.1 + 2.7.0 From 1018af52409e87398aafe0d6c137de19b26e6a3a Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 3 Aug 2017 14:30:14 +0300 Subject: [PATCH 086/702] OCE-354 Moved the chart tools to popup header --- .../map/tender-locations/location.jsx | 153 ++++++++++-------- .../map/tender-locations/style.less | 5 + 2 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 ui/oce/visualizations/map/tender-locations/style.less diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 0064ea4f9..8c4478507 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -8,6 +8,64 @@ import OverviewChart from '../../../visualizations/charts/overview'; import CostEffectiveness from '../../../visualizations/charts/cost-effectiveness'; import { cacheFn, download } from '../../../tools'; import ProcurementMethodChart from '../../../visualizations/charts/procurement-method'; +// eslint-disable-next-line no-unused-vars +import styles from './style.less'; + +const addTenderDeliveryLocationId = cacheFn( + (filters, id) => filters.set('tenderLoc', id), +); + +class Tab extends translatable(Component) {} + +export class ChartTab extends Tab { + constructor(props) { + super(props); + this.state = { + chartData: null, + }; + } + + static getMargins() { + return { + t: 0, + l: 50, + r: 50, + b: 50, + }; + } + + static getChartClass() { return ''; } + + render() { + const { filters, styling, years, translations, data, monthly, months } = this.props; + const decoratedFilters = addTenderDeliveryLocationId(filters, data._id); + const doExcelExport = () => download({ + ep: this.constructor.Chart.excelEP, + filters: decoratedFilters, + years, + months, + t: translationKey => this.t(translationKey), + }); + return ( +
+ this.setState({ chartData })} + width={500} + height={350} + margin={this.constructor.getMargins()} + legend="h" + /> +
+ ); + } +} class LocationWrapper extends translatable(Component) { constructor(props) { @@ -27,26 +85,43 @@ class LocationWrapper extends translatable(Component) {
+ + {CurrentTab.prototype instanceof ChartTab && + + + Export + + ReactDOM.findDOMNode(this).querySelector('.modebar-btn:first-child').click()} + > + Screenshot + + + } {data.name} - Export - Screenshot
- {this.constructor.TABS.map((Tab, index) => ( + {this.constructor.TABS.map((tab, index) => (
this.setState({ currentTab: index })} role="button" tabIndex={0} - > - {Tab.getName(t)} + > + {tab.getName(t)}
))}
@@ -69,8 +144,6 @@ class LocationWrapper extends translatable(Component) { } } -class Tab extends translatable(Component) {} - export class OverviewTab extends Tab { static getName(t) { return t('maps:tenderLocations:tabs:overview:title'); } @@ -88,60 +161,6 @@ export class OverviewTab extends Tab { } } -const addTenderDeliveryLocationId = cacheFn( - (filters, id) => filters.set('tenderLoc', id), -); - -export class ChartTab extends Tab { - constructor(props) { - super(props); - this.state = { - chartData: null, - }; - } - - static getMargins() { - return { - t: 0, - l: 50, - r: 50, - b: 50, - }; - } - - static getChartClass() { return ''; } - - render() { - const { filters, styling, years, translations, data, monthly, months } = this.props; - const decoratedFilters = addTenderDeliveryLocationId(filters, data._id); - const doExcelExport = () => download({ - ep: this.constructor.Chart.excelEP, - filters: decoratedFilters, - years, - months, - t: translationKey => this.t(translationKey), - }); - return ( -
- this.setState({ chartData })} - width={500} - height={350} - margin={this.constructor.getMargins()} - legend="h" - /> -
- ); - } -} - export class OverviewChartTab extends ChartTab { static getName(t) { return t('charts:overview:title'); } diff --git a/ui/oce/visualizations/map/tender-locations/style.less b/ui/oce/visualizations/map/tender-locations/style.less new file mode 100644 index 000000000..a1f2545c2 --- /dev/null +++ b/ui/oce/visualizations/map/tender-locations/style.less @@ -0,0 +1,5 @@ +.chart-tools{ + img{ + margin-right: 5px; + } +} From e7a909dcfc842d66195f7bda2ef8586ed88cff46 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 3 Aug 2017 18:40:26 +0300 Subject: [PATCH 087/702] OCE-354 Added a very black export icon for location popup --- ui/assets/icons/export-very-black.svg | 7 +++++++ ui/oce/visualizations/map/tender-locations/location.jsx | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 ui/assets/icons/export-very-black.svg diff --git a/ui/assets/icons/export-very-black.svg b/ui/assets/icons/export-very-black.svg new file mode 100644 index 000000000..59417e99a --- /dev/null +++ b/ui/assets/icons/export-very-black.svg @@ -0,0 +1,7 @@ + + + + diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index 8c4478507..f8f939a5e 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -85,12 +85,11 @@ class LocationWrapper extends translatable(Component) {
From bc00f2f67a1fb1957d7ea9ae1cadab0d08ceac92 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 3 Aug 2017 18:51:09 +0300 Subject: [PATCH 088/702] OCE-355 autodetection of schema version for release packages. --- .../validator/OcdsValidatorConstants.java | 4 ++ .../ocds/validator/OcdsValidatorService.java | 47 +++++++++++++++++++ .../test/OcdsValidatorTestRelease.java | 17 +++++++ 3 files changed, 68 insertions(+) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index c512c1273..e46c3a7fd 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -19,6 +19,9 @@ public static final class Versions { public static final String OCDS_1_0_1 = "1.0.1"; public static final String OCDS_1_0_2 = "1.0.2"; public static final String OCDS_1_1_0 = "1.1.0"; + + public static final String[] ALL = {OCDS_1_0_0, OCDS_1_0_1, OCDS_1_0_2, OCDS_1_1_0}; + } public static final class Schemas { @@ -69,6 +72,7 @@ public static final class Extensions { public static final String EXTENSIONS_PROPERTY = "extensions"; public static final String RELEASES_PROPERTY = "releases"; + public static final String VERSION_PROPERTY = "version"; public static final SortedSet EXTENSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( Extensions.ALL))); diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 06677667e..ec8008aeb 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -41,6 +41,9 @@ public class OcdsValidatorService { private Map extensionReleaseJson = new ConcurrentHashMap<>(); + private Map majorLatestFullVersion = new ConcurrentHashMap<>(); + + @Autowired private ObjectMapper jacksonObjectMapper; @@ -208,9 +211,29 @@ private void initExtensions() { }); } + private void initMajorLatestFullVersion() { + + for (int i = 0; i < OcdsValidatorConstants.Versions.ALL.length; i++) { + String[] versions = OcdsValidatorConstants.Versions.ALL[i].split("\\."); + String majorMinor = versions[0] + "." + versions[1]; + + if (majorLatestFullVersion.containsKey(majorMinor)) { + if (majorLatestFullVersion.get(majorMinor) < Integer.parseInt(versions[2])) { + majorLatestFullVersion.put(majorMinor, Integer.parseInt(versions[2])); + } + } else { + majorLatestFullVersion.put(majorMinor, Integer.parseInt(versions[2])); + } + + } + + + } + @PostConstruct private void init() { initSchemaNamePrefix(); + initMajorLatestFullVersion(); initExtensions(); } @@ -221,6 +244,11 @@ public ProcessingReport validate(OcdsValidatorApiRequest request) { OcdsValidatorNodeRequest nodeRequest = convertApiRequestToNodeRequest(request); if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { + + if (request.getVersion() == null) { + throw new RuntimeException("Not allowed null version info for release validation!"); + } + return validateRelease(nodeRequest); } @@ -250,6 +278,7 @@ private JsonNode getJsonNodeFromUrl(String url) { } } + /** * Validates a release or an array of releases * @@ -266,6 +295,10 @@ private ProcessingReport validateRelease(OcdsValidatorNodeRequest nodeRequest) { } } + private String constructFullVersion(String majorMinor, Integer bugfix) { + return majorMinor + "." + bugfix; + } + /** * Validates a release package * @@ -273,6 +306,20 @@ private ProcessingReport validateRelease(OcdsValidatorNodeRequest nodeRequest) { * @return */ private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeRequest) { + + if (nodeRequest.getVersion() == null) { + //try autodetect using version in node + if (nodeRequest.getNode().hasNonNull(OcdsValidatorConstants.VERSION_PROPERTY)) { + String majorMinor = nodeRequest.getNode().get(OcdsValidatorConstants.VERSION_PROPERTY).asText(); + if (!majorLatestFullVersion.containsKey(majorMinor)) { + throw new RuntimeException("Unrecognized package release version " + majorMinor); + } + nodeRequest.setVersion(constructFullVersion(majorMinor, majorLatestFullVersion.get(majorMinor))); + } else { + throw new RuntimeException("Version schema property has to be present!"); + } + } + JsonSchema schema = getSchema(nodeRequest); try { ProcessingReport releasePackageReport = schema.validate(nodeRequest.getNode()); diff --git a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java index d5cacd98f..6bc040b6d 100644 --- a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java +++ b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java @@ -81,4 +81,21 @@ public void testReleasePackageValidation() { } + @Test + public void testReleasePackageValidationWithVersionAutodetect() { + + OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(null, + new TreeSet<>(OcdsValidatorConstants.EXTENSIONS), OcdsValidatorConstants.Schemas.RELEASE_PACKAGE); + + request.setJson(getJsonFromResource("/release-package.json")); + + ProcessingReport processingReport = ocdsValidatorService.validate(request); + if (!processingReport.isSuccess()) { + System.out.println(processingReport); + } + + Assert.assertTrue(processingReport.isSuccess()); + + } + } From 328694e4b0632f8ef5188e7134cc40da03a0c150 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 3 Aug 2017 19:57:19 +0300 Subject: [PATCH 089/702] OCE-354 Refactored the excel export functionality --- .../map/tender-locations/location.jsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index f8f939a5e..b8ae6a4aa 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -39,13 +39,6 @@ export class ChartTab extends Tab { render() { const { filters, styling, years, translations, data, monthly, months } = this.props; const decoratedFilters = addTenderDeliveryLocationId(filters, data._id); - const doExcelExport = () => download({ - ep: this.constructor.Chart.excelEP, - filters: decoratedFilters, - years, - months, - t: translationKey => this.t(translationKey), - }); return (
this.t(translationKey), + }); + } + render() { const { currentTab } = this.state; const { data, translations, filters, years, styling, monthly, months } = this.props; @@ -87,7 +93,7 @@ class LocationWrapper extends translatable(Component) {
{CurrentTab.prototype instanceof ChartTab && - + this.doExcelExport()}> Export Date: Thu, 3 Aug 2017 20:28:59 +0300 Subject: [PATCH 090/702] OCE-354 Figured out a way to space axis title --- ui/oce/visualizations/map/tender-locations/style.less | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/oce/visualizations/map/tender-locations/style.less b/ui/oce/visualizations/map/tender-locations/style.less index a1f2545c2..d493ee828 100644 --- a/ui/oce/visualizations/map/tender-locations/style.less +++ b/ui/oce/visualizations/map/tender-locations/style.less @@ -3,3 +3,12 @@ margin-right: 5px; } } + +.tender-locations-popup{ + .chart-container svg.main-svg{ + overflow: visible; + } + .g-ytitle{ + transform: translateX(-5px); + } +} From eafa549a9aa1c303142ba47b87f41088ced3ac6f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 3 Aug 2017 20:44:21 +0300 Subject: [PATCH 091/702] OCE-354 Figured out a way to capitalize axes title --- ui/oce/visualizations/map/style.less | 2 +- .../map/tender-locations/location.jsx | 16 ++++++++++++---- .../map/tender-locations/style.less | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index fb9e2e2f2..023560c1b 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -10,7 +10,7 @@ .tender-locations-popup{ font-size: 9pt; .map-chart{ - margin-left: -35px; + margin-left: -30px; .main-svg{ background: transparent!important; } diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index b8ae6a4aa..f594d7dba 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -108,7 +108,6 @@ class LocationWrapper extends translatable(Component) { > Screenshot @@ -173,19 +172,28 @@ export class OverviewChartTab extends ChartTab { static getChartClass() { return 'overview'; } } -OverviewChartTab.Chart = OverviewChart; +const capitalizeAxisTitles = Class => class extends Class { + getLayout() { + const layout = super.getLayout(); + layout.xaxis.title = layout.xaxis.title.toUpperCase(); + layout.yaxis.title = layout.yaxis.title.toUpperCase(); + return layout; + } +}; + +OverviewChartTab.Chart = capitalizeAxisTitles(OverviewChart); export class CostEffectivenessTab extends ChartTab { static getName(t) { return t('charts:costEffectiveness:title'); } } -CostEffectivenessTab.Chart = CostEffectiveness; +CostEffectivenessTab.Chart = capitalizeAxisTitles(CostEffectiveness); export class ProcurementMethodTab extends ChartTab { static getName(t) { return t('charts:procurementMethod:title'); } } -ProcurementMethodTab.Chart = ProcurementMethodChart; +ProcurementMethodTab.Chart = capitalizeAxisTitles(ProcurementMethodChart); LocationWrapper.TABS = [OverviewTab, OverviewChartTab, CostEffectivenessTab, ProcurementMethodTab]; diff --git a/ui/oce/visualizations/map/tender-locations/style.less b/ui/oce/visualizations/map/tender-locations/style.less index d493ee828..142a88aca 100644 --- a/ui/oce/visualizations/map/tender-locations/style.less +++ b/ui/oce/visualizations/map/tender-locations/style.less @@ -9,6 +9,6 @@ overflow: visible; } .g-ytitle{ - transform: translateX(-5px); + transform: translateX(-10px); } } From 3e105ba75faa50436ee9d79016b5cf1ce7bd97d9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 3 Aug 2017 22:06:05 +0300 Subject: [PATCH 092/702] OCE-354 Smaller charts --- ui/oce/visualizations/map/style.less | 6 +++--- ui/oce/visualizations/map/tender-locations/location.jsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/oce/visualizations/map/style.less b/ui/oce/visualizations/map/style.less index 023560c1b..b702a6241 100644 --- a/ui/oce/visualizations/map/style.less +++ b/ui/oce/visualizations/map/style.less @@ -1,10 +1,10 @@ @headerHeight: 48px; -@windowHeight: 400px; +@windowHeight: 300px; @borderRadius: 12px; .leaflet-popup-content{ - width: 650px!important; - height: 400px; + width: 550px!important; + height: @windowHeight; } .tender-locations-popup{ diff --git a/ui/oce/visualizations/map/tender-locations/location.jsx b/ui/oce/visualizations/map/tender-locations/location.jsx index f594d7dba..38204b3c4 100644 --- a/ui/oce/visualizations/map/tender-locations/location.jsx +++ b/ui/oce/visualizations/map/tender-locations/location.jsx @@ -50,8 +50,8 @@ export class ChartTab extends Tab { translations={translations} data={this.state.chartData} requestNewData={(_, chartData) => this.setState({ chartData })} - width={500} - height={350} + width={400} + height={250} margin={this.constructor.getMargins()} legend="h" /> From 5c9a5db156612f0814456a36c347697163a48333 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 7 Aug 2017 18:05:55 +0300 Subject: [PATCH 093/702] OCE-355 OCDS version compatibility check added vor extensions --- validator/pom.xml | 7 ++++++ .../validator/OcdsValidatorConstants.java | 1 + .../ocds/validator/OcdsValidatorService.java | 24 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/validator/pom.xml b/validator/pom.xml index 691a4755b..821ddedc0 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -20,6 +20,7 @@ 1.8 1.9 2.2.6 + 2.0.3 @@ -56,6 +57,12 @@ jackson-databind + + com.vdurmont + semver4j + ${semver4j.version} + + com.github.fge json-patch diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index e46c3a7fd..aba11009a 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -79,5 +79,6 @@ public static final class Extensions { public static final String EXTENSION_META = "extension.json"; + public static final String EXTENSION_META_COMPAT_PROPERTY = "compatibility"; public static final String EXTENSION_RELEASE_JSON = "release-schema.json"; } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index ec8008aeb..580e227cb 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -11,6 +11,8 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; +import com.vdurmont.semver4j.Requirement; +import com.vdurmont.semver4j.Semver; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; @@ -22,6 +24,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -78,7 +81,12 @@ private JsonNode applyExtensions(JsonNode schemaNode, OcdsValidatorRequest reque for (String ext : request.getExtensions()) { try { logger.debug("Applying schema extension " + ext); - getExtensionMeta(ext); //TODO: check extension meta and see if they apply for the current standard + JsonNode nodeMeta = getExtensionMeta(ext); + if (!compatibleExtension(nodeMeta, request.getVersion())) { + throw new RuntimeException("Cannot apply extension " + ext + " due to version incompatibilities. " + + "Extension meta is " + nodeMeta.toString() + " requested schema version " + + request.getVersion()); + } schemaResult = getExtensionReleaseJson(ext).apply(schemaResult); } catch (JsonPatchException e) { throw new RuntimeException(e); @@ -105,6 +113,20 @@ private JsonNode getExtensionMeta(String id) { } } + private boolean compatibleExtension(JsonNode extensionNodeMeta, String fullVersion) { + Assert.notNull(fullVersion, "OCDS Version cannot be null!"); + Assert.notNull(extensionNodeMeta, "Extension meta must not be null!"); + + JsonNode compatNode = extensionNodeMeta.get(OcdsValidatorConstants.EXTENSION_META_COMPAT_PROPERTY); + if (compatNode == null) { + return true; //by default if we don't say anything about compatibility, it is compatible + } + + Semver requestedVersion = new Semver(fullVersion, Semver.SemverType.NPM); + return requestedVersion.satisfies(Requirement.buildNPM(compatNode.asText())); + } + + private JsonMergePatch getExtensionReleaseJson(String id) { //check if preloaded as extension if (extensionReleaseJson.containsKey(id)) { From 163ce97df6a1581bb91823ea7e654e831e8fc864 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 11 Aug 2017 13:31:20 +0300 Subject: [PATCH 094/702] OCE-355 patched for semver4j bug #7 https://github.com/vdurmont/semver4j/issues/7 --- .../ocds/validator/OcdsValidatorService.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 580e227cb..11bf639ce 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -123,7 +123,25 @@ private boolean compatibleExtension(JsonNode extensionNodeMeta, String fullVersi } Semver requestedVersion = new Semver(fullVersion, Semver.SemverType.NPM); - return requestedVersion.satisfies(Requirement.buildNPM(compatNode.asText())); + return requestedVersion.satisfies(Requirement.buildNPM(patchSemverNPMWildcardNotWorking(compatNode.asText()))); + } + + /** + * see https://github.com/vdurmont/semver4j/issues/7 + * @param version + * @return + */ + private String patchSemverNPMWildcardNotWorking(String version) { + String[] split = version.split("\\."); + if (split.length < 2) { + throw new RuntimeException("Incompatible version format."); + } + + if (split.length == 2) { + return version + ".0"; + } + + return version; } From c06df05c75ab78f4c66e38c9eab64e7ccd22c41c Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 11 Aug 2017 19:27:08 +0300 Subject: [PATCH 095/702] OCE-355 validator web based module for api calls --- checkstyle-suppressions.xml | 2 +- pom.xml | 1 + validator-web/.gitignore | 8 + validator-web/pom.xml | 174 ++++++++++++++++++ .../web/ValidatorWebApplication.java | 18 ++ .../web/ValidatorWebConfiguration.java | 13 ++ .../ocds/validator/web/application.properties | 12 ++ .../web/controller/ValidatorController.java | 49 +++++ validator/pom.xml | 7 + .../validator/OcdsValidatorApiRequest.java | 7 + .../ocds/validator/OcdsValidatorRequest.java | 8 +- web/pom.xml | 6 - 12 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 validator-web/.gitignore create mode 100644 validator-web/pom.xml create mode 100644 validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebApplication.java create mode 100644 validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebConfiguration.java create mode 100644 validator-web/src/main/java/org/devgateway/ocds/validator/web/application.properties create mode 100644 validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 663bfac0e..e8c04b99c 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -6,7 +6,7 @@ + files="PersistenceApplication\.java|MongoPersistenceApplication\.java|UIWebApplication\.java|WebApplication\.java|ReportingApplication\.java|ValidatorApplication\.java|ValidatorWebApplication\.java" /> \ No newline at end of file diff --git a/pom.xml b/pom.xml index b02e7cfd5..de0ccaac9 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ forms persistence-mongodb validator + validator-web 2015 diff --git a/validator-web/.gitignore b/validator-web/.gitignore new file mode 100644 index 000000000..fca2ac261 --- /dev/null +++ b/validator-web/.gitignore @@ -0,0 +1,8 @@ +/target/ +/.classpath +/.project +/.springBeans +/org.eclipse.m2e.core.prefs +/.settings/ +/derby.log +/.checkstyle diff --git a/validator-web/pom.xml b/validator-web/pom.xml new file mode 100644 index 000000000..18fd64409 --- /dev/null +++ b/validator-web/pom.xml @@ -0,0 +1,174 @@ + + + + + 4.0.0 + + validator-web + jar + + OCExplorer Validator Web + OCExplorer OCDS Validator Web + + + UTF-8 + org.devgateway.ocds.ValidatorApplication + 1.8 + 2.6.1 + + + + org.devgateway.ocds + oce + 0.0.1-SNAPSHOT + + + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + + + org.devgateway.ocds + validator + 0.0.1-SNAPSHOT + + + + junit + junit + test + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework + spring-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-devtools + true + + + + + com.fasterxml.jackson.core + jackson-databind + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.fasterxml.jackson.core + jackson-annotations + + + + + + + + true + src/main/java + + **/*.properties + **/*.sql + **/*.xml + + + + + true + src/main/resources + + **/*.properties + **/*.sql + **/*.xml + **/*.json + + + + + true + src/test/resources + + **/*.properties + **/*.sql + **/*.xml + **/*.json + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + ${spring.boot.version} + + + + + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + -proc:none + + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebApplication.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebApplication.java new file mode 100644 index 000000000..3a9ea115c --- /dev/null +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebApplication.java @@ -0,0 +1,18 @@ +package org.devgateway.ocds.validator.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; + +/** + * Created by mpostelnicu on 7/4/17. + */ +@SpringBootApplication +@PropertySource("classpath:/org/devgateway/ocds/validator/application.properties") +@ComponentScan("org.devgateway.ocds.validator") +public class ValidatorWebApplication { + public static void main(final String[] args) { + SpringApplication.run(ValidatorWebApplication.class, args); + } +} diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebConfiguration.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebConfiguration.java new file mode 100644 index 000000000..c5fc70837 --- /dev/null +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/ValidatorWebConfiguration.java @@ -0,0 +1,13 @@ +package org.devgateway.ocds.validator.web; + +import org.springframework.context.annotation.Configuration; + +/** + * Created by mpostelnicu on 7/7/17. + */ +@Configuration +public class ValidatorWebConfiguration { + + + +} diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/application.properties b/validator-web/src/main/java/org/devgateway/ocds/validator/web/application.properties new file mode 100644 index 000000000..ff438d37e --- /dev/null +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/application.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +spring.jmx.enabled=false diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java new file mode 100644 index 000000000..431f5fd70 --- /dev/null +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java @@ -0,0 +1,49 @@ +package org.devgateway.ocds.validator.web.controller; + +import com.github.fge.jsonschema.core.report.ProcessingReport; +import io.swagger.annotations.ApiOperation; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import org.devgateway.ocds.validator.OcdsValidatorApiRequest; +import org.devgateway.ocds.validator.OcdsValidatorService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Created by mpostelnicu on 16-May-17. + */ +@RestController +public class ValidatorController { + + @Autowired + private OcdsValidatorService ocdsValidatorService; + + @ApiOperation(value = "Validates data against Open Contracting Data Standard using x-www-form-urlencoded " + + "media type") + @RequestMapping(value = "/validateForm", method = {RequestMethod.GET, RequestMethod.POST}) + public ProcessingReport validateForm(@ModelAttribute @Valid OcdsValidatorApiRequest request, + final HttpServletResponse response) + throws IOException { + return ocdsValidatorService.validate(request); + } + + @ApiOperation(value = "Validates data against Open Contracting Data Standard using application/json " + + "media type") + @RequestMapping(value = "/validateJson", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity validateJson(@ModelAttribute @Valid @RequestBody + OcdsValidatorApiRequest request, + final HttpServletResponse response) + throws IOException { + return new ResponseEntity(ocdsValidatorService.validate(request), HttpStatus.OK); + } + +} diff --git a/validator/pom.xml b/validator/pom.xml index 821ddedc0..65eb6ddfe 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -21,6 +21,7 @@ 1.9 2.2.6 2.0.3 + 1.5.10 @@ -57,6 +58,12 @@ jackson-databind + + io.swagger + swagger-annotations + ${swagger.annotations.version} + + com.vdurmont semver4j diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java index 9324db3d2..60e49d412 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java @@ -1,5 +1,6 @@ package org.devgateway.ocds.validator; +import io.swagger.annotations.ApiModelProperty; import java.util.SortedSet; /** @@ -7,6 +8,8 @@ */ public class OcdsValidatorApiRequest extends OcdsValidatorRequest { + @ApiModelProperty(value = "This parameter specified which category can be used for grouping the results." + + " Possible values here are: bidTypeId, procuringEntityId.") private String json; private String url; @@ -15,6 +18,10 @@ public OcdsValidatorApiRequest(String version, SortedSet extensions, Str super(version, extensions, schemaType); } + public OcdsValidatorApiRequest() { + super(); + } + public String getJson() { return json; } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java index 5cd102f78..5826f0a62 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -1,6 +1,7 @@ package org.devgateway.ocds.validator; import java.util.SortedSet; +import java.util.TreeSet; import org.hibernate.validator.constraints.NotEmpty; /** @@ -8,6 +9,10 @@ */ public abstract class OcdsValidatorRequest { + public OcdsValidatorRequest() { + + } + public OcdsValidatorRequest(OcdsValidatorRequest request) { this.version = request.getVersion(); this.extensions = request.getExtensions(); @@ -22,6 +27,7 @@ public OcdsValidatorRequest(String version, SortedSet extensions, String /** * This returns a unique key of the validator request based on the set contents , version and schemaType + * * @return */ public String getKey() { @@ -30,7 +36,7 @@ public String getKey() { private String version; - private SortedSet extensions; + private SortedSet extensions = new TreeSet<>(); private String operation; diff --git a/web/pom.xml b/web/pom.xml index 554341807..15e5ec88b 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -36,12 +36,6 @@ org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - From 21c5175302adfbe6d44cbb54e8e017a9e3f59331 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 14 Aug 2017 17:39:34 +0300 Subject: [PATCH 096/702] OCE-355 fully working validator rest, for x-form, url, json inline formats --- .../web/controller/ValidatorController.java | 32 ++++++++++++------- .../validator/OcdsValidatorNodeRequest.java | 4 +++ .../ocds/validator/OcdsValidatorService.java | 26 ++++++++++----- ...t.java => OcdsValidatorStringRequest.java} | 14 ++------ .../validator/OcdsValidatorUrlRequest.java | 14 ++++++++ .../test/OcdsValidatorTestRelease.java | 8 ++--- 6 files changed, 64 insertions(+), 34 deletions(-) rename validator/src/main/java/org/devgateway/ocds/validator/{OcdsValidatorApiRequest.java => OcdsValidatorStringRequest.java} (62%) create mode 100644 validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java index 431f5fd70..499dc7ea4 100644 --- a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java @@ -3,15 +3,15 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import io.swagger.annotations.ApiOperation; import java.io.IOException; -import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import org.devgateway.ocds.validator.OcdsValidatorApiRequest; +import org.devgateway.ocds.validator.OcdsValidatorNodeRequest; import org.devgateway.ocds.validator.OcdsValidatorService; +import org.devgateway.ocds.validator.OcdsValidatorStringRequest; +import org.devgateway.ocds.validator.OcdsValidatorUrlRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -28,22 +28,32 @@ public class ValidatorController { @ApiOperation(value = "Validates data against Open Contracting Data Standard using x-www-form-urlencoded " + "media type") - @RequestMapping(value = "/validateForm", method = {RequestMethod.GET, RequestMethod.POST}) - public ProcessingReport validateForm(@ModelAttribute @Valid OcdsValidatorApiRequest request, - final HttpServletResponse response) + @RequestMapping(value = "/validateFormInline", method = {RequestMethod.GET, RequestMethod.POST}) + public ProcessingReport validateFormInline(@Valid OcdsValidatorStringRequest request) throws IOException { return ocdsValidatorService.validate(request); } @ApiOperation(value = "Validates data against Open Contracting Data Standard using application/json " + "media type") - @RequestMapping(value = "/validateJson", method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity validateJson(@ModelAttribute @Valid @RequestBody - OcdsValidatorApiRequest request, - final HttpServletResponse response) + @RequestMapping(value = "/validateJsonInline", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity validateJsonInline(@RequestBody @Valid + OcdsValidatorNodeRequest request) throws IOException { return new ResponseEntity(ocdsValidatorService.validate(request), HttpStatus.OK); } + + @ApiOperation(value = "Validates data against Open Contracting Data Standard using the " + + "data fetched from a given URL") + @RequestMapping(value = "/validateJsonUrl", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity validateJsonUrl(@RequestBody @Valid + OcdsValidatorUrlRequest request) + throws IOException { + return new ResponseEntity(ocdsValidatorService.validate(request), HttpStatus.OK); + } + + } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java index dba1de106..64faff676 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java @@ -14,6 +14,10 @@ public OcdsValidatorNodeRequest(String version, TreeSet extensions, Stri super(version, extensions, schemaType); } + public OcdsValidatorNodeRequest() { + + } + public OcdsValidatorNodeRequest(OcdsValidatorRequest request, JsonNode node) { super(request); this.node = node; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 11bf639ce..76ca5295d 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -220,7 +220,7 @@ private JsonSchema getSchema(OcdsValidatorRequest request) { schemaNode = applyExtensions(schemaNode, request); try { JsonSchema schema = JsonSchemaFactory.newBuilder() - .setReportProvider(new ListReportProvider(LogLevel.ERROR, LogLevel.FATAL)).freeze() + .setReportProvider(new ListReportProvider(LogLevel.INFO, LogLevel.FATAL)).freeze() .getJsonSchema(schemaNode); logger.debug("Saving to cache schema with extensions " + request.getKey()); keySchema.put(request.getKey(), schema); @@ -277,15 +277,21 @@ private void init() { initExtensions(); } + public ProcessingReport validate(OcdsValidatorStringRequest request) { + return validate(convertStringRequestToNodeRequest(request)); + } + + public ProcessingReport validate(OcdsValidatorUrlRequest request) { + return validate(convertUrlRequestToNodeRequest(request)); + } - public ProcessingReport validate(OcdsValidatorApiRequest request) { - logger.debug("Running validation for api request for schema of type " + request.getSchemaType() - + " and version " + request.getVersion()); - OcdsValidatorNodeRequest nodeRequest = convertApiRequestToNodeRequest(request); + public ProcessingReport validate(OcdsValidatorNodeRequest nodeRequest) { + logger.debug("Running validation for api request for schema of type " + nodeRequest.getSchemaType() + + " and version " + nodeRequest.getVersion()); if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { - if (request.getVersion() == null) { + if (nodeRequest.getVersion() == null) { throw new RuntimeException("Not allowed null version info for release validation!"); } @@ -394,12 +400,17 @@ private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeReq } - private OcdsValidatorNodeRequest convertApiRequestToNodeRequest(OcdsValidatorApiRequest request) { + private OcdsValidatorNodeRequest convertStringRequestToNodeRequest(OcdsValidatorStringRequest request) { JsonNode node = null; if (!StringUtils.isEmpty(request.getJson())) { node = getJsonNodeFromString(request.getJson()); } + return new OcdsValidatorNodeRequest(request, node); + } + + private OcdsValidatorNodeRequest convertUrlRequestToNodeRequest(OcdsValidatorUrlRequest request) { + JsonNode node = null; if (!StringUtils.isEmpty(request.getUrl())) { node = getJsonNodeFromUrl(request.getUrl()); } @@ -407,5 +418,4 @@ private OcdsValidatorNodeRequest convertApiRequestToNodeRequest(OcdsValidatorApi return new OcdsValidatorNodeRequest(request, node); } - } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java similarity index 62% rename from validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java rename to validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java index 60e49d412..b04ad3530 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorApiRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java @@ -6,19 +6,18 @@ /** * Created by mpostelnicu on 7/6/17. */ -public class OcdsValidatorApiRequest extends OcdsValidatorRequest { +public class OcdsValidatorStringRequest extends OcdsValidatorRequest { @ApiModelProperty(value = "This parameter specified which category can be used for grouping the results." + " Possible values here are: bidTypeId, procuringEntityId.") private String json; - private String url; - public OcdsValidatorApiRequest(String version, SortedSet extensions, String schemaType) { + public OcdsValidatorStringRequest(String version, SortedSet extensions, String schemaType) { super(version, extensions, schemaType); } - public OcdsValidatorApiRequest() { + public OcdsValidatorStringRequest() { super(); } @@ -30,11 +29,4 @@ public void setJson(String json) { this.json = json; } - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java new file mode 100644 index 000000000..03641b4c4 --- /dev/null +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java @@ -0,0 +1,14 @@ +package org.devgateway.ocds.validator; + +public class OcdsValidatorUrlRequest extends OcdsValidatorRequest { + + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java index 6bc040b6d..e98fb05a9 100644 --- a/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java +++ b/validator/src/test/java/org/devgateway/ocds/validator/test/OcdsValidatorTestRelease.java @@ -5,7 +5,7 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.util.TreeSet; -import org.devgateway.ocds.validator.OcdsValidatorApiRequest; +import org.devgateway.ocds.validator.OcdsValidatorStringRequest; import org.devgateway.ocds.validator.OcdsValidatorConstants; import org.devgateway.ocds.validator.OcdsValidatorService; import org.devgateway.ocds.validator.ValidatorApplication; @@ -33,7 +33,7 @@ public class OcdsValidatorTestRelease { @Test public void testReleaseValidation() { - OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, + OcdsValidatorStringRequest request = new OcdsValidatorStringRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, OcdsValidatorConstants.EXTENSIONS, OcdsValidatorConstants.Schemas.RELEASE); request.setJson(getJsonFromResource("/full-release.json")); @@ -67,7 +67,7 @@ private String getJsonFromResource(String resourceName) { @Test public void testReleasePackageValidation() { - OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, + OcdsValidatorStringRequest request = new OcdsValidatorStringRequest(OcdsValidatorConstants.Versions.OCDS_1_1_0, new TreeSet<>(OcdsValidatorConstants.EXTENSIONS), OcdsValidatorConstants.Schemas.RELEASE_PACKAGE); request.setJson(getJsonFromResource("/release-package.json")); @@ -84,7 +84,7 @@ public void testReleasePackageValidation() { @Test public void testReleasePackageValidationWithVersionAutodetect() { - OcdsValidatorApiRequest request = new OcdsValidatorApiRequest(null, + OcdsValidatorStringRequest request = new OcdsValidatorStringRequest(null, new TreeSet<>(OcdsValidatorConstants.EXTENSIONS), OcdsValidatorConstants.Schemas.RELEASE_PACKAGE); request.setJson(getJsonFromResource("/release-package.json")); From 8a4a2a4fa861da2d05b045ab021fdd6f49adb185 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 15 Aug 2017 16:30:09 +0300 Subject: [PATCH 097/702] OCE-355 changed validator response to return valid json messages --- .../web/controller/ValidatorController.java | 32 +++++++++++++------ .../validator/OcdsValidatorConstants.java | 1 + .../ocds/validator/OcdsValidatorService.java | 30 ++++++++++++++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java index 499dc7ea4..c6049aebb 100644 --- a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java @@ -1,5 +1,8 @@ package org.devgateway.ocds.validator.web.controller; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.github.fge.jsonschema.core.report.ListProcessingReport; import com.github.fge.jsonschema.core.report.ProcessingReport; import io.swagger.annotations.ApiOperation; import java.io.IOException; @@ -29,31 +32,42 @@ public class ValidatorController { @ApiOperation(value = "Validates data against Open Contracting Data Standard using x-www-form-urlencoded " + "media type") @RequestMapping(value = "/validateFormInline", method = {RequestMethod.GET, RequestMethod.POST}) - public ProcessingReport validateFormInline(@Valid OcdsValidatorStringRequest request) + public ResponseEntity validateFormInline(@Valid OcdsValidatorStringRequest request) throws IOException { - return ocdsValidatorService.validate(request); + return new ResponseEntity(processingReportToJsonNode(ocdsValidatorService.validate(request)), + HttpStatus.OK); } @ApiOperation(value = "Validates data against Open Contracting Data Standard using application/json " + "media type") @RequestMapping(value = "/validateJsonInline", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity validateJsonInline(@RequestBody @Valid - OcdsValidatorNodeRequest request) + public ResponseEntity validateJsonInline(@RequestBody @Valid + OcdsValidatorNodeRequest request) throws IOException { - return new ResponseEntity(ocdsValidatorService.validate(request), HttpStatus.OK); + return new ResponseEntity(processingReportToJsonNode( + ocdsValidatorService.validate(request)), HttpStatus.OK); } - @ApiOperation(value = "Validates data against Open Contracting Data Standard using the " + "data fetched from a given URL") @RequestMapping(value = "/validateJsonUrl", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity validateJsonUrl(@RequestBody @Valid - OcdsValidatorUrlRequest request) + public ResponseEntity validateJsonUrl(@RequestBody @Valid + OcdsValidatorUrlRequest request) throws IOException { - return new ResponseEntity(ocdsValidatorService.validate(request), HttpStatus.OK); + return new ResponseEntity(processingReportToJsonNode( + ocdsValidatorService.validate(request)), HttpStatus.OK); } + private JsonNode processingReportToJsonNode(ProcessingReport report) { + if (report.isSuccess()) { + return TextNode.valueOf("OK"); + } + if (report instanceof ListProcessingReport) { + return ((ListProcessingReport) report).asJson(); + } + throw new RuntimeException("Unsupported non ListProcessingReport!"); + } } diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index aba11009a..e61f479e6 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -72,6 +72,7 @@ public static final class Extensions { public static final String EXTENSIONS_PROPERTY = "extensions"; public static final String RELEASES_PROPERTY = "releases"; + public static final String OCID_PROPERTY = "ocid"; public static final String VERSION_PROPERTY = "version"; public static final SortedSet EXTENSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 76ca5295d..670e5ac74 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -6,8 +6,10 @@ import com.github.fge.jsonpatch.JsonPatchException; import com.github.fge.jsonpatch.mergepatch.JsonMergePatch; import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.report.ListProcessingReport; import com.github.fge.jsonschema.core.report.ListReportProvider; import com.github.fge.jsonschema.core.report.LogLevel; +import com.github.fge.jsonschema.core.report.ProcessingMessage; import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchema; import com.github.fge.jsonschema.main.JsonSchemaFactory; @@ -128,6 +130,7 @@ private boolean compatibleExtension(JsonNode extensionNodeMeta, String fullVersi /** * see https://github.com/vdurmont/semver4j/issues/7 + * * @param version * @return */ @@ -220,7 +223,7 @@ private JsonSchema getSchema(OcdsValidatorRequest request) { schemaNode = applyExtensions(schemaNode, request); try { JsonSchema schema = JsonSchemaFactory.newBuilder() - .setReportProvider(new ListReportProvider(LogLevel.INFO, LogLevel.FATAL)).freeze() + .setReportProvider(new ListReportProvider(LogLevel.ERROR, LogLevel.NONE)).freeze() .getJsonSchema(schemaNode); logger.debug("Saving to cache schema with extensions " + request.getKey()); keySchema.put(request.getKey(), schema); @@ -266,8 +269,6 @@ private void initMajorLatestFullVersion() { } } - - } @PostConstruct @@ -381,11 +382,26 @@ private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeReq } if (nodeRequest.getNode().hasNonNull(OcdsValidatorConstants.RELEASES_PROPERTY)) { + int i = 0; for (JsonNode release : nodeRequest.getNode().get(OcdsValidatorConstants.RELEASES_PROPERTY)) { OcdsValidatorNodeRequest releaseValidationRequest = new OcdsValidatorNodeRequest(nodeRequest, release); releaseValidationRequest.setSchemaType(OcdsValidatorConstants.Schemas.RELEASE); - releasePackageReport.mergeWith(validateRelease(releaseValidationRequest)); + ProcessingReport report = validateRelease(releaseValidationRequest); + if (!report.isSuccess()) { + ProcessingMessage message = new ProcessingMessage(); + message.setLogLevel(LogLevel.ERROR); + String ocid = getOcidFromRelease(release); + message.setMessage("Error(s) in release #" + i++ + + new String((ocid == null) ? "" : " with ocid "+ocid)); + + ProcessingReport wrapperReport = new ListProcessingReport(); + wrapperReport.error(message); + wrapperReport.mergeWith(report); + releasePackageReport.mergeWith(wrapperReport); + } else { + releasePackageReport.mergeWith(report); + } } } else { throw new RuntimeException("No releases were found during release package validation!"); @@ -399,6 +415,12 @@ private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeReq } } + private String getOcidFromRelease(JsonNode release) { + if (release.hasNonNull(OcdsValidatorConstants.OCID_PROPERTY)) { + return release.get(OcdsValidatorConstants.OCID_PROPERTY).asText(); + } + return null; + } private OcdsValidatorNodeRequest convertStringRequestToNodeRequest(OcdsValidatorStringRequest request) { JsonNode node = null; From e45fc29d17b7446c02a8c4d7c5a0443046b311d3 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 15 Aug 2017 17:13:52 +0300 Subject: [PATCH 098/702] OCE-355 added support to view supported builtin extensions and supported ocds versions --- .../web/controller/ValidatorController.java | 12 +++-- .../validator/OcdsValidatorConstants.java | 6 +++ .../validator/OcdsValidatorNodeRequest.java | 2 + .../ocds/validator/OcdsValidatorRequest.java | 14 +++++- .../ocds/validator/OcdsValidatorService.java | 49 +++++++++++++++---- .../validator/OcdsValidatorStringRequest.java | 2 + .../validator/OcdsValidatorUrlRequest.java | 5 ++ 7 files changed, 75 insertions(+), 15 deletions(-) diff --git a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java index c6049aebb..e4ee86b3f 100644 --- a/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java +++ b/validator-web/src/main/java/org/devgateway/ocds/validator/web/controller/ValidatorController.java @@ -7,7 +7,9 @@ import io.swagger.annotations.ApiOperation; import java.io.IOException; import javax.validation.Valid; +import org.devgateway.ocds.validator.OcdsValidatorConstants; import org.devgateway.ocds.validator.OcdsValidatorNodeRequest; +import org.devgateway.ocds.validator.OcdsValidatorRequest; import org.devgateway.ocds.validator.OcdsValidatorService; import org.devgateway.ocds.validator.OcdsValidatorStringRequest; import org.devgateway.ocds.validator.OcdsValidatorUrlRequest; @@ -34,7 +36,7 @@ public class ValidatorController { @RequestMapping(value = "/validateFormInline", method = {RequestMethod.GET, RequestMethod.POST}) public ResponseEntity validateFormInline(@Valid OcdsValidatorStringRequest request) throws IOException { - return new ResponseEntity(processingReportToJsonNode(ocdsValidatorService.validate(request)), + return new ResponseEntity(processingReportToJsonNode(ocdsValidatorService.validate(request), request), HttpStatus.OK); } @@ -46,7 +48,7 @@ public ResponseEntity validateJsonInline(@RequestBody @Valid OcdsValidatorNodeRequest request) throws IOException { return new ResponseEntity(processingReportToJsonNode( - ocdsValidatorService.validate(request)), HttpStatus.OK); + ocdsValidatorService.validate(request), request), HttpStatus.OK); } @ApiOperation(value = "Validates data against Open Contracting Data Standard using the " @@ -57,12 +59,12 @@ public ResponseEntity validateJsonUrl(@RequestBody @Valid OcdsValidatorUrlRequest request) throws IOException { return new ResponseEntity(processingReportToJsonNode( - ocdsValidatorService.validate(request)), HttpStatus.OK); + ocdsValidatorService.validate(request), request), HttpStatus.OK); } - private JsonNode processingReportToJsonNode(ProcessingReport report) { - if (report.isSuccess()) { + private JsonNode processingReportToJsonNode(ProcessingReport report, OcdsValidatorRequest request) { + if (report.isSuccess() && request.getOperation().equals(OcdsValidatorConstants.Operations.VALIDATE)) { return TextNode.valueOf("OK"); } if (report instanceof ListProcessingReport) { diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index e61f479e6..fedc61f5f 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -39,6 +39,12 @@ public static final class SchemaPrefixes { public static final String SCHEMA_POSTFIX = ".json"; + public static final class Operations { + public static final String VALIDATE = "validate"; + public static final String SHOW_SUPPORTED_OCDS = "show-supported-ocds"; + public static final String SHOW_BUILTIN_EXTENSIONS = "show-builtin-extensions"; + } + public static final class Extensions { //OFFICIAL public static final String OCDS_BID_EXTENSION_V1_1 = "ocds_bid_extension/v1.1"; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java index 64faff676..d83af4486 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorNodeRequest.java @@ -2,12 +2,14 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.TreeSet; +import javax.validation.constraints.NotNull; /** * Created by mpostelnicu on 7/6/17. */ public class OcdsValidatorNodeRequest extends OcdsValidatorRequest { + @NotNull(message = "Please provide the Json content!") private JsonNode node; public OcdsValidatorNodeRequest(String version, TreeSet extensions, String schemaType) { diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java index 5826f0a62..bedefb210 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorRequest.java @@ -2,6 +2,7 @@ import java.util.SortedSet; import java.util.TreeSet; +import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.NotEmpty; /** @@ -13,6 +14,14 @@ public OcdsValidatorRequest() { } + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + public OcdsValidatorRequest(OcdsValidatorRequest request) { this.version = request.getVersion(); this.extensions = request.getExtensions(); @@ -38,7 +47,10 @@ public String getKey() { private SortedSet extensions = new TreeSet<>(); - private String operation; + @Pattern(regexp = OcdsValidatorConstants.Operations.VALIDATE + "|" + + OcdsValidatorConstants.Operations.SHOW_BUILTIN_EXTENSIONS + "|" + + OcdsValidatorConstants.Operations.SHOW_SUPPORTED_OCDS) + private String operation = OcdsValidatorConstants.Operations.VALIDATE; @NotEmpty(message = "Please provide schemaType!") private String schemaType; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java index 670e5ac74..006b7a7c0 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorService.java @@ -287,22 +287,53 @@ public ProcessingReport validate(OcdsValidatorUrlRequest request) { } public ProcessingReport validate(OcdsValidatorNodeRequest nodeRequest) { - logger.debug("Running validation for api request for schema of type " + nodeRequest.getSchemaType() - + " and version " + nodeRequest.getVersion()); + if (nodeRequest.getOperation().equals(OcdsValidatorConstants.Operations.VALIDATE)) { + logger.debug("Running validation for api request for schema of type " + nodeRequest.getSchemaType() + + " and version " + nodeRequest.getVersion()); - if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { + if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE)) { - if (nodeRequest.getVersion() == null) { - throw new RuntimeException("Not allowed null version info for release validation!"); + if (nodeRequest.getVersion() == null) { + throw new RuntimeException("Not allowed null version info for release validation!"); + } + + return validateRelease(nodeRequest); } - return validateRelease(nodeRequest); + if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE_PACKAGE)) { + return validateReleasePackage(nodeRequest); + } } - if (nodeRequest.getSchemaType().equals(OcdsValidatorConstants.Schemas.RELEASE_PACKAGE)) { - return validateReleasePackage(nodeRequest); + if (nodeRequest.getOperation().equals(OcdsValidatorConstants.Operations.SHOW_BUILTIN_EXTENSIONS)) { + ListProcessingReport list = new ListProcessingReport(); + OcdsValidatorConstants.EXTENSIONS.forEach(e -> { + ProcessingMessage message = new ProcessingMessage(); + message.setMessage(e); + try { + list.info(message); + } catch (ProcessingException e1) { + e1.printStackTrace(); + } + } + ); + return list; } + + if (nodeRequest.getOperation().equals(OcdsValidatorConstants.Operations.SHOW_SUPPORTED_OCDS)) { + ListProcessingReport list = new ListProcessingReport(); + for (int i = 0; i < OcdsValidatorConstants.Versions.ALL.length; i++) { + ProcessingMessage message = new ProcessingMessage(); + message.setMessage(OcdsValidatorConstants.Versions.ALL[i]); + try { + list.info(message); + } catch (ProcessingException e1) { + e1.printStackTrace(); + } + } + return list; + } return null; } @@ -393,7 +424,7 @@ private ProcessingReport validateReleasePackage(OcdsValidatorNodeRequest nodeReq message.setLogLevel(LogLevel.ERROR); String ocid = getOcidFromRelease(release); message.setMessage("Error(s) in release #" + i++ - + new String((ocid == null) ? "" : " with ocid "+ocid)); + + new String((ocid == null) ? "" : " with ocid " + ocid)); ProcessingReport wrapperReport = new ListProcessingReport(); wrapperReport.error(message); diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java index b04ad3530..173745b61 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorStringRequest.java @@ -2,6 +2,7 @@ import io.swagger.annotations.ApiModelProperty; import java.util.SortedSet; +import javax.validation.constraints.NotNull; /** * Created by mpostelnicu on 7/6/17. @@ -10,6 +11,7 @@ public class OcdsValidatorStringRequest extends OcdsValidatorRequest { @ApiModelProperty(value = "This parameter specified which category can be used for grouping the results." + " Possible values here are: bidTypeId, procuringEntityId.") + @NotNull(message = "Please provide the Json text inside a json property!") private String json; diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java index 03641b4c4..e946c5b1b 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorUrlRequest.java @@ -1,7 +1,12 @@ package org.devgateway.ocds.validator; +import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.URL; + public class OcdsValidatorUrlRequest extends OcdsValidatorRequest { + @NotNull(message = "Please provide an URL!") + @URL private String url; public String getUrl() { From a11e46e100191da6bd294a3ab9769f6897e1e882 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 15 Aug 2017 21:04:42 +0300 Subject: [PATCH 099/702] OCE-355 added support for OCDS 1.1.1 --- .../validator/OcdsValidatorConstants.java | 4 +- .../record-package-schema-1.1.1.json | 217 ++ .../release-package-schema-1.1.1.json | 109 + .../schema/release/release-schema-1.1.1.json | 2061 +++++++++++++++++ 4 files changed, 2390 insertions(+), 1 deletion(-) create mode 100644 validator/src/main/resources/schema/record-package/record-package-schema-1.1.1.json create mode 100644 validator/src/main/resources/schema/release-package/release-package-schema-1.1.1.json create mode 100644 validator/src/main/resources/schema/release/release-schema-1.1.1.json diff --git a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java index fedc61f5f..c6e6ce061 100644 --- a/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java +++ b/validator/src/main/java/org/devgateway/ocds/validator/OcdsValidatorConstants.java @@ -19,8 +19,10 @@ public static final class Versions { public static final String OCDS_1_0_1 = "1.0.1"; public static final String OCDS_1_0_2 = "1.0.2"; public static final String OCDS_1_1_0 = "1.1.0"; + public static final String OCDS_1_1_1 = "1.1.1"; - public static final String[] ALL = {OCDS_1_0_0, OCDS_1_0_1, OCDS_1_0_2, OCDS_1_1_0}; + public static final String[] ALL = {OCDS_1_0_0, OCDS_1_0_1, OCDS_1_0_2, OCDS_1_1_0, + OCDS_1_1_1}; } diff --git a/validator/src/main/resources/schema/record-package/record-package-schema-1.1.1.json b/validator/src/main/resources/schema/record-package/record-package-schema-1.1.1.json new file mode 100644 index 000000000..8efc0be68 --- /dev/null +++ b/validator/src/main/resources/schema/record-package/record-package-schema-1.1.1.json @@ -0,0 +1,217 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Record package", + "description": "The record package contains a list of records along with some publishing metadata. The records pull together all the releases under a single Open Contracting ID and compile them into the latest version of the information along with the history of any data changes.", + "type": "object", + "properties": { + "uri": { + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world.", + "type": "string", + "format": "uri" + }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "publisher": { + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the organization or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": [ + "string", + "null" + ] + }, + "uid": { + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "name" + ] + }, + "license": { + "title": "License", + "description": "A link to the license that applies to the data in this data package. [Open Definition Conformant](http://opendefinition.org/licenses/) licenses are strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publicationPolicy": { + "title": "Publication policy", + "description": "A link to a document describing the publishers publication policy.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publishedDate": { + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", + "type": "string", + "format": "date-time" + }, + "packages": { + "title": "Packages", + "description": "A list of URIs of all the release packages that were used to create this record package.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "format": "uri" + }, + "uniqueItems": true + }, + "records": { + "title": "Records", + "description": "The records for this data package.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/record" + }, + "uniqueItems": true + } + }, + "required": [ + "uri", + "publisher", + "publishedDate", + "records", + "version" + ], + "definitions": { + "record": { + "title": "Record", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A unique identifier that identifies the unique Open Contracting Process. For more information see: http://standard.open-contracting.org/{{version}}/{{lang}}/getting_started/contracting_process/", + "type": "string" + }, + "releases": { + "title": "Releases", + "description": "An array of linking identifiers or releases", + "oneOf": [ + { + "title": "Linked releases", + "description": "A list of objects that identify the releases associated with this Open Contracting ID. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "description": "Information to uniquely identify the release.", + "type": "object", + "properties": { + "url": { + "description": "The URL of the release which contains the URL of the package with the releaseID appended using a fragment identifier e.g. http://standard.open-contracting.org/{{version}}/{{lang}}/examples/tender.json#ocds-213czf-000-00001", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Release Date", + "description": "The date of the release, should match `date` at the root level of the release. This is used to sort the releases in the list into date order.", + "type": "string", + "format": "date-time" + }, + "tag": { + "title": "Release Tag", + "description": "The tag should match the tag in the release. This provides additional context when reviewing a record to see what types of releases are included for this ocid.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + } + } + }, + "required": [ + "url", + "date" + ] + }, + "minItems": 1 + }, + { + "title": "Embedded releases", + "description": "A list of releases, with all the data. The releases MUST be sorted into date order in the array, from oldest (at position 0) to newest (last).", + "type": "array", + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__1/release-schema.json" + }, + "minItems": 1 + } + ] + }, + "compiledRelease": { + "title": "Compiled release", + "description": "This is the latest version of all the contracting data, it has the same schema as an open contracting release.", + "$ref": "http://standard.open-contracting.org/schema/1__1__1/release-schema.json" + }, + "versionedRelease": { + "title": "Versioned release", + "description": "This contains the history of the data in the compiledRecord. With all versions of the information and the release they came from.", + "$ref": "http://standard.open-contracting.org/schema/1__1__1/versioned-release-validation-schema.json" + } + }, + "required": [ + "ocid", + "releases" + ] + } + } +} diff --git a/validator/src/main/resources/schema/release-package/release-package-schema-1.1.1.json b/validator/src/main/resources/schema/release-package/release-package-schema-1.1.1.json new file mode 100644 index 000000000..373901610 --- /dev/null +++ b/validator/src/main/resources/schema/release-package/release-package-schema-1.1.1.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release Package", + "description": "Note that all releases within a release package must have a unique releaseID within this release package.", + "type": "object", + "required": [ + "uri", + "publisher", + "publishedDate", + "releases", + "version" + ], + "properties": { + "uri": { + "title": "Package identifier", + "description": "The URI of this package that identifies it uniquely in the world. Recommended practice is to use a dereferenceable URI, where a persistent copy of this package is available.", + "type": "string", + "format": "uri" + }, + "version": { + "title": "OCDS schema version", + "description": "The version of the OCDS schema used in this package, expressed as major.minor For example: 1.0 or 1.1", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "extensions": { + "title": "OCDS extensions", + "description": "An array of OCDS extensions used in this package. Each entry should be a URL to the extension.json file for that extension.", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "publishedDate": { + "title": "Published date", + "description": "The date that this package was published. If this package is generated 'on demand', this date should reflect the date of the last change to the underlying contents of the package.", + "type": "string", + "format": "date-time" + }, + "releases": { + "title": "Releases", + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://standard.open-contracting.org/schema/1__1__1/release-schema.json" + }, + "uniqueItems": true + }, + "publisher": { + "title": "Publisher", + "description": "Information to uniquely identify the publisher of this package.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the organization or department responsible for publishing this data.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "The scheme that holds the unique identifiers used to identify the item being identified.", + "type": [ + "string", + "null" + ] + }, + "uid": { + "title": "uid", + "description": "The unique ID for this entity under the given ID scheme. Note the use of 'uid' rather than 'id'. See issue #245.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the publisher.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "required": [ + "name" + ] + }, + "license": { + "title": "License", + "description": "A link to the license that applies to the data in this package. A Public Domain Dedication or [Open Definition Conformant](http://opendefinition.org/licenses/) license is strongly recommended. The canonical URI of the license should be used. Documents linked from this file may be under other license conditions. ", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "publicationPolicy": { + "title": "Publication policy", + "description": "A link to a document describing the publishers [publication policy](http://standard.open-contracting.org/{{version}}/{{lang}}/implementation/publication_policy/).", + "type": [ + "string", + "null" + ], + "format": "uri" + } + } +} diff --git a/validator/src/main/resources/schema/release/release-schema-1.1.1.json b/validator/src/main/resources/schema/release/release-schema-1.1.1.json new file mode 100644 index 000000000..31a4e1afe --- /dev/null +++ b/validator/src/main/resources/schema/release/release-schema-1.1.1.json @@ -0,0 +1,2061 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Schema for an Open Contracting Release", + "type": "object", + "properties": { + "ocid": { + "title": "Open Contracting ID", + "description": "A globally unique identifier for this Open Contracting Process. Composed of a publisher prefix and an identifier for the contracting process. For more information see the [Open Contracting Identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/)", + "type": "string", + "minLength": 1 + }, + "id": { + "title": "Release ID", + "description": "An identifier for this particular release of information. A release identifier must be unique within the scope of its related contracting process (defined by a common ocid), and unique within any release package it appears in. A release identifier must not contain the # character.", + "type": "string", + "minLength": 1, + "omitWhenMerged": true + }, + "date": { + "title": "Release Date", + "description": "The date this information was first released, or published.", + "type": "string", + "format": "date-time", + "omitWhenMerged": true + }, + "tag": { + "title": "Release Tag", + "description": "One or more values from the [releaseTag codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#release-tag). Tags may be used to filter release and to understand the kind of information that a release might contain.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "planning", + "planningUpdate", + "tender", + "tenderAmendment", + "tenderUpdate", + "tenderCancellation", + "award", + "awardUpdate", + "awardCancellation", + "contract", + "contractUpdate", + "contractAmendment", + "implementation", + "implementationUpdate", + "contractTermination", + "compiled" + ] + }, + "codelist": "releaseTag.csv", + "openCodelist": false, + "minItems": 1 + }, + "initiationType": { + "title": "Initiation type", + "description": "String specifying the type of initiation process used for this contract, taken from the [initiationType](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#initiation-type) codelist. Currently only tender is supported.", + "type": "string", + "enum": [ + "tender" + ], + "codelist": "initiationType.csv", + "openCodelist": false + }, + "parties": { + "title": "Parties", + "description": "Information on the parties (organizations, economic operators and other participants) who are involved in the contracting process and their roles, e.g. buyer, procuring entity, supplier etc. Organization references elsewhere in the schema are used to refer back to this entries in this list.", + "type": "array", + "items": { + "$ref": "#/definitions/Organization" + }, + "uniqueItems": true + }, + "buyer": { + "title": "Buyer", + "description": "The buyer is the entity whose budget will be used to purchase the goods. This may be different from the procuring entity who may be specified in the tender data.", + "$ref": "#/definitions/OrganizationReference" + }, + "planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. This includes information related to the process of deciding what to contract, when and how.", + "$ref": "#/definitions/Planning" + }, + "tender": { + "title": "Tender", + "description": "The activities undertaken in order to enter into a contract.", + "$ref": "#/definitions/Tender" + }, + "awards": { + "title": "Awards", + "description": "Information from the award phase of the contracting process. There may be more than one award per contracting process e.g. because the contract is split among different providers, or because it is a standing offer.", + "type": "array", + "items": { + "$ref": "#/definitions/Award" + }, + "uniqueItems": true + }, + "contracts": { + "title": "Contracts", + "description": "Information from the contract creation phase of the procurement process.", + "type": "array", + "items": { + "$ref": "#/definitions/Contract" + }, + "uniqueItems": true + }, + "language": { + "title": "Release language", + "description": "Specifies the default language of the data using either two-letter [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended.", + "type": [ + "string", + "null" + ], + "default": "en" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process follows on from one or more prior process, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to relate mini-competitions to their parent frameworks, full tenders to a pre-qualification phase, or individual tenders to a broad planning process.", + "title": "Related processes", + "type": "array" + } + }, + "required": [ + "ocid", + "id", + "date", + "tag", + "initiationType" + ], + "definitions": { + "Planning": { + "title": "Planning", + "description": "Information from the planning phase of the contracting process. Note that many other fields may be filled in a planning release, in the appropriate fields in other schema sections, these would likely be estimates at this stage e.g. totalValue in tender", + "type": "object", + "properties": { + "rationale": { + "title": "Rationale", + "description": "The rationale for the procurement provided in free text. More detail can be provided in an attached document.", + "type": [ + "string", + "null" + ] + }, + "budget": { + "$ref": "#/definitions/Budget" + }, + "documents": { + "title": "Documents", + "description": "A list of documents related to the planning process.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } + }, + "milestones": { + "title": "Planning milestones", + "description": "A list of milestones associated with the planning stage.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Tender": { + "title": "Tender", + "description": "Data regarding tender process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Tender ID", + "description": "An identifier for this tender process. This may be the same as the ocid, or may be drawn from an internally held identifier for this tender.", + "type": [ + "string", + "integer" + ], + "minLength": 1, + "versionId": true + }, + "title": { + "title": "Tender title", + "description": "A title for this tender. This will often be used by applications as a headline to attract interest, and to help analysts understand the nature of this procurement.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Tender description", + "description": "A summary description of the tender. This should complement structured information provided using the items array. Descriptions should be short and easy to read. Avoid using ALL CAPS. ", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Tender status", + "description": "The current status of the tender based on the [tenderStatus codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#tender-status)", + "type": [ + "string", + "null" + ], + "codelist": "tenderStatus.csv", + "openCodelist": false, + "enum": [ + "planning", + "planned", + "active", + "cancelled", + "unsuccessful", + "complete", + "withdrawn", + null + ] + }, + "procuringEntity": { + "title": "Procuring entity", + "description": "The entity managing the procurement. This may be different from the buyer who pays for, or uses, the items being procured.", + "$ref": "#/definitions/OrganizationReference" + }, + "items": { + "title": "Items to be procured", + "description": "The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.", + "type": "array", + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "value": { + "title": "Value", + "description": "The total upper estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "minValue": { + "title": "Minimum value", + "description": "The minimum estimated value of the procurement. A negative value indicates that the contracting process may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "procurementMethod": { + "title": "Procurement method", + "description": "Specify tendering method using the [method codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#method). This is a closed codelist. Local method types should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "method.csv", + "openCodelist": false, + "enum": [ + "open", + "selective", + "limited", + "direct", + null + ] + }, + "procurementMethodDetails": { + "title": "Procurement method details", + "description": "Additional detail on the procurement method used. This field may be used to provide the local name of the particular procurement method used.", + "type": [ + "string", + "null" + ] + }, + "procurementMethodRationale": { + "title": "Procurement method rationale", + "description": "Rationale for the chosen procurement method. This is especially important to provide a justification in the case of limited tenders or direct awards.", + "type": [ + "string", + "null" + ] + }, + "mainProcurementCategory": { + "title": "Main procurement category", + "description": "The primary category describing the main object of this contracting process from the [procurementCategory](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#procurement-category) codelist. This is a closed codelist. Local classifications should be mapped to this list.", + "type": [ + "string", + "null" + ], + "codelist": "procurementCategory.csv", + "openCodelist": false, + "enum": [ + "goods", + "works", + "services", + null + ] + }, + "additionalProcurementCategories": { + "title": "Additional procurement categories", + "description": "Any additional categories which describe the objects of this contracting process, from the [extendedProcurementCategory](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#extended-procurement-category) codelist. This is an open codelist. Local categories can be included in this list.", + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "codelist": "extendedProcurementCategory.csv", + "openCodelist": true + }, + "awardCriteria": { + "title": "Award criteria", + "description": "Specify the award criteria for the procurement, using the [award criteria codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#award-criteria)", + "type": [ + "string", + "null" + ], + "codelist": "awardCriteria.csv", + "openCodelist": true + }, + "awardCriteriaDetails": { + "title": "Award criteria details", + "description": "Any detailed or further information on the award or selection criteria.", + "type": [ + "string", + "null" + ] + }, + "submissionMethod": { + "title": "Submission method", + "description": "Specify the method by which bids must be submitted, in person, written, or electronic auction. Using the [submission method codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#submission-method)", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "codelist": "submissionMethod.csv", + "openCodelist": true + }, + "submissionMethodDetails": { + "title": "Submission method details", + "description": "Any detailed or further information on the submission method. This may include the address, e-mail address or online service to which bids should be submitted, and any special requirements to be followed for submissions.", + "type": [ + "string", + "null" + ] + }, + "tenderPeriod": { + "title": "Tender period", + "description": "The period when the tender is open for submissions. The end date is the closing date for tender submissions.", + "$ref": "#/definitions/Period" + }, + "enquiryPeriod": { + "title": "Enquiry period", + "description": "The period during which potential bidders may submit questions and requests for clarification to the entity managing procurement. Details of how to submit enquiries should be provided in attached notices, or in submissionMethodDetails. Structured dates for when responses to questions will be made can be provided using tender milestones.", + "$ref": "#/definitions/Period" + }, + "hasEnquiries": { + "title": "Has enquiries?", + "description": "A true/false field to indicate whether any enquiries were received during the tender process. Structured information on enquiries that were received, and responses to them, can be provided using the enquiries extension.", + "type": [ + "boolean", + "null" + ] + }, + "eligibilityCriteria": { + "title": "Eligibility criteria", + "description": "A description of any eligibility criteria for potential suppliers.", + "type": [ + "string", + "null" + ] + }, + "awardPeriod": { + "title": "Evaluation and award period", + "description": "The period for decision making regarding the contract award. The end date should be the date on which an award decision is due to be finalized. The start date is optional, and may be used to indicate the start of an evaluation period.", + "$ref": "#/definitions/Period" + }, + "contractPeriod": { + "description": "The period over which the contract is estimated or required to be active. If the tender does not specify explicit dates, the duration field may be used.", + "title": "Contract period", + "$ref": "#/definitions/Period" + }, + "numberOfTenderers": { + "title": "Number of tenderers", + "description": "The number of parties who submit a bid.", + "type": [ + "integer", + "null" + ] + }, + "tenderers": { + "title": "Tenderers", + "description": "All parties who submit a bid on a tender. More detailed information on bids and the bidding organization can be provided using the optional bid extension.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" + }, + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the tender, including any notices. See the [documentType codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#document-type) for details of potential documents to include. Common documents include official legal notices of tender, technical specifications, evaluation criteria, and, as a tender process progresses, clarifications and replies to queries.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + } + }, + "milestones": { + "title": "Milestones", + "description": "A list of milestones associated with the tender.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A tender amendment is a formal change to the tender, and generally involves the publication of a new tender notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(procurementMethodRationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(awardCriteriaDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(submissionMethodDetails_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(eligibilityCriteria_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Award": { + "title": "Award", + "description": "An award for the given procurement. There may be more than one award per contracting process e.g. because the contract is split among different providers, or because it is a standing offer.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "Award ID", + "description": "The identifier for this award. It must be unique and cannot change within the Open Contracting Process it is part of (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Award title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "Award description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Award status", + "description": "The current status of the award drawn from the [awardStatus codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#award-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "unsuccessful", + null + ], + "codelist": "awardStatus.csv", + "openCodelist": false + }, + "date": { + "title": "Award date", + "description": "The date of the contract award. This is usually the date on which a decision to award was made.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "title": "Value", + "description": "The total value of this award. In the case of a framework contract this may be the total estimated lifetime value, or maximum value, of the agreement. There may be more than one award per procurement. A negative value indicates that the award may involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "suppliers": { + "title": "Suppliers", + "description": "The suppliers awarded this award. If different suppliers have been awarded different items of values, these should be split into separate award blocks.", + "type": "array", + "items": { + "$ref": "#/definitions/OrganizationReference" + }, + "uniqueItems": true + }, + "items": { + "title": "Items awarded", + "description": "The goods and services awarded in this award, broken into line items wherever possible. Items should not be duplicated, but the quantity specified instead.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "contractPeriod": { + "title": "Contract period", + "description": "The period for which the contract has been awarded.", + "$ref": "#/definitions/Period" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the award, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "amendments": { + "description": "An award amendment is a formal change to the details of the award, and generally involves the publication of a new award notice/release. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Contract": { + "type": "object", + "title": "Contract", + "description": "Information regarding the signed contract between the buyer and supplier(s).", + "required": [ + "id", + "awardID" + ], + "properties": { + "id": { + "title": "Contract ID", + "description": "The identifier for this contract. It must be unique and cannot change within its Open Contracting Process (defined by a single ocid). See the [identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/) for further details.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "awardID": { + "title": "Award ID", + "description": "The award.id against which this contract is being issued.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Contract title", + "description": "Contract title", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Contract description", + "description": "Contract description", + "type": [ + "string", + "null" + ] + }, + "status": { + "title": "Contract status", + "description": "The current status of the contract. Drawn from the [contractStatus codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#contract-status)", + "type": [ + "string", + "null" + ], + "enum": [ + "pending", + "active", + "cancelled", + "terminated", + null + ], + "codelist": "contractStatus.csv", + "openCodelist": false + }, + "period": { + "title": "Period", + "description": "The start and end date for the contract.", + "$ref": "#/definitions/Period" + }, + "value": { + "title": "Value", + "description": "The total value of this contract. A negative value indicates that the contract will involve payments from the supplier to the buyer (commonly used in concession contracts).", + "$ref": "#/definitions/Value" + }, + "items": { + "title": "Items contracted", + "description": "The goods, services, and any intangible outcomes in this contract. Note: If the items are the same as the award do not repeat.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/Item" + }, + "uniqueItems": true + }, + "dateSigned": { + "title": "Date signed", + "description": "The date the contract was signed. In the case of multiple signatures, the date of the last signature.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "documents": { + "title": "Documents", + "description": "All documents and attachments related to the contract, including any notices.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + }, + "implementation": { + "title": "Implementation", + "description": "Information related to the implementation of the contract in accordance with the obligations laid out therein.", + "$ref": "#/definitions/Implementation" + }, + "relatedProcesses": { + "uniqueItems": true, + "items": { + "$ref": "#/definitions/RelatedProcess" + }, + "description": "If this process is followed by one or more contracting processes, represented under a separate open contracting identifier (ocid) then details of the related process can be provided here. This is commonly used to point to subcontracts, or to renewal and replacement processes for this contract.", + "title": "Related processes", + "type": "array" + }, + "milestones": { + "title": "Contract milestones", + "description": "A list of milestones associated with the finalization of this contract.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + } + }, + "amendments": { + "description": "A contract amendment is a formal change to, or extension of, a contract, and generally involves the publication of a new contract notice/release, or some other documents detailing the change. The rationale and a description of the changes made can be provided here.", + "type": "array", + "title": "Amendments", + "items": { + "$ref": "#/definitions/Amendment" + } + }, + "amendment": { + "title": "Amendment", + "description": "The use of individual amendment objects has been deprecated. From OCDS 1.1 information should be provided in the amendments array.", + "$ref": "#/definitions/Amendment", + "deprecated": { + "description": "The single amendment object has been deprecated in favour of including amendments in an amendments (plural) array.", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Implementation": { + "type": "object", + "title": "Implementation", + "description": "Information during the performance / implementation stage of the contract.", + "properties": { + "transactions": { + "title": "Transactions", + "description": "A list of the spending transactions made against this contract", + "type": "array", + "items": { + "$ref": "#/definitions/Transaction" + }, + "uniqueItems": true + }, + "milestones": { + "title": "Milestones", + "description": "As milestones are completed, milestone completions should be documented.", + "type": "array", + "items": { + "$ref": "#/definitions/Milestone" + }, + "uniqueItems": true + }, + "documents": { + "title": "Documents", + "description": "Documents and reports that are part of the implementation phase e.g. audit and evaluation reports.", + "type": "array", + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + } + } + }, + "Milestone": { + "title": "Milestone", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier for this milestone, unique within this block. This field is used to keep track of multiple revisions of a milestone through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "title": { + "title": "Title", + "description": "Milestone title", + "type": [ + "string", + "null" + ] + }, + "type": { + "title": "Milestone type", + "description": "The type of milestone, drawn from an extended [milestoneType codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#milestone-type).", + "type": [ + "string", + "null" + ], + "codelist": "milestoneType.csv", + "openCodelist": true + }, + "description": { + "title": "Description", + "description": "A description of the milestone.", + "type": [ + "string", + "null" + ] + }, + "code": { + "title": "Milestone code", + "description": "Milestone codes can be used to track specific events that take place for a particular kind of contracting process. For example, a code of 'approvalLetter' could be used to allow applications to understand this milestone represents the date an approvalLetter is due or signed. Milestone codes is an open codelist, and codes should be agreed among data producers and the applications using that data.", + "type": [ + "string", + "null" + ] + }, + "dueDate": { + "title": "Due date", + "description": "The date the milestone is due.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateMet": { + "format": "date-time", + "title": "Date met", + "description": "The date on which the milestone was met.", + "type": [ + "string", + "null" + ] + }, + "dateModified": { + "title": "Date modified", + "description": "The date the milestone was last reviewed or modified and the status was altered or confirmed to still be correct.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "status": { + "title": "Status", + "description": "The status that was realized on the date provided in dateModified, drawn from the [milestoneStatus codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#milestone-status).", + "type": [ + "string", + "null" + ], + "enum": [ + "scheduled", + "met", + "notMet", + "partiallyMet", + null + ], + "codelist": "milestoneStatus.csv", + "openCodelist": false + }, + "documents": { + "title": "Documents", + "description": "List of documents associated with this milestone (Deprecated in 1.1).", + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "Inclusion of documents at the milestone level is now deprecated. Documentation should be attached in the tender, award, contract or implementation sections, and titles and descriptions used to highlight the related milestone. Publishers who wish to continue to provide documents at the milestone level should explicitly declare this by using the milestone documents extension." + }, + "items": { + "$ref": "#/definitions/Document" + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Document": { + "type": "object", + "title": "Document", + "description": "Links to, or descriptions of, external documents can be attached at various locations within the standard. Documents may be supporting information, formal notices, downloadable forms, or any other kind of resource that should be made public as part of full open contracting.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local, unique identifier for this document. This field is used to keep track of multiple revisions of a document through the compilation from release to record mechanism.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "documentType": { + "title": "Document type", + "description": "A classification of the document described taken from the [documentType codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#document-type). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": [ + "string", + "null" + ], + "codelist": "documentType.csv", + "openCodelist": true + }, + "title": { + "title": "Title", + "description": "The document title.", + "type": [ + "string", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A short description of the document. We recommend descriptions do not exceed 250 words. In the event the document is not accessible online, the description field can be used to describe arrangements for obtaining a copy of the document.", + "type": [ + "string", + "null" + ] + }, + "url": { + "title": "URL", + "description": " direct link to the document or attachment. The server providing access to this document should be configured to correctly report the document mime type.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "datePublished": { + "title": "Date published", + "description": "The date on which the document was first published. This is particularly important for legally important documents such as notices of a tender.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "dateModified": { + "title": "Date modified", + "description": "Date that the document was last modified", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "format": { + "title": "Format", + "description": "The format of the document taken from the [IANA Media Types codelist](http://www.iana.org/assignments/media-types/), with the addition of one extra value for 'offline/print', used when this document entry is being used to describe the offline publication of a document. Use values from the template column. Links to web pages should be tagged 'text/html'.", + "type": [ + "string", + "null" + ] + }, + "language": { + "title": "Language", + "description": "Specifies the language of the linked document using either two-letter [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), or extended [BCP47 language tags](http://www.w3.org/International/articles/language-tags/). The use of lowercase two-letter codes from [ISO639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) is strongly recommended unless there is a clear user need for distinguishing the language subtype.", + "type": [ + "string", + "null" + ] + } + }, + "patternProperties": { + "^(title_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Budget": { + "type": "object", + "title": "Budget information", + "description": "This section contain information about the budget line, and associated projects, through which this contracting process is funded. It draws upon data model of the [Fiscal Data Package](http://fiscal.dataprotocols.org/), and should be used to cross-reference to more detailed information held using a Budget Data Package, or, where no linked Budget Data Package is available, to provide enough information to allow a user to manually or automatically cross-reference with another published source of budget and project information.", + "properties": { + "id": { + "title": "ID", + "description": "An identifier for the budget line item which provides funds for this contracting process. This identifier should be possible to cross-reference against the provided data source.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Budget Source", + "description": "A short free text description of the budget source. May be used to provide the title of the budget line, or the programme used to fund this project.", + "type": [ + "string", + "null" + ] + }, + "amount": { + "title": "Amount", + "description": "The value reserved in the budget for this contracting process. A negative value indicates anticipated income to the budget as a result of this contracting process, rather than expenditure. Where the budget is drawn from multiple sources, the budget breakdown extension can be used.", + "$ref": "#/definitions/Value" + }, + "project": { + "title": "Project title", + "description": "The name of the project that through which this contracting process is funded (if applicable). Some organizations maintain a registry of projects, and the data should use the name by which the project is known in that registry. No translation option is offered for this string, as translated values can be provided in third-party data, linked from the data source above.", + "type": [ + "string", + "null" + ] + }, + "projectID": { + "title": "Project identifier", + "description": "An external identifier for the project that this contracting process forms part of, or is funded via (if applicable). Some organizations maintain a registry of projects, and the data should use the identifier from the relevant registry of projects.", + "type": [ + "string", + "integer", + "null" + ] + }, + "uri": { + "title": "Linked budget information", + "description": "A URI pointing directly to a machine-readable record about the budget line-item or line-items that fund this contracting process. Information may be provided in a range of formats, including using IATI, the Open Fiscal Data Standard or any other standard which provides structured data on budget sources. Human readable documents can be included using the planning.documents block.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "source": { + "title": "Data Source", + "description": "(Deprecated in 1.1) Used to point either to a corresponding Budget Data Package, or to a machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "deprecated": { + "deprecatedVersion": "1.1", + "description": "The budget data source field was intended to link to machine-readable data about the budget for a contracting process, but has been widely mis-used to provide free-text descriptions of budget providers. As a result, it has been removed from version 1.1. budget/uri can be used to provide a link to machine-readable budget information, and budget/description can be used to provide human-readable information on the budget source." + }, + "format": "uri" + } + }, + "patternProperties": { + "^(source_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + }, + "^(project_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Transaction": { + "type": "object", + "title": "Transaction information", + "description": "A spending transaction related to the contracting process. Draws upon the data models of the [Fiscal Data Package](http://fiscal.dataprotocols.org/) and the [International Aid Transparency Initiative](http://iatistandard.org/activity-standard/iati-activities/iati-activity/transaction/) and should be used to cross-reference to more detailed information held using a Fiscal Data Package, IATI file, or to provide enough information to allow a user to manually or automatically cross-reference with some other published source of transactional spending data.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A unique identifier for this transaction. This identifier should be possible to cross-reference against the provided data source. For IATI this is the transaction reference.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "source": { + "title": "Data source", + "description": "Used to point either to a corresponding Fiscal Data Package, IATI file, or machine or human-readable source where users can find further information on the budget line item identifiers, or project identifiers, provided here.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "date": { + "title": "Date", + "description": "The date of the transaction", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "value": { + "$ref": "#/definitions/Value", + "title": "Value", + "description": "The value of the transaction." + }, + "payer": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payer", + "description": "An organization reference for the organization from which the funds in this transaction originate." + }, + "payee": { + "$ref": "#/definitions/OrganizationReference", + "title": "Payee", + "description": "An organization reference for the organization which receives the funds in this transaction." + }, + "uri": { + "title": "Linked spending information", + "description": "A URI pointing directly to a machine-readable record about this spending transaction.", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "amount": { + "title": "Amount", + "description": "(Deprecated in 1.1. Use transaction.value instead) The value of the transaction. A negative value indicates a refund or correction.", + "$ref": "#/definitions/Value", + "deprecated": { + "description": "This field has been replaced by the `transaction.value` field for consistency with the use of value and amount elsewhere in the standard.", + "deprecatedVersion": "1.1" + } + }, + "providerOrganization": { + "title": "Provider organization", + "description": "(Deprecated in 1.1. Use transaction.payer instead.) The Organization Identifier for the organization from which the funds in this transaction originate. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the `transaction.payer` field to resolve ambiguity arising from 'provider' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" + } + }, + "receiverOrganization": { + "title": "Receiver organization", + "description": "(Deprecated in 1.1. Use transaction.payee instead). The Organization Identifier for the organization which receives the funds in this transaction. Expressed following the Organizational Identifier standard - consult the documentation and the codelist.", + "$ref": "#/definitions/Identifier", + "deprecated": { + "description": "This field has been replaced by the `transaction.payee` field to resolve ambiguity arising from 'receiver' being interpreted as relating to the goods or services procured rather than the flow of funds between the parties.", + "deprecatedVersion": "1.1" + } + } + } + }, + "OrganizationReference": { + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the party being referenced. This must match the name of an entry in the parties section.", + "title": "Organization name", + "minLength": 1 + }, + "id": { + "type": [ + "string", + "integer" + ], + "description": "The id of the party being referenced. This must match the id of an entry in the parties section.", + "title": "Organization ID" + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/) for the preferred scheme and identifier to use.", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and detailed legal identifier information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Identifier" + }, + "address": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and address information should only be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/Address", + "description": "(Deprecated outside the parties section)", + "title": "Address" + }, + "additionalIdentifiers": { + "type": "array", + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and additional identifiers for an organization should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "items": { + "$ref": "#/definitions/Identifier" + }, + "title": "Additional identifiers", + "uniqueItems": true, + "wholeListMerge": true, + "description": "(Deprecated outside the parties section) A list of additional / supplemental identifiers for the organization, using the [organization identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier." + }, + "contactPoint": { + "deprecated": { + "deprecatedVersion": "1.1", + "description": "From version 1.1, organizations should be referenced by their identifier and name in a document, and contact point information for an organization should be provided in the relevant cross-referenced entry in the parties section at the top level of a release." + }, + "$ref": "#/definitions/ContactPoint", + "description": "(Deprecated outside the parties section)", + "title": "Contact point" + } + }, + "required": [ + "name" + ], + "type": "object", + "description": "The id and name of the party being referenced. Used to cross-reference to the parties section", + "title": "Organization reference" + }, + "Organization": { + "title": "Organization", + "description": "A party (organization)", + "type": "object", + "properties": { + "name": { + "title": "Common name", + "description": "A common name for this organization or other participant in the contracting process. The identifier object provides an space for the formal legal name, and so this may either repeat that value, or could provide the common name by which this organization or entity is known. This field may also include details of the department or sub-unit involved in this contracting process.", + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string" + ], + "description": "The ID used for cross-referencing to this party from other sections of the release. This field may be built with the following structure {identifier.scheme}-{identifier.id}(-{department-identifier}).", + "title": "Entity ID" + }, + "identifier": { + "title": "Primary identifier", + "description": "The primary identifier for this organization or participant. Identifiers that uniquely pick out a legal entity should be preferred. Consult the [organization identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/) for the preferred scheme and identifier to use.", + "$ref": "#/definitions/Identifier" + }, + "additionalIdentifiers": { + "title": "Additional identifiers", + "description": "A list of additional / supplemental identifiers for the organization or participant, using the [organization identifier guidance](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/identifiers/). This could be used to provide an internally used identifier for this organization in addition to the primary legal entity identifier.", + "type": "array", + "items": { + "$ref": "#/definitions/Identifier" + }, + "uniqueItems": true, + "wholeListMerge": true + }, + "address": { + "$ref": "#/definitions/Address" + }, + "contactPoint": { + "$ref": "#/definitions/ContactPoint" + }, + "roles": { + "title": "Party roles", + "description": "The party's role(s) in the contracting process. Role(s) should be taken from the [partyRole codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#party-role). Values from the provided codelist should be used wherever possible, though extended values can be provided if the codelist does not have a relevant code.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "codelist": "partyRole.csv", + "openCodelist": true + }, + "details": { + "type": [ + "object", + "null" + ], + "description": "Additional classification information about parties can be provided using partyDetail extensions that define particular properties and classification schemes. ", + "title": "Details" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Item": { + "title": "Item", + "type": "object", + "description": "A good, service, or work to be contracted.", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "ID", + "description": "A local identifier to reference and merge the items by. Must be unique within a given array of items.", + "type": [ + "string", + "integer" + ], + "minLength": 1 + }, + "description": { + "title": "Description", + "description": "A description of the goods, services to be provided.", + "type": [ + "string", + "null" + ] + }, + "classification": { + "title": "Classification", + "description": "The primary classification for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#item-classification-scheme) to identify preferred classification lists, including CPV and GSIN.", + "$ref": "#/definitions/Classification" + }, + "additionalClassifications": { + "title": "Additional classifications", + "description": "An array of additional classifications for the item. See the [itemClassificationScheme](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#item-classification-scheme) codelist for common options to use in OCDS. This may also be used to present codes from an internal classification scheme.", + "type": "array", + "items": { + "$ref": "#/definitions/Classification" + }, + "uniqueItems": true, + "wholeListMerge": true + }, + "quantity": { + "title": "Quantity", + "description": "The number of units required", + "type": [ + "number", + "null" + ] + }, + "unit": { + "title": "Unit", + "description": "A description of the unit in which the supplies, services or works are provided (e.g. hours, kilograms) and the unit-price. For comparability, an established list of units can be used. ", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "The list from which units of measure identifiers are taken. This should be an entry from the options available in the [unitClassificationScheme](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#unit-classification-scheme) codelist. Use of the scheme 'UNCEFACT' for the UN/CEFACT Recommendation 20 list of 'Codes for Units of Measure Used in International Trade' is recommended, although other options are available.", + "type": [ + "string", + "null" + ], + "codelist": "unitClassificationScheme.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The identifier from the codelist referenced in the scheme property. Check the codelist for details of how to find and use identifiers from the scheme in use.", + "type": [ + "string", + "null" + ], + "versionId": true + }, + "name": { + "title": "Name", + "description": "Name of the unit.", + "type": [ + "string", + "null" + ] + }, + "value": { + "title": "Value", + "description": "The monetary value of a single unit.", + "$ref": "#/definitions/Value" + }, + "uri": { + "title": "URI", + "description": "If the scheme used provide a machine-readable URI for this unit of measure, this can be given.", + "format": "uri", + "type": [ + "string", + "null" + ] + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Amendment": { + "title": "Amendment", + "type": "object", + "description": "Amendment information", + "properties": { + "date": { + "title": "Amendment date", + "description": "The date of this amendment.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "rationale": { + "title": "Rationale", + "description": "An explanation for the amendment.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "An identifier for this amendment: often the amendment number", + "type": [ + "string", + "null" + ], + "title": "ID" + }, + "description": { + "description": "A free text, or semi-structured, description of the changes made in this amendment.", + "type": [ + "string", + "null" + ], + "title": "Description" + }, + "amendsReleaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **before** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amended release (identifier)" + }, + "releaseID": { + "description": "Provide the identifier (release.id) of the OCDS release (from this contracting process) that provides the values for this contracting process **after** the amendment was made.", + "type": [ + "string", + "null" + ], + "title": "Amending release (identifier)" + }, + "changes": { + "title": "Amended fields", + "description": "An array change objects describing the fields changed, and their former values. (Deprecated in 1.1)", + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "title": "Property", + "description": "The property name that has been changed relative to the place the amendment is. For example if the contract value has changed, then the property under changes within the contract.amendment would be value.amount. (Deprecated in 1.1)", + "type": "string" + }, + "former_value": { + "title": "Former Value", + "description": "The previous value of the changed property, in whatever type the property is. (Deprecated in 1.1)", + "type": [ + "string", + "number", + "integer", + "array", + "object", + "null" + ] + } + } + }, + "deprecated": { + "description": "A free-text or semi-structured string describing the changes made in each amendment can be provided in the amendment.description field. To provide structured information on the fields that have changed, publishers should provide releases indicating the state of the contracting process before and after the amendment. ", + "deprecatedVersion": "1.1" + } + } + }, + "patternProperties": { + "^(rationale_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Classification": { + "title": "Classification", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "An classification should be drawn from an existing scheme or list of codes. This field is used to indicate the scheme/codelist from which the classification is drawn. For line item classifications, this value should represent an known [Item Classification Scheme](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#item-classification-scheme) wherever possible.", + "type": [ + "string", + "null" + ], + "codelist": "itemClassificationScheme.csv", + "openCodelist": true + }, + "id": { + "title": "ID", + "description": "The classification code drawn from the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "description": { + "title": "Description", + "description": "A textual description or title for the code.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the code. In the event individual URIs are not available for items in the identifier scheme this value should be left blank.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(description_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Identifier": { + "title": "Identifier", + "type": "object", + "properties": { + "scheme": { + "title": "Scheme", + "description": "Organization identifiers should be drawn from an existing organization identifier list. The scheme field is used to indicate the list or register from which the identifier is drawn. This value should be drawn from the [Organization Identifier Scheme](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#organization-identifier-scheme) codelist.", + "type": [ + "string", + "null" + ] + }, + "id": { + "title": "ID", + "description": "The identifier of the organization in the selected scheme.", + "type": [ + "string", + "integer", + "null" + ] + }, + "legalName": { + "title": "Legal Name", + "description": "The legally registered name of the organization.", + "type": [ + "string", + "null" + ] + }, + "uri": { + "title": "URI", + "description": "A URI to identify the organization, such as those provided by [Open Corporates](http://www.opencorporates.com) or some other relevant URI provider. This is not for listing the website of the organization: that can be done through the URL field of the Organization contact point.", + "type": [ + "string", + "null" + ], + "format": "uri" + } + }, + "patternProperties": { + "^(legalName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Address": { + "title": "Address", + "description": "An address. This may be the legally registered address of the organization, or may be a correspondence address for this particular contracting process.", + "type": "object", + "properties": { + "streetAddress": { + "title": "Street address", + "type": [ + "string", + "null" + ], + "description": "The street address. For example, 1600 Amphitheatre Pkwy." + }, + "locality": { + "title": "Locality", + "type": [ + "string", + "null" + ], + "description": "The locality. For example, Mountain View." + }, + "region": { + "title": "Region", + "type": [ + "string", + "null" + ], + "description": "The region. For example, CA." + }, + "postalCode": { + "title": "Postal code", + "type": [ + "string", + "null" + ], + "description": "The postal code. For example, 94043." + }, + "countryName": { + "title": "Country name", + "type": [ + "string", + "null" + ], + "description": "The country name. For example, United States." + } + }, + "patternProperties": { + "^(countryName_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "ContactPoint": { + "title": "Contact point", + "type": "object", + "description": "An person, contact point or department to contact in relation to this contracting process.", + "properties": { + "name": { + "title": "Name", + "type": [ + "string", + "null" + ], + "description": "The name of the contact person, department, or contact point, for correspondence relating to this contracting process." + }, + "email": { + "title": "Email", + "type": [ + "string", + "null" + ], + "description": "The e-mail address of the contact point/person." + }, + "telephone": { + "title": "Telephone", + "type": [ + "string", + "null" + ], + "description": "The telephone number of the contact point/person. This should include the international dialing code." + }, + "faxNumber": { + "title": "Fax number", + "type": [ + "string", + "null" + ], + "description": "The fax number of the contact point/person. This should include the international dialing code." + }, + "url": { + "title": "URL", + "type": [ + "string", + "null" + ], + "description": "A web address for the contact point/person.", + "format": "uri" + } + }, + "patternProperties": { + "^(name_(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)))$": { + "type": [ + "string", + "null" + ] + } + } + }, + "Value": { + "title": "Value", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "Amount as a number.", + "type": [ + "number", + "null" + ] + }, + "currency": { + "title": "Currency", + "description": "The currency for each amount should always be specified using the uppercase 3-letter currency code from ISO4217.", + "type": [ + "string", + "null" + ], + "codelist": "currency.csv", + "openCodelist": false, + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOV", + "BRL", + "BSD", + "BTN", + "BWP", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EEK", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "IRR", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LVL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MXV", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SSP", + "SRD", + "STD", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "USN", + "USS", + "UYI", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XBT", + "XCD", + "XDR", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMK", + "ZWL", + null + ] + } + } + }, + "Period": { + "title": "Period", + "description": " ", + "type": "object", + "properties": { + "startDate": { + "title": "Start date", + "description": "The start date for the period. When known, a precise start date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "endDate": { + "title": "End date", + "description": "The end date for the period. When known, a precise end date must always be provided.", + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "maxExtentDate": { + "description": "The period cannot be extended beyond this date. This field is optional, and can be used to express the maximum available data for extension or renewal of this period.", + "format": "date-time", + "title": "Maximum extent", + "type": [ + "string", + "null" + ] + }, + "durationInDays": { + "description": "The maximum duration of this period in days. A user interface may wish to collect or display this data in months or years as appropriate, but should convert it into days when completing this field. This field can be used when exact dates are not known. Where a startDate and endDate are given, this field is optional, and should reflect the difference between those two days. Where a startDate and maxExtentDate are given, this field is optional, and should reflect the difference between startDate and maxExtentDate.", + "title": "Duration (days)", + "type": [ + "integer", + "null" + ] + } + } + }, + "RelatedProcess": { + "description": "A reference to a related contracting process: generally one preceding or following on from the current process.", + "type": "object", + "title": "Related Process", + "properties": { + "id": { + "title": "Relationship ID", + "description": "A local identifier for this relationship, unique within this array.", + "type": [ + "string" + ] + }, + "relationship": { + "items": { + "type": "string" + }, + "description": "Specify the type of relationship using the [related process codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#related-process).", + "title": "Relationship", + "type": [ + "array", + "null" + ], + "codelist": "relatedProcess.csv", + "openCodelist": true + }, + "title": { + "description": "The title of the related process, where referencing an open contracting process, this field should match the tender/title field in the related process.", + "title": "Related process title", + "type": [ + "string", + "null" + ] + }, + "scheme": { + "title": "Scheme", + "description": "The identification scheme used by this cross-reference from the [related process scheme codelist](http://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#related-process-scheme) codelist. When cross-referencing information also published using OCDS, an Open Contracting ID (ocid) should be used.", + "type": [ + "string", + "null" + ], + "codelist": "relatedProcessScheme.csv", + "openCodelist": true + }, + "identifier": { + "description": "The identifier of the related process. When cross-referencing information also published using OCDS, this should be the Open Contracting ID (ocid).", + "title": "Identifier", + "type": [ + "string", + "null" + ] + }, + "uri": { + "format": "uri", + "description": "A URI pointing to a machine-readable document, release or record package containing the identified related process.", + "title": "Related process URI", + "type": [ + "string", + "null" + ] + } + } + } + } +} From 8385ff8c8e3d4ac4d2260c963ecd6c9c3d91247b Mon Sep 17 00:00:00 2001 From: Alexei Date: Fri, 18 Aug 2017 14:04:05 +0300 Subject: [PATCH 100/702] OCE-334 Linting --- ui/oce/index.jsx | 509 +++++++++++++++++++++++++---------------------- 1 file changed, 270 insertions(+), 239 deletions(-) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 8d15a4229..fd5a30400 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -1,26 +1,29 @@ -import cn from "classnames"; -import {fromJS, Map, Set} from "immutable"; -import {fetchJson, debounce, download, pluck, range} from "./tools"; -import URI from "urijs"; -import Filters from "./filters"; -import OCEStyle from "./style.less"; - -const MENU_BOX_COMPARISON = "menu-box"; +import cn from 'classnames'; +import { fromJS, Map, Set } from 'immutable'; +import URI from 'urijs'; +import PropTypes from 'prop-types'; +import { fetchJson, debounce, download, pluck, range } from './tools'; +import Filters from './filters'; +// eslint-disable-next-line no-unused-vars +import OCEStyle from './style.less'; + +const MENU_BOX_COMPARISON = 'menu-box'; const MENU_BOX_FILTERS = 'filters'; const ROLE_ADMIN = 'ROLE_ADMIN'; -class OCApp extends React.Component{ - constructor(props){ +// eslint-disable-next-line no-undef +class OCApp extends React.Component { + constructor(props) { super(props); this.tabs = []; this.state = { dashboardSwitcherOpen: false, exporting: false, - locale: localStorage.oceLocale || "en_US", + locale: localStorage.oceLocale || 'en_US', width: 0, currentTab: 0, - menuBox: "", - compareBy: "", + menuBox: '', + compareBy: '', comparisonCriteriaValues: [], selectedYears: Set(), selectedMonths: Set(range(1, 12)), @@ -31,280 +34,298 @@ class OCApp extends React.Component{ years: fromJS([]), user: { loggedIn: false, - isAdmin: false - } - } + isAdmin: false, + }, + }; } - registerTab(tab){ - this.tabs.push(tab); + componentWillMount() { + this.setState({ + width: document.querySelector('.years-bar').offsetWidth - 30, + }); } - t(text){ - const {locale} = this.state; - return this.constructor.TRANSLATIONS[locale][text]; - } + componentDidMount() { + this.fetchBidTypes(); + this.fetchYears(); + this.fetchUserInfo(); - updateComparisonCriteria(criteria){ - this.setState({ - menuBox: "", - compareBy: criteria, - comparisonCriteriaValues: [], - comparisonData: fromJS({}) - }); - if(!criteria) return; - fetchJson(new URI('/api/costEffectivenessTenderAmount').addSearch({ - groupByCategory: criteria, - pageSize: 3 - })).then(data => this.setState({ - comparisonCriteriaValues: data.map(datum => datum[0] || datum._id) + window.addEventListener('resize', debounce(() => { + this.setState({ + width: document.querySelector('.years-bar').offsetWidth - 30, + }); })); } - fetchBidTypes(){ - fetchJson('/api/ocds/bidType/all').then(data => - this.setState({bidTypes: data.reduce((map, datum) => map.set(datum.id, datum.description), Map())}) - ); + setMenuBox(e, slug) { + const { menuBox } = this.state; + e.stopPropagation(); + this.setState({ menuBox: menuBox === slug ? '' : slug }); } - fetchYears(){ - fetchJson('/api/tendersAwardsYears').then(data => { - const years = fromJS(data.map(pluck('_id'))); - this.setState({ - years, - selectedYears: Set(years) - }) - }) + setLocale(locale) { + this.setState({ locale }); + localStorage.oceLocale = locale; } - fetchUserInfo(){ - const noCacheUrl = new URI('/rest/userDashboards/getCurrentAuthenticatedUserDetails').addSearch('time', new Date()); - fetchJson(noCacheUrl).then( - ({username, id, roles}) => this.setState({ - user: { - loggedIn: true, - isAdmin: roles.some(({authority}) => authority == ROLE_ADMIN), - id - } - }) - ).catch( - () => this.setState({ - user: { - loggedIn: false - } - }) - ) + monthsBar() { + const { selectedMonths } = this.state; + return range(1, 12).map(month => ( this.setState({ + selectedMonths: selectedMonths.has(+month) ? + selectedMonths.delete(+month) : + selectedMonths.add(+month), + })} + > + {this.t(`general:months:${month}`)} + )); } - componentDidMount(){ - this.fetchBidTypes(); - this.fetchYears(); - this.fetchUserInfo(); + showMonths() { + const { years, selectedYears } = this.state; + return selectedYears.intersect(years).count() === 1; + } - this.setState({ - width: document.querySelector('.years-bar').offsetWidth - 30 + yearsBar() { + const { years, selectedYears } = this.state; + const toggleYear = year => this.setState({ + selectedYears: selectedYears.has(+year) ? + selectedYears.delete(+year) : + selectedYears.add(+year), + }); + const toggleOthersYears = year => this.setState({ + selectedYears: selectedYears.count() === 1 && selectedYears.has(year) ? + Set(years) : + Set([year]), }); + return years.sort().map(year => + ( toggleOthersYears(year)} + onClick={e => (e.shiftKey ? toggleOthersYears(year) : toggleYear(year))} + > + {year} + + {this.t('yearsBar:ctrlClickHint')} + + ), + ).toArray(); + } - window.addEventListener("resize", debounce(() => { - this.setState({ - width: document.querySelector('.years-bar').offsetWidth - 30 - }); - })); + content() { + const { filters, compareBy, comparisonCriteriaValues, currentTab, selectedYears, selectedMonths, + bidTypes, width, locale } = this.state; + const Tab = this.tabs[currentTab]; + return ( this.updateData([currentTab, ...path], data)} + requestNewComparisonData={(path, data) => + this.updateComparisonData([currentTab, ...path], data) + } + data={this.state.data.get(currentTab) || fromJS({})} + comparisonData={this.state.comparisonData.get(currentTab) || fromJS({})} + monthly={this.showMonths()} + years={selectedYears} + months={selectedMonths} + bidTypes={bidTypes} + width={width} + translations={this.constructor.TRANSLATIONS[locale]} + styling={this.constructor.STYLING} + />); } - setMenuBox(e, slug){ - let {menuBox} = this.state; - e.stopPropagation(); - this.setState({menuBox: menuBox == slug ? "" : slug}) + updateComparisonData(path, data) { + this.setState({ comparisonData: this.state.comparisonData.setIn(path, data) }); + } + + updateData(path, data) { + this.setState({ data: this.state.data.setIn(path, data) }); } - filters(){ - let {menuBox, bidTypes, locale, user} = this.state; - return this.setMenuBox(e, MENU_BOX_FILTERS)} - onUpdate={filters => this.setState({filters, menuBox: ""})} - open={menuBox == MENU_BOX_FILTERS} - bidTypes={bidTypes} - user={user} - translations={this.constructor.TRANSLATIONS[locale]} - /> + navigation() { + return this.tabs.map((tab, index) => this.navigationLink(tab, index)); } - comparison(){ - let {menuBox, compareBy} = this.state; - return
this.setMenuBox(e, MENU_BOX_COMPARISON)} - className={cn("filters compare", {open: menuBox == MENU_BOX_COMPARISON})} + navigationLink({ getName, icon }, index) { + return ( this.setState({ currentTab: index })} > - + + navigation icon + + +   + {getName(this.t.bind(this))} + ); + } + + comparison() { + const { menuBox, compareBy } = this.state; + return (
this.setMenuBox(e, MENU_BOX_COMPARISON)} + className={cn('filters compare', { open: menuBox === MENU_BOX_COMPARISON })} + > + compare {this.t('header:comparison:title')} - -
e.stopPropagation()}> + +
e.stopPropagation()}>
- +
-
; +
); } - navigationLink({getName, icon}, index){ - return this.setState({currentTab: index})}> - - - - -   - {getName(this.t.bind(this))} - + filters() { + const { menuBox, bidTypes, locale, user } = this.state; + return ( this.setMenuBox(e, MENU_BOX_FILTERS)} + onUpdate={filters => this.setState({ filters, menuBox: '' })} + open={menuBox === MENU_BOX_FILTERS} + bidTypes={bidTypes} + user={user} + translations={this.constructor.TRANSLATIONS[locale]} + />); } - navigation(){ - return this.tabs.map((tab, index) => this.navigationLink(tab, index)); - } - - updateData(path, data){ - this.setState({data: this.state.data.setIn(path, data)}); + fetchUserInfo() { + const noCacheUrl = new URI('/rest/userDashboards/getCurrentAuthenticatedUserDetails').addSearch('time', new Date()); + fetchJson(noCacheUrl).then( + ({ id, roles }) => this.setState({ + user: { + loggedIn: true, + isAdmin: roles.some(({ authority }) => authority === ROLE_ADMIN), + id, + }, + }), + ).catch( + () => this.setState({ + user: { + loggedIn: false, + }, + }), + ); } - updateComparisonData(path, data){ - this.setState({comparisonData: this.state.comparisonData.setIn(path, data)}) + fetchYears() { + fetchJson('/api/tendersAwardsYears').then((data) => { + const years = fromJS(data.map(pluck('_id'))); + this.setState({ + years, + selectedYears: Set(years), + }); + }); } - content(){ - let {filters, compareBy, comparisonCriteriaValues, currentTab, selectedYears, selectedMonths, bidTypes, width, - locale} = this.state; - let Tab = this.tabs[currentTab]; - return this.updateData([currentTab, ...path], data)} - requestNewComparisonData={(path, data) => this.updateComparisonData([currentTab, ...path], data)} - data={this.state.data.get(currentTab) || fromJS({})} - comparisonData={this.state.comparisonData.get(currentTab) || fromJS({})} - monthly={this.showMonths()} - years={selectedYears} - months={selectedMonths} - bidTypes={bidTypes} - width={width} - translations={this.constructor.TRANSLATIONS[locale]} - styling={this.constructor.STYLING} - />; + fetchBidTypes() { + fetchJson('/api/ocds/bidType/all').then(data => + this.setState({ + bidTypes: data.reduce((map, datum) => map.set(datum.id, datum.description), Map()), + }), + ); } - yearsBar(){ - const {years, selectedYears} = this.state; - const toggleYear = year => this.setState({ - selectedYears: selectedYears.has(+year) ? - selectedYears.delete(+year) : - selectedYears.add(+year) - }); - const toggleOthersYears = year => this.setState({ - selectedYears: 1 == selectedYears.count() && selectedYears.has(year) ? - Set(years) : - Set([year]) + updateComparisonCriteria(criteria) { + this.setState({ + menuBox: '', + compareBy: criteria, + comparisonCriteriaValues: [], + comparisonData: fromJS({}), }); - return years.sort().map(year => - toggleOthersYears(year)} - onClick={e => e.shiftKey ? toggleOthersYears(year) : toggleYear(year)} - > - {year} - - {this.t('yearsBar:ctrlClickHint')} - - - ).toArray(); - } - - showMonths(){ - const {years, selectedYears} = this.state; - return selectedYears.intersect(years).count() == 1; + if (!criteria) return; + fetchJson(new URI('/api/costEffectivenessTenderAmount').addSearch({ + groupByCategory: criteria, + pageSize: 3, + })).then(data => this.setState({ + comparisonCriteriaValues: data.map(datum => datum[0] || datum._id), + })); } - monthsBar(){ - const {selectedMonths} = this.state; - return range(1, 12).map(month => this.setState({ - selectedMonths: selectedMonths.has(+month) ? - selectedMonths.delete(+month) : - selectedMonths.add(+month) - })} - > - {this.t(`general:months:${month}`)} - ) + t(text) { + const { locale } = this.state; + return this.constructor.TRANSLATIONS[locale][text]; } - setLocale(locale){ - this.setState({locale}); - localStorage.oceLocale = locale; + registerTab(tab) { + this.tabs.push(tab); } - loginBox(){ - if(this.state.user.loggedIn){ - return - {this.t("general:logout")} - + loginBox() { + if (this.state.user.loggedIn) { + return ( + {this.t('general:logout')} + ); } - return - {this.t("general:login")} - + return ( + {this.t('general:login')} + ); } - languageSwitcher(){ - const {TRANSLATIONS} = this.constructor; - if(Object.keys(TRANSLATIONS).length <= 1) return null; + languageSwitcher() { + const { TRANSLATIONS } = this.constructor; + if (Object.keys(TRANSLATIONS).length <= 1) return null; return Object.keys(TRANSLATIONS).map(locale => - {`${locale} this.setLocale(locale)} - key={locale} - /> - ) + ({`${locale} this.setLocale(locale)} + key={locale} + />), + ); } - downloadExcel(){ - let {filters, selectedYears: years, selectedMonths: months} = this.state; - let onDone = () => this.setState({exporting: false}); - this.setState({exporting: true}); + downloadExcel() { + const { filters, selectedYears: years, selectedMonths: months } = this.state; + const onDone = () => this.setState({ exporting: false }); + this.setState({ exporting: true }); download({ ep: 'excelExport', filters, years, months, - t: this.t.bind(this) + t: this.t.bind(this), }).then(onDone).catch(onDone); - } - exportBtn(){ + exportBtn() { const { filters, selectedYears, locale, selectedMonths } = this.state; let url = new URI('/api/ocds/excelExport') .addSearch(filters.toJS()) .addSearch('year', selectedYears.toArray()) .addSearch('language', locale); - if(selectedYears.count() == 1){ + if (selectedYears.count() === 1) { url = url.addSearch('month', selectedMonths && selectedMonths.toJS()) .addSearch('monthly', true); } @@ -312,44 +333,54 @@ class OCApp extends React.Component{ return ( - ) + ); } - toggleDashboardSwitcher(e){ + toggleDashboardSwitcher(e) { e.stopPropagation(); - const {dashboardSwitcherOpen} = this.state; - this.setState({dashboardSwitcherOpen: !dashboardSwitcherOpen}); + const { dashboardSwitcherOpen } = this.state; + this.setState({ dashboardSwitcherOpen: !dashboardSwitcherOpen }); } - dashboardSwitcher(){ - const {dashboardSwitcherOpen} = this.state; - const {onSwitch} = this.props; + dashboardSwitcher() { + const { dashboardSwitcherOpen } = this.state; + const { onSwitch } = this.props; return ( -
-

+
+

this.toggleDashboardSwitcher()}> {this.t('general:title')} - + {this.t('general:subtitle')}

{dashboardSwitcherOpen && - }
- ) + ); } } +OCApp.propTypes = { + onSwitch: PropTypes.func.isRequired, +} + OCApp.TRANSLATIONS = { - us: {} + us: {}, }; OCApp.Filters = Filters; @@ -357,19 +388,19 @@ OCApp.Filters = Filters; OCApp.STYLING = { charts: { axisLabelColor: undefined, - traceColors: [] - } + traceColors: [], + }, }; OCApp.COMPARISON_TYPES = [{ value: '', - label: 'header:comparison:criteria:none' + label: 'header:comparison:criteria:none', }, { value: 'bidTypeId', - label: 'header:comparison:criteria:bidType' + label: 'header:comparison:criteria:bidType', }, { value: 'procuringEntityId', - label: 'header:comparison:criteria:procuringEntity' + label: 'header:comparison:criteria:procuringEntity', }]; export default OCApp; From 4fcc8da78b004ec6acfb330f77d2fbc00cb48252 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 21 Aug 2017 14:12:26 +0300 Subject: [PATCH 101/702] OCE-334 Added a router --- ui/oce/router.es6 | 4 ++++ ui/package.json | 1 + 2 files changed, 5 insertions(+) create mode 100644 ui/oce/router.es6 diff --git a/ui/oce/router.es6 b/ui/oce/router.es6 new file mode 100644 index 000000000..8a9f21af6 --- /dev/null +++ b/ui/oce/router.es6 @@ -0,0 +1,4 @@ +import Router from 'minimal-router'; + +const router = new Router(); +export default router; diff --git a/ui/package.json b/ui/package.json index 2f24c412f..0cd7010a5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -65,6 +65,7 @@ "leaflet.markercluster": "https://github.com/Leaflet/Leaflet.markercluster.git#leaflet-0.7", "less": "^2.5.1", "less-loader": "^2.2.0", + "minimal-router": "^1.0.5", "plotly.js": "^1.28.3", "prop-types": "^15.5.10", "rc-slider": "^7.0.3", From 59ecdffb7ebbb64aa9a7c4ebe15cc4b8be25125d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 21 Aug 2017 14:33:15 +0300 Subject: [PATCH 102/702] OCE-334 Fixed trying to access an unmounted DOM node --- ui/oce/index.jsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index fd5a30400..14d989579 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -39,22 +39,18 @@ class OCApp extends React.Component { }; } - componentWillMount() { - this.setState({ - width: document.querySelector('.years-bar').offsetWidth - 30, - }); - } - componentDidMount() { this.fetchBidTypes(); this.fetchYears(); this.fetchUserInfo(); - window.addEventListener('resize', debounce(() => { - this.setState({ - width: document.querySelector('.years-bar').offsetWidth - 30, - }); - })); + const calcYearsBarWidth = () => this.setState({ + width: document.querySelector('.years-bar').offsetWidth - 30, + }); + + calcYearsBarWidth(); + + window.addEventListener('resize', calcYearsBarWidth); } setMenuBox(e, slug) { From 33018b14f705f50554ad3ab4d587650e41277355 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 21 Aug 2017 14:37:47 +0300 Subject: [PATCH 103/702] OCE-334 Debouncing the window resize callback --- ui/oce/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 14d989579..c57018c18 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -50,7 +50,7 @@ class OCApp extends React.Component { calcYearsBarWidth(); - window.addEventListener('resize', calcYearsBarWidth); + window.addEventListener('resize', debounce(calcYearsBarWidth)); } setMenuBox(e, slug) { From 39e12a06abfc0eb32cbefccaf52fd0ac23f2be3c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 21 Aug 2017 15:40:49 +0300 Subject: [PATCH 104/702] OCE-334 Purged minimal-router, rolling our own --- ui/oce/router.es6 | 20 +++++++++++++++++--- ui/package.json | 1 - 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ui/oce/router.es6 b/ui/oce/router.es6 index 8a9f21af6..d1c18a46c 100644 --- a/ui/oce/router.es6 +++ b/ui/oce/router.es6 @@ -1,4 +1,18 @@ -import Router from 'minimal-router'; +const PREFIX = '#!'; +const listeners = []; -const router = new Router(); -export default router; +export const onNavigation = (listener) => { + listeners.push(listener); +}; + +export const getRoute = () => { + const raw = location.hash.split('/'); + const [maybePrefix, ...params] = raw; + return maybePrefix === PREFIX ? params : []; +}; + +export const navigate = (...params) => { + location.hash = `${PREFIX}/${params.join('/')}`; + const route = getRoute(); + listeners.forEach(listener => listener(route)); +}; diff --git a/ui/package.json b/ui/package.json index 0cd7010a5..2f24c412f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -65,7 +65,6 @@ "leaflet.markercluster": "https://github.com/Leaflet/Leaflet.markercluster.git#leaflet-0.7", "less": "^2.5.1", "less-loader": "^2.2.0", - "minimal-router": "^1.0.5", "plotly.js": "^1.28.3", "prop-types": "^15.5.10", "rc-slider": "^7.0.3", From 89b978d3bb44fdcad7c0bb9a9ace7fb5a488de41 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 21 Aug 2017 21:48:58 +0300 Subject: [PATCH 105/702] OCE-355 documentation --- validator-web/README.md | 2 ++ validator/README.md | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 validator-web/README.md create mode 100644 validator/README.md diff --git a/validator-web/README.md b/validator-web/README.md new file mode 100644 index 000000000..90b4a7f00 --- /dev/null +++ b/validator-web/README.md @@ -0,0 +1,2 @@ +# jOCDS - Open Contracting Data Standard Validator - Web API Component + diff --git a/validator/README.md b/validator/README.md new file mode 100644 index 000000000..42594ae0e --- /dev/null +++ b/validator/README.md @@ -0,0 +1,57 @@ +# jOCDS - The Java based Open Contracting Data Standard (OCDS) Validator + +## Introduction + +Open Contracting Data Standard (OCDS) is an emerging JSON based standard to track +contracting and tendering processes in a standardized format. +As the number of parties and institutions that are publishing tendering +and contracting data into the OCDS format is growing, there is a great need for +various tools to better validate and help with data standard compliance. + +This need is amplified by the existence of many extensions to the standard, +some official and many that have been contributed by the community +but also by the several version releases of the standard itself, which is still in +active development. Vendors and institions will sometimes choose to export into an +OCDS version (say 1.0.2) and expect tools to comply with this exact version. +Updates to latest standard format are sometimes not possible due to difficulties +financing support contracts to keep the export feeds always up to date. + +Although the OCDS team makes great efforts to keep the standard backwards compatible, +it is not always 100% possible to respect this since human error can and should be +addressed and sometimes the nature of the fixes may have proprity over +backwards compatibility. + +## Meet jOCDS + +jOCDS is a Java implementation of an OCDS Validator tool. + +Key features: +- one single file, java based +- can be started as a web server and use as a JSON REST API service to validate +incoming json data (see validator-web project). +- can be started from the command line and used as a command tool to validate OCDS +files and URLs. +- works in offline mode with all OCDS versions including the minor and patch versions. +Supported formats include 1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1 +- supports in offline mode all OCDS core extensions +- supports any other kind of extension that can be given as a URL +- perfoms extension compatiblity check, verifying if an extension is compliant or +not with the OCDS standard +- autodetects OCDS version from the release file, or enforces a specific OCDS version. +This can be helpful to check for backwards or forwards compatibility issues before +an actual data migration to the new OCDS version is performed +- works with release entities alone or in release packages +- caches all schemas after the given extensions are applied, therefore one should +expect good performance when validating large amounts of data, for example when the +tool is used as a web-based validator +- can be used as part of the OC Explorer suite of tools, or as a separate standalone tool + +## Architecture + +jOCDS is designed as a Spring service. It is packcaged as a Spring Boot +[fat executable JAR](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-build.html#howto-create-an-executable-jar-with-maven) +which does not have any dependencies other than Java Runtime Edition (JRE) installed +on the host machine and can be started from the command line. + + + From 2f6426608e2ae3a4e6009593e63d72ad5109da4d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 22 Aug 2017 07:12:14 +0300 Subject: [PATCH 106/702] OCE-334 1st lvl(dashboard) routing --- ui/oce-standalone/index.jsx | 6 ++--- ui/oce/corruption-risk/constants.es6 | 2 +- ui/oce/corruption-risk/index.jsx | 2 +- ui/oce/index.jsx | 2 +- ui/oce/switcher.jsx | 37 +++++++++++++++++----------- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ui/oce-standalone/index.jsx b/ui/oce-standalone/index.jsx index 954074e28..22aefda57 100644 --- a/ui/oce-standalone/index.jsx +++ b/ui/oce-standalone/index.jsx @@ -67,7 +67,7 @@ class OCEChild extends OCApp{

{dashboardSwitcherOpen && @@ -216,8 +216,8 @@ CorruptionRickDashboard.STYLING.charts.traceColors = ["#234e6d", "#3f7499", "#80 class OceSwitcher extends ViewSwitcher{} -OceSwitcher.views.default = OCEChild; -OceSwitcher.views.corruptionRiskDashboard = CorruptionRickDashboard; +OceSwitcher.views['m-and-e'] = OCEChild; +OceSwitcher.views.crd = CorruptionRickDashboard; ReactDOM.render( {dashboardSwitcherOpen && diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index c57018c18..36b2ed803 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -361,7 +361,7 @@ class OCApp extends React.Component { {dashboardSwitcherOpen && diff --git a/ui/oce/switcher.jsx b/ui/oce/switcher.jsx index db67d3d93..dc1603245 100644 --- a/ui/oce/switcher.jsx +++ b/ui/oce/switcher.jsx @@ -1,29 +1,36 @@ -import URI from "urijs"; +import PropTypes from 'prop-types'; +import { getRoute, navigate, onNavigation } from './router'; class OCESwitcher extends React.Component{ constructor(...args){ super(...args); - const uri = new URI(location); - const view = uri.hasQuery('corruption-risk-dashboard') ? - 'corruptionRiskDashboard' : - Object.keys(this.constructor.views)[0]; + const view = getRoute()[0] || Object.keys(this.constructor.views)[0]; - this.state={ - view - } + this.state = { + view, + }; + + onNavigation(([view]) => this.setState({view})); } - render(){ - const {translations, styling} = this.props; + render() { + const { translations, styling } = this.props; const CurrentView = this.constructor.views[this.state.view]; - return this.setState({view})} - translations={translations} - styling={styling} - />; + return ( + + ); } } +OCESwitcher.propTypes = { + translations: PropTypes.object.isRequired, + styling: PropTypes.object.isRequired, +}; + OCESwitcher.views = {}; export default OCESwitcher; From 99174ce2c4d7717305ca2125f62cfe18a3741816 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 22 Aug 2017 08:43:12 +0300 Subject: [PATCH 107/702] OCE-334 Refactored the switcher --- ui/oce/switcher.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ui/oce/switcher.jsx b/ui/oce/switcher.jsx index dc1603245..a495dc695 100644 --- a/ui/oce/switcher.jsx +++ b/ui/oce/switcher.jsx @@ -4,18 +4,22 @@ import { getRoute, navigate, onNavigation } from './router'; class OCESwitcher extends React.Component{ constructor(...args){ super(...args); - const view = getRoute()[0] || Object.keys(this.constructor.views)[0]; - this.state = { - view, + route: getRoute(), }; - onNavigation(([view]) => this.setState({view})); + onNavigation(route => this.setState({ route })); } render() { const { translations, styling } = this.props; - const CurrentView = this.constructor.views[this.state.view]; + const { route } = this.state; + const { views } = this.constructor; + + let [dashboard] = route; + if (!dashboard) dashboard = Object.keys(views)[0]; + const CurrentView = views[dashboard]; + return ( Date: Tue, 22 Aug 2017 09:02:12 +0300 Subject: [PATCH 108/702] OCE-334 Linting --- ui/oce/corruption-risk/index.jsx | 314 ++++++++++++++++--------------- 1 file changed, 167 insertions(+), 147 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 52954aae8..9506737c2 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -1,32 +1,33 @@ -import style from "./style.less"; -import cn from "classnames"; -import URI from "urijs"; -import {fetchJson, debounce, cacheFn, range, pluck, callFunc} from "../tools"; -import OverviewPage from "./overview-page"; -import CorruptionTypePage from "./corruption-type"; -import {Map, Set} from "immutable"; -import IndividualIndicatorPage from "./individual-indicator"; -import Filters from "./filters"; -import TotalFlags from "./total-flags"; -import LandingPopup from "./landing-popup"; -import {LOGIN_URL} from "./constants"; - -const ROLE_ADMIN = 'ROLE_ADMIN'; +import cn from 'classnames'; +import URI from 'urijs'; +import { Map, Set } from 'immutable'; +import PropTypes from 'prop-types'; +import { fetchJson, debounce, cacheFn, range, pluck, callFunc } from '../tools'; +import OverviewPage from './overview-page'; +import CorruptionTypePage from './corruption-type'; +import IndividualIndicatorPage from './individual-indicator'; +import Filters from './filters'; +import TotalFlags from './total-flags'; +import LandingPopup from './landing-popup'; +import { LOGIN_URL } from './constants'; +// eslint-disable-next-line no-unused-vars +import style from './style.less'; const CORRUPTION_TYPES = { - FRAUD: "Fraud", - RIGGING: "Process rigging", - COLLUSION: "Collusion" + FRAUD: 'Fraud', + RIGGING: 'Process rigging', + COLLUSION: 'Collusion', }; -class CorruptionRiskDashboard extends React.Component{ - constructor(...args){ +// eslint-disable-next-line no-undef +class CorruptionRiskDashboard extends React.Component { + constructor(...args) { super(...args); - this.state={ + this.state = { dashboardSwitcherOpen: false, user: { loggedIn: false, - isAdmin: false + isAdmin: false, }, page: 'overview', indicatorTypesMapping: {}, @@ -37,116 +38,64 @@ class CorruptionRiskDashboard extends React.Component{ allYears: [], width: 0, data: Map(), - showLandingPopup: !localStorage.alreadyVisited + showLandingPopup: !localStorage.alreadyVisited, }; localStorage.alreadyVisited = true; - this.destructFilters = cacheFn(filters => { - return { - filters: filters.delete('years').delete('months'), - years: filters.get('years', Set()), - months: filters.get('months', Set()) - } - }); - } - - fetchUserInfo(){ - const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); - fetchJson(noCacheUrl).then(({authenticated, disabledApiSecurity}) => { - this.setState({ - user: { - loggedIn: authenticated, - }, - showLandingPopup: !authenticated || disabledApiSecurity, - disabledApiSecurity - }) - }); - } - - fetchIndicatorTypesMapping(){ - fetchJson('/api/indicatorTypesMapping').then(data => this.setState({indicatorTypesMapping: data})); - } - - fetchYears(){ - fetchJson('/api/tendersAwardsYears').then(data => { - const years = data.map(pluck('_id')); - const {allMonths, currentFiltersState, appliedFilters} = this.state; - this.setState({ - currentFiltersState: currentFiltersState - .set('years', Set(years)) - .set('months', Set(allMonths)), - appliedFilters: appliedFilters - .set('years', Set(years)) - .set('months', Set(allMonths)), - allYears: years - }); - }); + this.destructFilters = cacheFn(filters => ({ + filters: filters.delete('years').delete('months'), + years: filters.get('years', Set()), + months: filters.get('months', Set()), + })); } - componentDidMount(){ + componentDidMount() { this.fetchUserInfo(); this.fetchIndicatorTypesMapping(); this.fetchYears(); + // eslint-disable-next-line react/no-did-mount-set-state this.setState({ - width: document.querySelector('.content').offsetWidth - 30 + width: document.querySelector('.content').offsetWidth - 30, }); - window.addEventListener("resize", debounce(() => { + window.addEventListener('resize', debounce(() => { this.setState({ - width: document.querySelector('.content').offsetWidth - 30 + width: document.querySelector('.content').offsetWidth - 30, }); })); } - toggleDashboardSwitcher(e){ - e.stopPropagation(); - const {dashboardSwitcherOpen} = this.state; - this.setState({dashboardSwitcherOpen: !dashboardSwitcherOpen}); - } - - loginBox(){ - if(this.state.user.loggedIn){ - return ( - - - - ) - } - return - - - } - - getPage(){ - const {translations} = this.props; + getPage() { + const { translations } = this.props; const styling = this.constructor.STYLING || this.props.styling; - const {page, appliedFilters, indicatorTypesMapping, width} = this.state; + const { page, appliedFilters, indicatorTypesMapping, width } = this.state; - const {filters, years, months} = this.destructFilters(appliedFilters); - const monthly = years.count() == 1; + const { filters, years, months } = this.destructFilters(appliedFilters); + const monthly = years.count() === 1; - if(page == 'overview'){ - return ; - } else if(page == 'corruption-type') { - const {corruptionType} = this.state; + if (page === 'overview') { + return (); + } else if (page === 'corruption-type') { + const { corruptionType } = this.state; const indicators = - Object.keys(indicatorTypesMapping).filter(key => indicatorTypesMapping[key].types.indexOf(corruptionType) > -1); + Object.keys(indicatorTypesMapping).filter(key => + indicatorTypesMapping[key].types.indexOf(corruptionType) > -1); return ( this.setState({page: 'individual-indicator', individualIndicator})} + onGotoIndicator={individualIndicator => this.setState({ page: 'individual-indicator', individualIndicator })} filters={filters} translations={translations} corruptionType={corruptionType} @@ -157,8 +106,8 @@ class CorruptionRiskDashboard extends React.Component{ styling={styling} /> ); - } else if(page == 'individual-indicator'){ - const {individualIndicator} = this.state; + } else if (page === 'individual-indicator') { + const { individualIndicator } = this.state; return ( - ) + ); + } + return null; + } + + loginBox() { + if (this.state.user.loggedIn) { + return ( + + + + ); } + return ( + + ); + } + + toggleDashboardSwitcher(e) { + e.stopPropagation(); + const { dashboardSwitcherOpen } = this.state; + this.setState({ dashboardSwitcherOpen: !dashboardSwitcherOpen }); + } + + fetchYears() { + fetchJson('/api/tendersAwardsYears').then((data) => { + const years = data.map(pluck('_id')); + const { allMonths, currentFiltersState, appliedFilters } = this.state; + this.setState({ + currentFiltersState: currentFiltersState + .set('years', Set(years)) + .set('months', Set(allMonths)), + appliedFilters: appliedFilters + .set('years', Set(years)) + .set('months', Set(allMonths)), + allYears: years, + }); + }); } - render(){ - const {dashboardSwitcherOpen, corruptionType, page, filterBoxIndex, currentFiltersState, appliedFilters - , data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, disabledApiSecurity, user} = this.state; - const {onSwitch, translations} = this.props; + fetchIndicatorTypesMapping() { + fetchJson('/api/indicatorTypesMapping').then(data => this.setState({ indicatorTypesMapping: data })); + } - const {filters, years, months} = this.destructFilters(appliedFilters); - const monthly = years.count() == 1; + fetchUserInfo() { + const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); + fetchJson(noCacheUrl).then(({ authenticated, disabledApiSecurity }) => { + this.setState({ + user: { + loggedIn: authenticated, + }, + showLandingPopup: !authenticated || disabledApiSecurity, + disabledApiSecurity, + }); + }); + } + + render() { + const { dashboardSwitcherOpen, corruptionType, page, filterBoxIndex, currentFiltersState, + appliedFilters, data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, + disabledApiSecurity } = this.state; + const { onSwitch, translations } = this.props; + + const { filters, years, months } = this.destructFilters(appliedFilters); + const monthly = years.count() === 1; return ( -
this.setState({dashboardSwitcherOpen: false, filterBoxIndex: null})} +
this.setState({ dashboardSwitcherOpen: false, filterBoxIndex: null })} > {showLandingPopup && - this.setState({showLandingPopup: false})}/>} + this.setState({ showLandingPopup: false })} + /> + }
- -
-

+ DG logo +
+

this.toggleDashboardSwitcher()} + > Corruption Risk Dashboard - +

{dashboardSwitcherOpen && @@ -208,15 +219,18 @@ class CorruptionRiskDashboard extends React.Component{
{!disabledApiSecurity && this.loginBox()}
-
-
+

this.setState({currentFiltersState})} - onApply={filtersToApply => this.setState({filterBoxIndex: null, appliedFilters: filtersToApply, currentFiltersState: filtersToApply})} + onUpdate={filtersState => this.setState({ filtersState })} + onApply={filtersToApply => this.setState({ + filterBoxIndex: null, + appliedFilters: filtersToApply, + currentFiltersState: filtersToApply, + })} translations={translations} currentBoxIndex={filterBoxIndex} - requestNewBox={index => this.setState({filterBoxIndex: index})} + requestNewBox={index => this.setState({ filterBoxIndex: index })} state={currentFiltersState} appliedFilters={appliedFilters} allYears={allYears} @@ -224,38 +238,38 @@ class CorruptionRiskDashboard extends React.Component{ />
- ) + ); } } +CorruptionRiskDashboard.propTypes = { + translations: PropTypes.object.isRequired, + styling: PropTypes.object.isRequired, + onSwitch: PropTypes.func.isRequired, +}; + export default CorruptionRiskDashboard; From 69b31feb3ddb66517c9bdbafd1d8d69e24400005 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 22 Aug 2017 10:55:00 +0300 Subject: [PATCH 109/702] OCE-334 CRD routing --- ui/oce/corruption-risk/index.jsx | 30 +++++++++++++++++------------- ui/oce/switcher.jsx | 5 +++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 9506737c2..dd006d04d 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -29,7 +29,6 @@ class CorruptionRiskDashboard extends React.Component { loggedIn: false, isAdmin: false, }, - page: 'overview', indicatorTypesMapping: {}, currentFiltersState: Map(), appliedFilters: Map(), @@ -67,9 +66,11 @@ class CorruptionRiskDashboard extends React.Component { } getPage() { - const { translations } = this.props; + const { translations, route, navigate } = this.props; const styling = this.constructor.STYLING || this.props.styling; - const { page, appliedFilters, indicatorTypesMapping, width } = this.state; + const [page] = route; + + const { appliedFilters, indicatorTypesMapping, width } = this.state; const { filters, years, months } = this.destructFilters(appliedFilters); const monthly = years.count() === 1; @@ -85,8 +86,8 @@ class CorruptionRiskDashboard extends React.Component { styling={styling} width={width} />); - } else if (page === 'corruption-type') { - const { corruptionType } = this.state; + } else if (page === 'type') { + const [, corruptionType] = route; const indicators = Object.keys(indicatorTypesMapping).filter(key => @@ -95,7 +96,7 @@ class CorruptionRiskDashboard extends React.Component { return ( this.setState({ page: 'individual-indicator', individualIndicator })} + onGotoIndicator={individualIndicator => navigate('indicator', individualIndicator)} filters={filters} translations={translations} corruptionType={corruptionType} @@ -106,8 +107,8 @@ class CorruptionRiskDashboard extends React.Component { styling={styling} /> ); - } else if (page === 'individual-indicator') { - const { individualIndicator } = this.state; + } else if (page === 'indicator') { + const [, individualIndicator] = route; return (
this.setState({ filtersState })} + onUpdate={currentFiltersState => this.setState({ currentFiltersState })} onApply={filtersToApply => this.setState({ filterBoxIndex: null, appliedFilters: filtersToApply, diff --git a/ui/oce/filters/inputs/type-ahead.jsx b/ui/oce/filters/inputs/type-ahead.jsx index a5a682b58..8fa514e00 100644 --- a/ui/oce/filters/inputs/type-ahead.jsx +++ b/ui/oce/filters/inputs/type-ahead.jsx @@ -79,7 +79,7 @@ class TypeAhead extends orgNamesFetching(translatable(Component)) { id: option.get('id'), name: option.get('name'), checked: false, - cb: selectedOption => this.select(selectedOption), + cb: () => this.select(option), }))} From 16c843a9b35e2d931309478494e5a9f906ef3103 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 4 Sep 2017 14:40:38 +0300 Subject: [PATCH 138/702] OCE-368 Set CRD Overview page as the default --- ui/oce/corruption-risk/index.jsx | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index bd677c1c0..5c237eb80 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -75,18 +75,7 @@ class CorruptionRiskDashboard extends React.Component { const { filters, years, months } = this.destructFilters(appliedFilters); const monthly = years.count() === 1; - if (page === 'overview') { - return (); - } else if (page === 'type') { + if (page === 'type') { const [, corruptionType] = route; const indicators = @@ -122,8 +111,20 @@ class CorruptionRiskDashboard extends React.Component { styling={styling} /> ); + } else { + return ( + + ); } - return null; } loginBox() { From 21d2aef6e01de93aab0a2815c6166919c2cf63b2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 4 Sep 2017 14:41:58 +0300 Subject: [PATCH 139/702] OCE-368 Fixed click event not being passed to the dashboard switcher --- ui/oce/corruption-risk/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 5c237eb80..4b85837e6 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -206,7 +206,7 @@ class CorruptionRiskDashboard extends React.Component {

this.toggleDashboardSwitcher()} + onClick={(e) => this.toggleDashboardSwitcher(e)} > Corruption Risk Dashboard From 29d7f70f8faee11091964a38502fc723b623e21a Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Sep 2017 15:36:43 +0300 Subject: [PATCH 140/702] OCE-366 Added the language switcher --- ui/assets/flags/es_ES.png | Bin 0 -> 55042 bytes ui/oce-standalone/style.less | 11 +++++ ui/oce/corruption-risk/index.jsx | 25 +++++++++- ui/oce/filters/tabs/index.jsx | 2 +- web/public/languages/es_ES.json | 76 +++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 ui/assets/flags/es_ES.png create mode 100644 web/public/languages/es_ES.json diff --git a/ui/assets/flags/es_ES.png b/ui/assets/flags/es_ES.png new file mode 100644 index 0000000000000000000000000000000000000000..70b4ec089bfa71f9ed6e1b383f3c4a7fec6f9b99 GIT binary patch literal 55042 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

atgRy%i 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 float:left; } + +.language-switcher{ + display: flex; + justify-content: center; + align-items: center; + img{ + width: 32px; + margin-left: .4em; + cursor: pointer; + } +} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 4b85837e6..6af93ebfe 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -65,6 +65,21 @@ class CorruptionRiskDashboard extends React.Component { })); } + languageSwitcher() { + const { TRANSLATIONS } = this.constructor; + if (Object.keys(TRANSLATIONS).length <= 1) return null; + return Object.keys(TRANSLATIONS).map(locale => + ({`${locale} this.setLocale(locale)} + key={locale} + />), + ); + } + + getPage() { const { translations, route, navigate } = this.props; const styling = this.constructor.STYLING || this.props.styling; @@ -220,7 +235,10 @@ class CorruptionRiskDashboard extends React.Component { }

-
+
+ {this.languageSwitcher()} +
+
{!disabledApiSecurity && this.loginBox()}
@@ -297,4 +315,9 @@ CorruptionRiskDashboard.propTypes = { navigate: PropTypes.func.isRequired }; +CorruptionRiskDashboard.TRANSLATIONS = { + en_US: require('../../../web/public/languages/en_US.json'), + es_ES: require('../../../web/public/languages/es_ES.json'), +} + export default CorruptionRiskDashboard; diff --git a/ui/oce/filters/tabs/index.jsx b/ui/oce/filters/tabs/index.jsx index 9083e33e9..8737b16e1 100644 --- a/ui/oce/filters/tabs/index.jsx +++ b/ui/oce/filters/tabs/index.jsx @@ -18,4 +18,4 @@ class Tab extends translatable(React.Component){ } } -export default Tab; \ No newline at end of file +export default Tab; diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json new file mode 100644 index 000000000..0a0c6147e --- /dev/null +++ b/web/public/languages/es_ES.json @@ -0,0 +1,76 @@ +{ + "crd:corruptionType:FRAUD:pageTitle": "Fraud Risk", + "crd:corruptionType:FRAUD:name": "Fraud", + "crd:corruptionType:FRAUD:introduction": "Fraud is “Any act or omission, including a misrepresentation, that knowingly or recklessly misleads, or attempts to mislead, a party to obtain a financial or other benefit or to avoid an obligation,\" according to the International Financial Institutions (IFI) Anti-Corruption Task Force Guidelines. Suppliers may engage in a variety of fraudulent activities in attempt to increase their chances at winning contracts or in demonstration of progress in implementation. Some of these fraud schemes include: false invoicing, false bidding, product substitution, fictitious contracting, and shadow bidding. The flags represented in each tile below indicate the possible existance of fraud in a procurement process. To learn more about each flag, click on the associated tile.", + "crd:corruptionType:FRAUD:crosstabTitle": "Fraud Risk Crosstab", + "crd:corruptionType:FRAUD:crosstab": "The table below shows the overlap between any two flags for fraud risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at an increased risk for fraud and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged and the corresponding flags.", + "crd:corruptionType:RIGGING:name": "Process Rigging", + "crd:corruptionType:RIGGING:pageTitle": "Risk of Process Rigging", + "crd:corruptionType:RIGGING:introduction": "\"Process rigging\" refers to an effort to rig the bidding, awarding, contracting or implementation process in favor of a particular player or entity to the exclusion of other legitimate participants. Whether intentionally or unintentionally, this form of risk implies the possible that a government official intervenes in the process to the benefit of one or more participants. Specific forms of process rigging may include: information withholding or misinformation, rigged specifications, unjustified direct (sole source) contracting, split purchasing, bid manipulation, favoring/excluding qualified bidders, change order abuse, and contracting abuse.", + "crd:corruptionType:RIGGING:crosstabTitle": "Process Rigging Crosstab", + "crd:corruptionType:RIGGING:crosstab": "The table below shows the overlap between any two process rigging flags. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at an increased risk for process rigging and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurements that have been flagged and the corresponding flags.", + "crd:corruptionType:COLLUSION:name": "Collusion", + "crd:corruptionType:COLLUSION:pageTitle": "Collusion Risk", + "crd:corruptionType:COLLUSION:introduction": "IFI Guidelines define a collusive practice as: “…an arrangement between two or more parties designed to achieve an improper purpose, including to influence improperly the actions of another party.” Here, we focus on collusive behavior between and among bidders; not between bidders and government officials (for this type of collusion, visit the process rigging page).", + "crd:corruptionType:COLLUSION:crosstabTitle": "Collusion Risk Crosstab", + "crd:corruptionType:COLLUSION:crosstab": "The table below shows the overlap between any two flags for collusion risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at a higher risk for collusion and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged, along with the corresponding flags.", + "crd:indicators:general:indicator": "Indicator:", + "crd:indicators:general:eligibility": "Eligibility:", + "crd:indicators:general:thresholds": "Thresholds:", + "crd:indicators:general:description": "Description:", + "crd:indicators:i002:name": "Low Bid Price", + "crd:indicators:i002:indicator": "Winning supplier provides a substantially lower bid price than competitors.", + "crd:indicators:i002:eligibility": "Award has been made; Procurement method is competitive.", + "crd:indicators:i002:thresholds": "Winning supplier's bid price must be 25% (or more) lower than next competitor.", + "crd:indicators:i002:description": "If the winning supplier's bid is substantially lower than those of its competitors, it could be a sign that the winner had access to information that other bidders did not. It could also signal that the winner is providing a reduced bid in order to win and anticipates a price markup after the award phase is complete. This indicator may be linked to fraud or process rigging.", + "crd:indicators:i003:name": "Only Winner Eligible", + "crd:indicators:i003:indicator": "Only the winning bidder was eligible to have received the contract for a tender when 2 or more bidders apply.", + "crd:indicators:i003:eligibility": "Award has been made.", + "crd:indicators:i003:thresholds": "2 or more suppliers must have submitted bids.", + "crd:indicators:i003:description": "When a competitive contracting process receives a single eligible bid among other ineligible bids, there is a possibility that the ineligible bids are fictitious, serving only the purpose of giving the appearance of a competitive process. It could also suggest that the bid has been rigged to favor one bidder or exclude others, including by withholding necessary information or misinforming some bidders. This could be a sign of either fraud or process rigging.", + "crd:indicators:i004:name": "Ineligible Direct Award", + "crd:indicators:i004:description": "Sole source award is awarded above the competitive threshold despite legal requirements", + "crd:indicators:i004:indicator": "A sole source contract is awarded above the competitive threshold.", + "crd:indicators:i004:eligibility": "Award has been made; Procurement method is sole source (direct).", + "crd:indicators:i004:thresholds": "Sole sorce thresholds used in accordance with local legislation.", + "crd:indicators:i007:name": "Single Bidder Only", + "crd:indicators:i007:indicator": "This awarded competitive tender features a single bid only.", + "crd:indicators:i007:eligibility": "Award has been made; Procurement method is competitive.", + "crd:indicators:i007:thresholds": "Only one supplier has offered a bid.", + "crd:indicators:i007:description": "The presence of a single bidder only in a competitive process could suggest that the bidding process has been altered to favor an individual bidder or to exclude others.", + "crd:indicators:i019:name": "Contract Negotiation Delay", + "crd:indicators:i019:indicator": "Long delays in contract negotiations or award. (over 60 days)", + "crd:indicators:i019:eligibility": "Award has been made.", + "crd:indicators:i019:thresholds": "Award period is longer than 60 days.", + "crd:indicators:i019:description": "A long delay in the negotiation of a contract or signing of an award could indicate that malfeasance is taking place in the form of bribery or other illicit behaviors.", + "crd:indicators:i038:name": "Short Bid Period", + "crd:indicators:i038:indicator": "Bid period is shorter than 3 days ", + "crd:indicators:i038:eligibility": "Bid period complete; Procurement method is competitive.", + "crd:indicators:i038:thresholds": "3-day bid period.", + "crd:indicators:i038:description": "A particularly short bidding period could indicate that the period for suppliers to submit their bids was reduced to favor certain suppliers over others.", + "crd:indicators:i077:name": "Multiple Contract Winner", + "crd:indicators:i077:description": "High number of competitive contract awards to one supplier within a calendar year", + "crd:indicators:i077:indicator": "A high number of contracts are awarded to one supplier within a one-year time period by a single procuring entity.", + "crd:indicators:i077:eligibility": "Award has been made.", + "crd:indicators:i077:thresholds": "Minimum of 2 awards made to 1 supplier by 1 procuring entity.", + "crd:indicators:i083:name": "Winner-Loser Pattern", + "crd:indicators:i083:description": "When X supplier wins, same set of tenderers loses (at least twice)", + "crd:indicators:i083:indicator": "When one supplier wins, the same tenderers always lose.", + "crd:indicators:i083:eligibility": "Award has been made; Procurement method is competitive; At least 2 suppliers bid.", + "crd:indicators:i083:thresholds": "1 supplier wins at least 2 awards against the same group of bidders.", + "crd:indicators:i085:name": "Whole % Bid Prices", + "crd:indicators:i085:indicator": "The difference between bid prices is an exact percentage (a whole number).", + "crd:indicators:i085:eligibility": "Award has been made; Procurement method is competitive; At least 2 bidders submitted bids.", + "crd:indicators:i085:thresholds": "Any two bids are an exact percentage apart (e.g. 2 bids differ by exactly 3%).", + "crd:indicators:i085:description": "It is extremely rare that two bids will be a whole-number percentage apart (eg. exactly 3% apart). The presence of two such bids in a contracting process could signal collusion, in that one bidder cast an invalid bid to buffer the bid proposal of another bidder, or fraud, in that a fake bid was submitted for a similar purpose.", + "crd:indicators:i171:name": "Bid Near Estimate", + "crd:indicators:i171:indicator": "Winning bid is within 1% of estimated price.", + "crd:indicators:i171:eligibility": "Award has been made; Procurement method is competitive.", + "crd:indicators:i171:thresholds": "None.", + "crd:indicators:i171:description": "In most instances, the estimated price of a good or work is 7%-12% higher than the actual competitive price. If a contract is awarded within 1% of the estimated price (or even higher than the estimated price) there is a possibility that the supplier was aware of the estimated price (by accessing inside information). The presence of shadow bidding, in which the winning supplier is bidding on behalf of a clandestine entity, is another possible cause.", + "crd:indicators:i180:name": "Multiple Direct Awards", + "crd:indicators:i180:indicator": "Supplier receives multiple sole-source/non-competitive contracts from a single procuring entity during a defined time period.", + "crd:indicators:i180:eligibility": "Award has been made; Procurement method is sole source (direct).", + "crd:indicators:i180:thresholds": "Supplier must receive at least 2 awards from a single procuring entity; Time period is 1 calendar year.", + "crd:indicators:i180:description": "This flag identifies instances in which a single supplier receives multiple direct contracts during a one-year period. This could be an indication of split bidding, where a larger award is split into two or more smaller awards to avoid breaching the competitive threshold, or of a preference for an individual supplier, which may provide kickbacks to individuals involved in the award process." +} From 94d093d004173b7c3d084c2bd9799879f728b8b0 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Sep 2017 16:13:53 +0300 Subject: [PATCH 141/702] OCE-366 Top level translation for CRD --- ui/oce/corruption-risk/index.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 6af93ebfe..dd0dd0ab1 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -38,6 +38,7 @@ class CorruptionRiskDashboard extends React.Component { width: 0, data: Map(), showLandingPopup: !localStorage.alreadyVisited, + locale: localStorage.oceLocale || 'en_US', }; localStorage.alreadyVisited = true; @@ -79,6 +80,10 @@ class CorruptionRiskDashboard extends React.Component { ); } + setLocale(locale) { + this.setState({ locale }); + localStorage.oceLocale = locale; + } getPage() { const { translations, route, navigate } = this.props; @@ -194,6 +199,12 @@ class CorruptionRiskDashboard extends React.Component { }); } + t(str){ + const { locale } = this.state; + const { TRANSLATIONS } = this.constructor; + return TRANSLATIONS[locale][str] || TRANSLATIONS['en_US'][str] || str; + } + render() { const { dashboardSwitcherOpen, corruptionType, filterBoxIndex, currentFiltersState, appliedFilters, data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, From e62c7e3d15b294f39c58e3cf09a85a5fb5f624d6 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Sep 2017 16:27:01 +0300 Subject: [PATCH 142/702] OCE-366 Translated root component --- ui/oce/corruption-risk/index.jsx | 43 ++++++++++++++------------------ web/public/languages/en_US.json | 5 +++- web/public/languages/es_ES.json | 11 +++++--- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index dd0dd0ab1..c4980abab 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -13,11 +13,7 @@ import { LOGIN_URL } from './constants'; // eslint-disable-next-line no-unused-vars import style from './style.less'; -const CORRUPTION_TYPES = { - FRAUD: 'Fraud', - RIGGING: 'Process rigging', - COLLUSION: 'Collusion', -}; +const CORRUPTION_TYPES = ['FRAUD', 'RIGGING', 'COLLUSION']; // eslint-disable-next-line no-undef class CorruptionRiskDashboard extends React.Component { @@ -234,7 +230,7 @@ class CorruptionRiskDashboard extends React.Component { className="corruption-dash-title" onClick={(e) => this.toggleDashboardSwitcher(e)} > - Corruption Risk Dashboard + {this.t('crd:title')} {dashboardSwitcherOpen && @@ -272,31 +268,30 @@ class CorruptionRiskDashboard extends React.Component {
@@ -149,15 +149,16 @@ class OverviewPage extends CRDPage{ render(){ const {corruptionType, topFlaggedContracts} = this.state; - const {filters, translations, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; + const {filters, t, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; + return (
-

Risk of Fraud, Collusion and Process Rigging Over Time

+

{this.t('crd:overview:title')}

this.setState({corruptionType})} - translations={translations} + t={t} data={corruptionType} years={years} monthly={monthly} @@ -169,11 +170,11 @@ class OverviewPage extends CRDPage{ />
-

The Procurement Processes with the Most Flags

+

Date: Sat, 9 Sep 2017 12:04:06 +0300 Subject: [PATCH 145/702] OCE-366 Corruption type page translations and overview page fixes --- ui/oce/corruption-risk/corruption-type.jsx | 89 +++++++++++----------- ui/oce/corruption-risk/overview-page.jsx | 4 +- web/public/languages/en_US.json | 9 ++- web/public/languages/es_ES.json | 3 +- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/ui/oce/corruption-risk/corruption-type.jsx b/ui/oce/corruption-risk/corruption-type.jsx index 557433b79..903d121ff 100644 --- a/ui/oce/corruption-risk/corruption-type.jsx +++ b/ui/oce/corruption-risk/corruption-type.jsx @@ -94,13 +94,13 @@ class IndicatorTile extends CustomPopupChart{

-
Procurements Flagged
+
{this.t('crd:corruptionType:indicatorTile:procurementsFlagged')}
{datum.get('totalTrue')}
-
Eligible Procurements
+
{this.t('crd:corruptionType:indicatorTile:eligibleProcurements')}
{datum.get('totalPrecondMet')}
-
% Eligible Procurements Flagged
+
{this.t('crd:corruptionType:indicatorTile:percentEligibleFlagged')}
{datum.get('percentTruePrecondMet').toFixed(2)} %
-
% Procurements Eligible
+
{this.t('crd:corruptionType:indicatorTile:percentProcurementsEligible')}
{datum.get('percentPrecondMet').toFixed(2)} %
@@ -152,42 +152,45 @@ class Crosstab extends Table{ const rowIndicatorDescription = this.t(`crd:indicators:${rowIndicatorID}:indicator`); return ( - {rowIndicatorName} - {rowData.getIn([rowIndicatorID, 'count'])} - {rowData.map((datum, indicatorID) => { - const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); - const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); - if(indicatorID == rowIndicatorID){ - return — - } else { - const percent = datum.get('percent'); - const count = datum.get('count'); - const color = Math.round(255 - 255 * (percent/100)) - const style = {backgroundColor: `rgb(${color}, 255, ${color})`} - return ( - - {percent && percent.toFixed(2)} % -
-
-
- {percent.toFixed(2)}% of procurements flagged for "{rowIndicatorName}" are also flagged for "{indicatorName}" -
-
-
-
-
-

{count} Procurements flagged with both;

-

{rowIndicatorName}: {rowIndicatorDescription}

-

and

-

{indicatorName}: {indicatorDescription}

-
-
-
-
- - ) - } - }).toArray()} + {rowIndicatorName} + {rowData.getIn([rowIndicatorID, 'count'])} + {rowData.map((datum, indicatorID) => { + const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); + const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); + if(indicatorID == rowIndicatorID){ + return — + } else { + const percent = datum.get('percent'); + const count = datum.get('count'); + const color = Math.round(255 - 255 * (percent/100)) + const style = {backgroundColor: `rgb(${color}, 255, ${color})`} + return ( + + {percent && percent.toFixed(2)} % +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
+
+
+

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+

{rowIndicatorName}: {rowIndicatorDescription}

+

{this.t('crd:corruptionType:crosstab:popup:and')}

+

{indicatorName}: {indicatorDescription}

+
+
+
+
+ + ) + } + }).toArray()} ) } @@ -241,7 +244,7 @@ class CorruptionType extends translatable(CRDPage){ render(){ const {indicators, onGotoIndicator, corruptionType, filters, years, monthly, months, - translations, width, styling} = this.props; + t, width, styling} = this.props; const {crosstab, indicatorTiles} = this.state; if(!indicators || !indicators.length) return null; @@ -262,7 +265,7 @@ class CorruptionType extends translatable(CRDPage){

{indicatorDescription}

this.updateIndicatorTile(indicator, data)} data={indicatorTiles[indicator]} @@ -294,7 +297,7 @@ class CorruptionType extends translatable(CRDPage){ indicators={indicators} data={crosstab} requestNewData={(_, data) => this.setState({crosstab: data})} - translations={translations} + t={t} />
diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 947b60c3b..9d9cc35f4 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -154,7 +154,7 @@ class OverviewPage extends CRDPage{ return (
-

{this.t('crd:overview:title')}

+

{this.t('crd:overview:overTimeChart:title')}

this.setState({corruptionType})} @@ -170,7 +170,7 @@ class OverviewPage extends CRDPage{ />
-

+

{this.t('crd:overview:topFlagged:title')}

Date: Sat, 9 Sep 2017 12:46:00 +0300 Subject: [PATCH 146/702] OCE-366 Individual indicator page translations --- ui/oce/corruption-risk/individual-indicator.jsx | 12 ++++++------ web/public/languages/en_US.json | 8 +++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx index 584e24405..6cbaeaaa5 100644 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ b/ui/oce/corruption-risk/individual-indicator.jsx @@ -103,13 +103,13 @@ class IndividualIndicatorChart extends CustomPopupChart{

-
Procurements Flagged
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged')}
{datum.get('totalTrue')}
-
Eligible Procurements
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements')}
{datum.get('totalPrecondMet')}
-
% Eligible Procurements Flagged
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:percentOfEligibleFlagged')}
{datum.get('percentTruePrecondMet').toFixed(2)} %
-
% Procurements Eligible
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:percentEligible')}
{datum.get('percentPrecondMet').toFixed(2)} %
@@ -167,7 +167,7 @@ class IndividualIndicatorPage extends translatable(CRDPage){

- Eligible Procurements and Flagged Procurements for {this.t(`crd:indicators:${indicator}:name`)} + {this.t('crd:indicatorPage:individualIndicatorChart:title').replace('$#$', this.t(`crd:indicators:${indicator}:name`))}

- List of Procurements Flagged for {this.t(`crd:indicators:${indicator}:name`)} + {this.t('crd:indicatorPage:projectTable:title').replace('$#$', this.t(`crd:indicators:${indicator}:name`))}

Date: Mon, 11 Sep 2017 14:22:25 +0300 Subject: [PATCH 147/702] OCE-366 Indicator name and thresholds Spanish translations --- web/public/languages/es_ES.json | 41 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index de4186fca..e9148cd70 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -18,61 +18,60 @@ "crd:indicators:general:eligibility": "Eligibility:", "crd:indicators:general:thresholds": "Thresholds:", "crd:indicators:general:description": "Description:", - "crd:indicators:i002:name": "Low Bid Price", "crd:indicators:i002:indicator": "Winning supplier provides a substantially lower bid price than competitors.", "crd:indicators:i002:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i002:thresholds": "Winning supplier's bid price must be 25% (or more) lower than next competitor.", "crd:indicators:i002:description": "If the winning supplier's bid is substantially lower than those of its competitors, it could be a sign that the winner had access to information that other bidders did not. It could also signal that the winner is providing a reduced bid in order to win and anticipates a price markup after the award phase is complete. This indicator may be linked to fraud or process rigging.", - "crd:indicators:i003:name": "Only Winner Eligible", "crd:indicators:i003:indicator": "Only the winning bidder was eligible to have received the contract for a tender when 2 or more bidders apply.", "crd:indicators:i003:eligibility": "Award has been made.", - "crd:indicators:i003:thresholds": "2 or more suppliers must have submitted bids.", "crd:indicators:i003:description": "When a competitive contracting process receives a single eligible bid among other ineligible bids, there is a possibility that the ineligible bids are fictitious, serving only the purpose of giving the appearance of a competitive process. It could also suggest that the bid has been rigged to favor one bidder or exclude others, including by withholding necessary information or misinforming some bidders. This could be a sign of either fraud or process rigging.", - "crd:indicators:i004:name": "Ineligible Direct Award", "crd:indicators:i004:description": "Sole source award is awarded above the competitive threshold despite legal requirements", "crd:indicators:i004:indicator": "A sole source contract is awarded above the competitive threshold.", "crd:indicators:i004:eligibility": "Award has been made; Procurement method is sole source (direct).", - "crd:indicators:i004:thresholds": "Sole sorce thresholds used in accordance with local legislation.", - "crd:indicators:i007:name": "Single Bidder Only", "crd:indicators:i007:indicator": "This awarded competitive tender features a single bid only.", "crd:indicators:i007:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i007:thresholds": "Only one supplier has offered a bid.", "crd:indicators:i007:description": "The presence of a single bidder only in a competitive process could suggest that the bidding process has been altered to favor an individual bidder or to exclude others.", - "crd:indicators:i019:name": "Contract Negotiation Delay", "crd:indicators:i019:indicator": "Long delays in contract negotiations or award. (over 60 days)", "crd:indicators:i019:eligibility": "Award has been made.", - "crd:indicators:i019:thresholds": "Award period is longer than 60 days.", "crd:indicators:i019:description": "A long delay in the negotiation of a contract or signing of an award could indicate that malfeasance is taking place in the form of bribery or other illicit behaviors.", - "crd:indicators:i038:name": "Short Bid Period", "crd:indicators:i038:indicator": "Bid period is shorter than 3 days ", "crd:indicators:i038:eligibility": "Bid period complete; Procurement method is competitive.", - "crd:indicators:i038:thresholds": "3-day bid period.", "crd:indicators:i038:description": "A particularly short bidding period could indicate that the period for suppliers to submit their bids was reduced to favor certain suppliers over others.", - "crd:indicators:i077:name": "Multiple Contract Winner", "crd:indicators:i077:description": "High number of competitive contract awards to one supplier within a calendar year", "crd:indicators:i077:indicator": "A high number of contracts are awarded to one supplier within a one-year time period by a single procuring entity.", "crd:indicators:i077:eligibility": "Award has been made.", - "crd:indicators:i077:thresholds": "Minimum of 2 awards made to 1 supplier by 1 procuring entity.", + "crd:indicators:i002:name": "Precio de oferta baja", + "crd:indicators:i002:thresholds": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", + "crd:indicators:i003:name": "Sólo ganador elegible", + "crd:indicators:i003:thresholds": "Sólo el licitador ganador era elegible para obtener el contrato de licitación cuando 2+ licitadores aplican", + "crd:indicators:i004:name": "Adjudicación inelegible directa", + "crd:indicators:i004:thresholds": "La adjudicación de única fuente se otorga por encima del umbral competitivo a pesar de los requisitos legales", + "crd:indicators:i007:name": "Licitador único", + "crd:indicators:i007:thresholds": "La licitación competitiva otorgada solo cuenta con una oferta", + "crd:indicators:i019:name": "Retrasos en la negociación del contrato", + "crd:indicators:i019:thresholds": "Largos retrasos en las negociaciones del contrato o adjudicación (más de 60 días)", + "crd:indicators:i038:name": "Periodo de oferta corto", + "crd:indicators:i038:thresholds": "El periodo de oferta es menor a 3 días", + "crd:indicators:i077:name": "Ganador de contratos múltiples", + "crd:indicators:i077:thresholds": "Un alto número de contratos competitivos se otorgan a un solo proveedor durante un año natural", "crd:indicators:i083:name": "Winner-Loser Pattern", "crd:indicators:i083:description": "When X supplier wins, same set of tenderers loses (at least twice)", "crd:indicators:i083:indicator": "When one supplier wins, the same tenderers always lose.", "crd:indicators:i083:eligibility": "Award has been made; Procurement method is competitive; At least 2 suppliers bid.", "crd:indicators:i083:thresholds": "1 supplier wins at least 2 awards against the same group of bidders.", - "crd:indicators:i085:name": "Whole % Bid Prices", "crd:indicators:i085:indicator": "The difference between bid prices is an exact percentage (a whole number).", "crd:indicators:i085:eligibility": "Award has been made; Procurement method is competitive; At least 2 bidders submitted bids.", - "crd:indicators:i085:thresholds": "Any two bids are an exact percentage apart (e.g. 2 bids differ by exactly 3%).", "crd:indicators:i085:description": "It is extremely rare that two bids will be a whole-number percentage apart (eg. exactly 3% apart). The presence of two such bids in a contracting process could signal collusion, in that one bidder cast an invalid bid to buffer the bid proposal of another bidder, or fraud, in that a fake bid was submitted for a similar purpose.", - "crd:indicators:i171:name": "Bid Near Estimate", "crd:indicators:i171:indicator": "Winning bid is within 1% of estimated price.", "crd:indicators:i171:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i171:thresholds": "None.", "crd:indicators:i171:description": "In most instances, the estimated price of a good or work is 7%-12% higher than the actual competitive price. If a contract is awarded within 1% of the estimated price (or even higher than the estimated price) there is a possibility that the supplier was aware of the estimated price (by accessing inside information). The presence of shadow bidding, in which the winning supplier is bidding on behalf of a clandestine entity, is another possible cause.", - "crd:indicators:i180:name": "Multiple Direct Awards", "crd:indicators:i180:indicator": "Supplier receives multiple sole-source/non-competitive contracts from a single procuring entity during a defined time period.", "crd:indicators:i180:eligibility": "Award has been made; Procurement method is sole source (direct).", - "crd:indicators:i180:thresholds": "Supplier must receive at least 2 awards from a single procuring entity; Time period is 1 calendar year.", - "crd:indicators:i180:description": "This flag identifies instances in which a single supplier receives multiple direct contracts during a one-year period. This could be an indication of split bidding, where a larger award is split into two or more smaller awards to avoid breaching the competitive threshold, or of a preference for an individual supplier, which may provide kickbacks to individuals involved in the award process.", + "crd:indicators:i085:name": "% de precios de ofertas", + "crd:indicators:i085:thresholds": "La diferencia entre los precios de las ofertas es un porcentaje exacto (numero entero)", + "crd:indicators:i171:name": "Aproximación del estimado de la oferta", + "crd:indicators:i171:thresholds": "La oferta ganadora está dentro del 1% del precio estimado", + "crd:indicators:i180:name": "Adjudicacione directas múltiples", + "crd:indicators:i180:thresholds": "El proveedor recibe contratos de una sola fuente/no competitvos de una sola entidad contratante durante un año", "crd:title": "Tablero de riesgo de corrupción", "crd:overview": "Resumen del Riesgo de corrupción", "crd:description": "Los tableros de Riesgo de corrupción emplean un enfoque de advertencia para que los usuarios entiendan la potencial presencia de fraude, colusión o manipulación en el proceso en las contrataciones públicas. Mientras que las advertencias pueden indicar la presencia de corrupción, también pueden atribuirse a problemas de calidad de los datos, violación de leyes o de buenas prácticas internacionales, o otros problemas.", From 2ddb7379b5dffed027187ff98a961f59e90484b9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 14:29:47 +0300 Subject: [PATCH 148/702] OCE-366 Translated general indicator text --- web/public/languages/es_ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index e9148cd70..877ce5ea1 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -14,10 +14,6 @@ "crd:corruptionType:COLLUSION:introduction": "IFI Guidelines define a collusive practice as: “…an arrangement between two or more parties designed to achieve an improper purpose, including to influence improperly the actions of another party.” Here, we focus on collusive behavior between and among bidders; not between bidders and government officials (for this type of collusion, visit the process rigging page).", "crd:corruptionType:COLLUSION:crosstabTitle": "Collusion Risk Crosstab", "crd:corruptionType:COLLUSION:crosstab": "The table below shows the overlap between any two flags for collusion risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at a higher risk for collusion and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged, along with the corresponding flags.", - "crd:indicators:general:indicator": "Indicator:", - "crd:indicators:general:eligibility": "Eligibility:", - "crd:indicators:general:thresholds": "Thresholds:", - "crd:indicators:general:description": "Description:", "crd:indicators:i002:indicator": "Winning supplier provides a substantially lower bid price than competitors.", "crd:indicators:i002:eligibility": "Award has been made; Procurement method is competitive.", "crd:indicators:i002:description": "If the winning supplier's bid is substantially lower than those of its competitors, it could be a sign that the winner had access to information that other bidders did not. It could also signal that the winner is providing a reduced bid in order to win and anticipates a price markup after the award phase is complete. This indicator may be linked to fraud or process rigging.", @@ -39,6 +35,10 @@ "crd:indicators:i077:description": "High number of competitive contract awards to one supplier within a calendar year", "crd:indicators:i077:indicator": "A high number of contracts are awarded to one supplier within a one-year time period by a single procuring entity.", "crd:indicators:i077:eligibility": "Award has been made.", + "crd:indicators:general:indicator": "Indicador:", + "crd:indicators:general:eligibility": "Elegibilidad:", + "crd:indicators:general:thresholds": "Umbrales:", + "crd:indicators:general:description": "Descripción:", "crd:indicators:i002:name": "Precio de oferta baja", "crd:indicators:i002:thresholds": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", "crd:indicators:i003:name": "Sólo ganador elegible", From c8207f9084676ee75863d565880782f00340d484 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 14:47:13 +0300 Subject: [PATCH 149/702] OCE-366 Updated indicators --- web/public/languages/es_ES.json | 79 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 877ce5ea1..a968bb65a 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -14,64 +14,65 @@ "crd:corruptionType:COLLUSION:introduction": "IFI Guidelines define a collusive practice as: “…an arrangement between two or more parties designed to achieve an improper purpose, including to influence improperly the actions of another party.” Here, we focus on collusive behavior between and among bidders; not between bidders and government officials (for this type of collusion, visit the process rigging page).", "crd:corruptionType:COLLUSION:crosstabTitle": "Collusion Risk Crosstab", "crd:corruptionType:COLLUSION:crosstab": "The table below shows the overlap between any two flags for collusion risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at a higher risk for collusion and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged, along with the corresponding flags.", - "crd:indicators:i002:indicator": "Winning supplier provides a substantially lower bid price than competitors.", - "crd:indicators:i002:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i002:description": "If the winning supplier's bid is substantially lower than those of its competitors, it could be a sign that the winner had access to information that other bidders did not. It could also signal that the winner is providing a reduced bid in order to win and anticipates a price markup after the award phase is complete. This indicator may be linked to fraud or process rigging.", - "crd:indicators:i003:indicator": "Only the winning bidder was eligible to have received the contract for a tender when 2 or more bidders apply.", - "crd:indicators:i003:eligibility": "Award has been made.", - "crd:indicators:i003:description": "When a competitive contracting process receives a single eligible bid among other ineligible bids, there is a possibility that the ineligible bids are fictitious, serving only the purpose of giving the appearance of a competitive process. It could also suggest that the bid has been rigged to favor one bidder or exclude others, including by withholding necessary information or misinforming some bidders. This could be a sign of either fraud or process rigging.", - "crd:indicators:i004:description": "Sole source award is awarded above the competitive threshold despite legal requirements", - "crd:indicators:i004:indicator": "A sole source contract is awarded above the competitive threshold.", - "crd:indicators:i004:eligibility": "Award has been made; Procurement method is sole source (direct).", - "crd:indicators:i007:indicator": "This awarded competitive tender features a single bid only.", - "crd:indicators:i007:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i007:description": "The presence of a single bidder only in a competitive process could suggest that the bidding process has been altered to favor an individual bidder or to exclude others.", - "crd:indicators:i019:indicator": "Long delays in contract negotiations or award. (over 60 days)", - "crd:indicators:i019:eligibility": "Award has been made.", - "crd:indicators:i019:description": "A long delay in the negotiation of a contract or signing of an award could indicate that malfeasance is taking place in the form of bribery or other illicit behaviors.", - "crd:indicators:i038:indicator": "Bid period is shorter than 3 days ", - "crd:indicators:i038:eligibility": "Bid period complete; Procurement method is competitive.", - "crd:indicators:i038:description": "A particularly short bidding period could indicate that the period for suppliers to submit their bids was reduced to favor certain suppliers over others.", - "crd:indicators:i077:description": "High number of competitive contract awards to one supplier within a calendar year", - "crd:indicators:i077:indicator": "A high number of contracts are awarded to one supplier within a one-year time period by a single procuring entity.", - "crd:indicators:i077:eligibility": "Award has been made.", "crd:indicators:general:indicator": "Indicador:", "crd:indicators:general:eligibility": "Elegibilidad:", "crd:indicators:general:thresholds": "Umbrales:", "crd:indicators:general:description": "Descripción:", "crd:indicators:i002:name": "Precio de oferta baja", - "crd:indicators:i002:thresholds": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", + "crd:indicators:i002:indicator": "El proveedor ganador proporciona un precio de oferta sustancialmente menor (25% o más) que los competidores.", + "crd:indicators:i002:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es competitivo.", + "crd:indicators:i002:thresholds": " El precio de oferta del proveedor ganador debe ser 25% (o más) menor que el siguiente competidor.", + "crd:indicators:i002:description": "Si la oferta del proveedor ganador es sustancialmente inferior a la de sus competidores, podría ser una señal de que el ganador tuvo acceso a la información que otros compradores no. También podría indicar que el ganador está ofreciendo una oferta reducida con el fin de ganar y anticipa una marcación de precios después de la fase de adjudicación se ha completado. Este indicador puede estar vinculado a fraude o manipulación del proceso.", "crd:indicators:i003:name": "Sólo ganador elegible", - "crd:indicators:i003:thresholds": "Sólo el licitador ganador era elegible para obtener el contrato de licitación cuando 2+ licitadores aplican", + "crd:indicators:i003:indicator": "Sólo el licitador ganador fue elegible para recibir el contrato de una licitación cuando 2 o más licitadores solicitan.", + "crd:indicators:i003:eligibility": "Se ha otorgado una adjudicación.", + "crd:indicators:i003:thresholds": "2 o más proveedores deben haber presentado ofertas.", + "crd:indicators:i003:description": "Cuando un proceso competitivo de contratación recibe una sola oferta elegible entre otras ofertas no elegibles, existe la posibilidad de que las ofertas inelegibles sean ficticias, sirviendo sólo el propósito de dar la apariencia de un proceso competitivo. También podría sugerir que la oferta ha sido manipulada para favorecer a un licitador o excluir a otros, incluyendo la retención de información necesaria o la información errónea de algunos licitadores. Esto podría ser una señal de fraude o manipulación del proceso.", "crd:indicators:i004:name": "Adjudicación inelegible directa", - "crd:indicators:i004:thresholds": "La adjudicación de única fuente se otorga por encima del umbral competitivo a pesar de los requisitos legales", + "crd:indicators:i004:indicator": "Se otorga un contrato de única fuente por encima del umbral competitivo.", + "crd:indicators:i004:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es la única fuente (directa).", + "crd:indicators:i004:thresholds": "Umbrales de origen único utilizados de acuerdo con la legislación local.", + "crd:indicators:i004:description": "Esta advertencia de manipulación del proceso sugiere que las reglas de licitación competitivas han sido ignoradas para permitir que un proceso de licitación directa avance cuando se requiere un método competitivo.", "crd:indicators:i007:name": "Licitador único", - "crd:indicators:i007:thresholds": "La licitación competitiva otorgada solo cuenta con una oferta", + "crd:indicators:i007:indicator": "Esta licitación adjudicada presenta una sola oferta.", + "crd:indicators:i007:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es competitivo.", + "crd:indicators:i007:thresholds": "Sólo un proveedor ha ofrecido una oferta.", + "crd:indicators:i007:description": "La presencia de un único licitante en un proceso competitivo podría sugerir que el proceso de licitación se ha alterado para favorecer a un licitante individual o excluir a otros.", "crd:indicators:i019:name": "Retrasos en la negociación del contrato", - "crd:indicators:i019:thresholds": "Largos retrasos en las negociaciones del contrato o adjudicación (más de 60 días)", + "crd:indicators:i019:indicator": "Largos retrasos en la negociación o adjudicación de contratos. (Más de 60 días)", + "crd:indicators:i019:eligibility": "Se ha otorgado una adjudicación.", + "crd:indicators:i019:thresholds": "El período de concesión es superior a 60 días.", + "crd:indicators:i019:description": "Un largo retraso en la negociación de un contrato o la firma de una adjudicación podría indicar que la malversación se lleva a cabo en forma de soborno u otros comportamientos ilícitos.", "crd:indicators:i038:name": "Periodo de oferta corto", - "crd:indicators:i038:thresholds": "El periodo de oferta es menor a 3 días", + "crd:indicators:i038:indicator": "El período de licitación es menor a 3 días.", + "crd:indicators:i038:eligibility": "Periodo de oferta compelto; El método de adquisición es competitivo.", + "crd:indicators:i038:thresholds": "Período de 3 días de aofert.", + "crd:indicators:i038:description": "Un período de licitación particularmente corto podría indicar que el plazo para que los proveedores presentaran sus ofertas se redujo para favorecer a ciertos proveedores en detrimento de otros.", "crd:indicators:i077:name": "Ganador de contratos múltiples", - "crd:indicators:i077:thresholds": "Un alto número de contratos competitivos se otorgan a un solo proveedor durante un año natural", + "crd:indicators:i077:indicator": "Se otorga un alto número de contratos competitivos a un proveedor dentro de un período de un año por una sola entidad adjudicadora.", + "crd:indicators:i077:eligibility": "Se ha otorgado una adjudicación. El método de adquisición es competitivo.", + "crd:indicators:i077:thresholds": "Mínimo de 2 adjudicaciones otorgadas a 1 proveedor por 1 entidad contratante.", + "crd:indicators:i077:description": "Cuando un proveedor único gana múltiples contratos competitivos de una entidad contratante dentro de un período de tiempo condensado, puede indicar un esfuerzo para favorecer a un proveedor individual, lo que puede resultar en sobornos a las personas involucradas en el proceso de evaluación o adjudicación.", "crd:indicators:i083:name": "Winner-Loser Pattern", "crd:indicators:i083:description": "When X supplier wins, same set of tenderers loses (at least twice)", "crd:indicators:i083:indicator": "When one supplier wins, the same tenderers always lose.", "crd:indicators:i083:eligibility": "Award has been made; Procurement method is competitive; At least 2 suppliers bid.", "crd:indicators:i083:thresholds": "1 supplier wins at least 2 awards against the same group of bidders.", - "crd:indicators:i085:indicator": "The difference between bid prices is an exact percentage (a whole number).", - "crd:indicators:i085:eligibility": "Award has been made; Procurement method is competitive; At least 2 bidders submitted bids.", - "crd:indicators:i085:description": "It is extremely rare that two bids will be a whole-number percentage apart (eg. exactly 3% apart). The presence of two such bids in a contracting process could signal collusion, in that one bidder cast an invalid bid to buffer the bid proposal of another bidder, or fraud, in that a fake bid was submitted for a similar purpose.", - "crd:indicators:i171:indicator": "Winning bid is within 1% of estimated price.", - "crd:indicators:i171:eligibility": "Award has been made; Procurement method is competitive.", - "crd:indicators:i171:description": "In most instances, the estimated price of a good or work is 7%-12% higher than the actual competitive price. If a contract is awarded within 1% of the estimated price (or even higher than the estimated price) there is a possibility that the supplier was aware of the estimated price (by accessing inside information). The presence of shadow bidding, in which the winning supplier is bidding on behalf of a clandestine entity, is another possible cause.", - "crd:indicators:i180:indicator": "Supplier receives multiple sole-source/non-competitive contracts from a single procuring entity during a defined time period.", - "crd:indicators:i180:eligibility": "Award has been made; Procurement method is sole source (direct).", "crd:indicators:i085:name": "% de precios de ofertas", - "crd:indicators:i085:thresholds": "La diferencia entre los precios de las ofertas es un porcentaje exacto (numero entero)", + "crd:indicators:i085:indicator": "La diferencia entre los precios de oferta es un porcentaje exacto (un número entero).", + "crd:indicators:i085:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es competitivo; Por lo menos 2 licitadores presentaron ofertas.", + "crd:indicators:i085:thresholds": "Cualquiera de las dos ofertas son un porcentaje exacto aparte (por ejemplo, 2 ofertas difieren exactamente en un 3%).", + "crd:indicators:i085:description": "Es extremadamente raro que dos ofertas sean un porcentaje de número entero aparte (por ejemplo, exactamente 3% aparte). La presencia de dos ofertas iguales en un proceso de contratación podría señalar colusión, en ese caso un licitador lanzó una oferta inválida para amortiguar la propuesta de oferta de otro licitador, o fraude, en que una oferta falsa fue presentada para un propósito similar.", "crd:indicators:i171:name": "Aproximación del estimado de la oferta", - "crd:indicators:i171:thresholds": "La oferta ganadora está dentro del 1% del precio estimado", + "crd:indicators:i171:indicator": "La oferta ganadora está dentro del 1% del precio estimado.", + "crd:indicators:i171:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es competitivo.", + "crd:indicators:i171:thresholds": "Ninguno.", + "crd:indicators:i171:description": "En la mayoría de los casos, el precio estimado de un bien o trabajo es 7% -12% más alto que el precio competitivo real. Si un contrato se adjudica dentro del 1% del precio estimado (o incluso más alto que el precio estimado) existe la posibilidad de que el proveedor conozca el precio estimado (accediendo a información privilegiada). Otra causa posible es la presencia de licitación sombra, en la que el proveedor ganador está haciendo una oferta en nombre de una entidad clandestina.", "crd:indicators:i180:name": "Adjudicacione directas múltiples", - "crd:indicators:i180:thresholds": "El proveedor recibe contratos de una sola fuente/no competitvos de una sola entidad contratante durante un año", + "crd:indicators:i180:indicator": "El proveedor recibe múltiples contratos de única fuente/ no competitivos de una sola entidad de contratación durante un tiempo definido.", + "crd:indicators:i180:eligibility": "La adjudicación se ha hecho, el método de contratación es única fuente (directa)", + "crd:indicators:i180:thresholds": "Proveedores deben recibir almenos 2 adjudicaciones de una sola identidad de contratación, el periodo de tiempo es 1 año.", + "crd:indicators:i180:description": "Esta advertencia identifica casos en los que un solo proveedor recibe múltiples contratos directos durante el periodo de un año. Esto puede ser un indicio de licitación dividida, cuando una adjudicación grande ha sido dividida en dos o más adjudicaciones pequeñas para evitar violar el umbral competitivo, o preferencia por un proveedor individual, lo cual puede resultar en sobornos a las personas involucradas en el proceso de adjudicación.", "crd:title": "Tablero de riesgo de corrupción", "crd:overview": "Resumen del Riesgo de corrupción", "crd:description": "Los tableros de Riesgo de corrupción emplean un enfoque de advertencia para que los usuarios entiendan la potencial presencia de fraude, colusión o manipulación en el proceso en las contrataciones públicas. Mientras que las advertencias pueden indicar la presencia de corrupción, también pueden atribuirse a problemas de calidad de los datos, violación de leyes o de buenas prácticas internacionales, o otros problemas.", From cde21a82e3605aac03f6980f86f8b5fd14ef10ac Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 15:20:14 +0300 Subject: [PATCH 150/702] OCE-366 Added a `shortDescription` indicator prop, displaying that on corruption type pages instead of `indicator` --- ui/oce/corruption-risk/corruption-type.jsx | 2 +- web/public/languages/en_US.json | 12 +++++++++++- web/public/languages/es_ES.json | 13 ++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/corruption-type.jsx b/ui/oce/corruption-risk/corruption-type.jsx index 903d121ff..915c52c18 100644 --- a/ui/oce/corruption-risk/corruption-type.jsx +++ b/ui/oce/corruption-risk/corruption-type.jsx @@ -257,7 +257,7 @@ class CorruptionType extends translatable(CRDPage){
{row.map(indicator => { const indicatorName = this.t(`crd:indicators:${indicator}:name`); - const indicatorDescription = this.t(`crd:indicators:${indicator}:indicator`); + const indicatorDescription = this.t(`crd:indicators:${indicator}:shortDescription`); return (
onGotoIndicator(indicator)}>
diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index b690cb1d0..29012a510 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -235,5 +235,15 @@ "crd:indicatorPage:individualIndicatorChart:popup:percentOfEligibleFlagged": "% Eligible Procurements Flagged", "crd:indicatorPage:individualIndicatorChart:popup:percentEligible": "% Procurements Eligible", "crd:indicatorPage:individualIndicatorChart:title": "Eligible Procurements and Flagged Procurements for $#$", - "crd:indicatorPage:projectTable:title": "List of Procurements Flagged for $#$" + "crd:indicatorPage:projectTable:title": "List of Procurements Flagged for $#$", + "crd:indicators:i002:shortDescription": "Winning supplier provides a substantially lower bid price than competitors", + "crd:indicators:i003:shortDescription": "Only the winning bidder was eligible to have received the contract for a tender when 2+ bidders apply", + "crd:indicators:i004:shortDescription": "Sole source award is awarded above the competitive threshold despite legal requirements", + "crd:indicators:i007:shortDescription": "This awarded competitive tender only featured a single bid ", + "crd:indicators:i019:shortDescription": "Long delays in contract negotiations or award (over 60 days)", + "crd:indicators:i038:shortDescription": "Bid period is shorter than 3 days", + "crd:indicators:i077:shortDescription": "High number of competitive contract awards to one supplier within a calendar year", + "crd:indicators:i085:shortDescription": "Difference between bid prices is an exact percentage (whole number)", + "crd:indicators:i171:shortDescription": "Winning bid is within 1% of estimated price", + "crd:indicators:i180:shortDescription": "Supplier receives multiple single-source/non-competitive contracts from a single procuring entity during a calendar year" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index a968bb65a..10dd77da2 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -76,5 +76,16 @@ "crd:title": "Tablero de riesgo de corrupción", "crd:overview": "Resumen del Riesgo de corrupción", "crd:description": "Los tableros de Riesgo de corrupción emplean un enfoque de advertencia para que los usuarios entiendan la potencial presencia de fraude, colusión o manipulación en el proceso en las contrataciones públicas. Mientras que las advertencias pueden indicar la presencia de corrupción, también pueden atribuirse a problemas de calidad de los datos, violación de leyes o de buenas prácticas internacionales, o otros problemas.", - "crd:corruptionType:crosstab:popup:and": "y" + "crd:corruptionType:crosstab:popup:and": "y", + "crd:indicators:i002:shortDescription": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", + "crd:indicators:i003:shortDescription": "Sólo el licitador ganador era elegible para obtener el contrato de licitación cuando 2+ licitadores aplican", + "crd:indicators:i004:shortDescription": "La adjudicación de única fuente se otorga por encima del umbral competitivo a pesar de los requisitos legales", + "crd:indicators:i007:shortDescription": "La licitación competitiva otorgada solo cuenta con una oferta", + "crd:indicators:i019:shortDescription": "Largos retrasos en las negociaciones del contrato o adjudicación (más de 60 días)", + "crd:indicators:i038:shortDescription": "El periodo de oferta es menor a 3 días", + "crd:indicators:i077:shortDescription": "Un alto número de contratos competitivos se otorgan a un solo proveedor durante un año natural", + "crd:indicators:i085:shortDescription": "La diferencia entre los precios de las ofertas es un porcentaje exacto (numero entero)", + "crd:indicators:i171:shortDescription": "La oferta ganadora está dentro del 1% del precio estimado", + "crd:indicators:i180:shortDescription": "El proveedor recibe contratos de una sola fuente/no competitvos de una sola entidad contratante durante un año" + } From 41d70385971d714399fe768f916664bbe814d315 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 16:54:11 +0300 Subject: [PATCH 151/702] OCE-366 Landing popup translations --- ui/oce/corruption-risk/index.jsx | 1 + ui/oce/corruption-risk/landing-popup.jsx | 48 ++++++++++++------------ web/public/languages/en_US.json | 14 ++++++- web/public/languages/es_ES.json | 15 +++++++- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 63434a0cd..71b12e23d 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -222,6 +222,7 @@ class CorruptionRiskDashboard extends React.Component { this.setState({ showLandingPopup: false })} + t={t} /> }
diff --git a/ui/oce/corruption-risk/landing-popup.jsx b/ui/oce/corruption-risk/landing-popup.jsx index b8dea860e..c2339af1f 100644 --- a/ui/oce/corruption-risk/landing-popup.jsx +++ b/ui/oce/corruption-risk/landing-popup.jsx @@ -1,7 +1,8 @@ -import {LOGIN_URL} from "./constants"; -import {debounce} from "../tools"; +import { LOGIN_URL } from "./constants"; +import { debounce } from "../tools"; +import translatable from '../translatable'; -class LandingPopup extends React.Component{ +class LandingPopup extends translatable(React.Component) { constructor(...args){ super(...args); this.state = { @@ -47,7 +48,7 @@ class LandingPopup extends React.Component{
-

Corruption Risk Dashboard

+

{this.t('crd:title')}

@@ -55,11 +56,12 @@ class LandingPopup extends React.Component{
- The Corruption Risk Dashboard (CRD) is an open source tool that aims to help users understand the potential presence of corruption in public contracting. Through a red flagging approach (highlighting procurement activities that have certain risk factors), this prototype explores corruption risk through the visualization of 10 indicators that are mapped to three different forms of corruption: fraud, collusion, and process rigging. Users are free to explore the data, which has been converted to the Open Contracting Data Standard (OCDS), using a variety of filters. A crosstab table enables users to explore the overlap between any two indicators that are mapped to the same risk type. -
-
- The methodological approach that informs the CRD, which was built by Development Gateway (DG) with the collaboration and support of the Open Contracting Partnership (OCP), is presented in a co-authored research paper. Explanations of the principal concepts and indicators are available within the Dashboard. + {this.t('crd:landing:introduction:1')}
+

@@ -69,19 +71,19 @@ class LandingPopup extends React.Component{
- Intended Use + {this.t('crd:landing:intendedUse:title')}
- The CRD was designed with the primary objective of supporting two specific use cases: + {this.t('crd:landing:intendedUse:introduction')}
- 1. To facilitate the efforts of procurement regulators and informed observers to identify individual contracts that may warrant investigation, and; + {this.t('crd:landing:intendedUse:introduction:1')}
- 2. To aid these individuals to monitor corruption risk in procurement markets over time. + {this.t('crd:landing:intendedUse:introduction:2')}
@@ -91,32 +93,32 @@ class LandingPopup extends React.Component{
-
- Distinguishing between “Corruption” and “Corruption Risk” -
+
- While flags associated with a procurement process may indicate the possibility that corruption has taken place, they may also be attributable to poor data quality, systemic weaknesses, or practices that may be appropriate when specifically authorized by a procurement authority or regulatory institution. For this reason, this tool is best viewed as a mechanism for identifying the “risk” of corruption, rather than “corruption” itself. + {this.t('crd:landing:distinguishing:1')}
- Furthermore, in some instances, a single flag - such as for the awarding of two contracts to the same supplier by a single procuring entity - may not show any evidence of wrongdoing, though the confluence of multiple indicators suggests greater cause of concern. + {this.t('crd:landing:distinguishing:2')}

-
Your feedback is welcome
+
{this.t('crd:landing:feedback:title')}
-
- We welcome your feedback on this prototype. Please contact Andrew Mandelbaum at DG if you have any questions or suggestions:  - amandelbaum@developmentgateway.org -
+
diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 29012a510..7dd93609f 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -245,5 +245,17 @@ "crd:indicators:i077:shortDescription": "High number of competitive contract awards to one supplier within a calendar year", "crd:indicators:i085:shortDescription": "Difference between bid prices is an exact percentage (whole number)", "crd:indicators:i171:shortDescription": "Winning bid is within 1% of estimated price", - "crd:indicators:i180:shortDescription": "Supplier receives multiple single-source/non-competitive contracts from a single procuring entity during a calendar year" + "crd:indicators:i180:shortDescription": "Supplier receives multiple single-source/non-competitive contracts from a single procuring entity during a calendar year", + "crd:landing:introduction:1": "The Corruption Risk Dashboard (CRD) is an open source tool that aims to help users understand the potential presence of corruption in public contracting. Through a red flagging approach (highlighting procurement activities that have certain risk factors), this prototype explores corruption risk through the visualization of 10 indicators that are mapped to three different forms of corruption: fraud, collusion, and process rigging. Users are free to explore the data, which has been converted to the Open Contracting Data Standard (OCDS), using a variety of filters. A crosstab table enables users to explore the overlap between any two indicators that are mapped to the same risk type.", + "crd:landing:introduction:2": "The methodological approach that informs the CRD, which was built by Development Gateway (DG) with the collaboration and support of the Open Contracting Partnership (OCP), is presented in a co-authored research paper. Explanations of the principal concepts and indicators are available within the Dashboard.", + "crd:landing:intendedUse:title": "Intended Use", + "crd:landing:intendedUse:introduction": "The CRD was designed with the primary objective of supporting two specific use cases:", + "crd:landing:intendedUse:introduction:1": "1. To facilitate the efforts of procurement regulators and informed observers to identify individual contracts that may warrant investigation, and;", + "crd:landing:intendedUse:introduction:2": "2. To aid these individuals to monitor corruption risk in procurement markets over time. ", + "crd:landing:distinguishing:title": "Distinguishing between “Corruption” and “Corruption Risk”", + "crd:landing:distinguishing:1": "While flags associated with a procurement process may indicate the possibility that corruption has taken place, they may also be attributable to poor data quality, systemic weaknesses, or practices that may be appropriate when specifically authorized by a procurement authority or regulatory institution. For this reason, this tool is best viewed as a mechanism for identifying the “risk” of corruption, rather than “corruption” itself. ", + "crd:landing:distinguishing:2": "Furthermore, in some instances, a single flag - such as for the awarding of two contracts to the same supplier by a single procuring entity - may not show any evidence of wrongdoing, though the confluence of multiple indicators suggests greater cause of concern. ", + "crd:landing:feedback:title": "Your feedback is welcome", + "crd:landing:feedback:text": "We welcome your feedback on this prototype. Please contact Andrew Mandelbaum at DG if you have any questions or suggestions: amandelbaum@developmentgateway.org", + "crd:landing:enter": "Enter!" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 10dd77da2..47b0e1628 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -86,6 +86,17 @@ "crd:indicators:i077:shortDescription": "Un alto número de contratos competitivos se otorgan a un solo proveedor durante un año natural", "crd:indicators:i085:shortDescription": "La diferencia entre los precios de las ofertas es un porcentaje exacto (numero entero)", "crd:indicators:i171:shortDescription": "La oferta ganadora está dentro del 1% del precio estimado", - "crd:indicators:i180:shortDescription": "El proveedor recibe contratos de una sola fuente/no competitvos de una sola entidad contratante durante un año" - + "crd:indicators:i180:shortDescription": "El proveedor recibe contratos de una sola fuente/no competitvos de una sola entidad contratante durante un año", + "crd:landing:introduction:1": "El tablero de Riesgo de Corrupción (CRD por sus siglas en inglés) es una herramienta de fuente abierta que ayuda a que los usuarios entiendas la potencial presencia de corrupción en la contratación pública. Utilizando un enfoque de advertencias (resaltando las actividades de contratación que tienen algunos factores de riesgo), este prototipo examina el riesgo de corrupción a través de la visualización de 10 indicadores que se presentan bajo tres formas diferentes de corrupción: fraude, colusión, y manipulación en el proceso. Los usuarios pueden explorar los datos, los cuales se han convertido al Estándar de Datos para la Contratación Abierta (OCDS por sus siglas en inglés), utilizando una variedad de filtros. Una tabla de cruce permite a los usuarios cruzar dos indicadores asignados al mismo tipo de riesgo.", + "crd:landing:introduction:2": "El enfoque metodológico que genera información para los CRD, los cuales fueron creados por Development Gateway con la colaboración y apoyo de La Alianza para las Contrataciones Abierta (OCP por sus siglas en inglés), se encuentra plasmado en un trabajo de investigación escrito conjuntamente. Las explicaciones de los conceptos e indicadores principales se encuentran disponibles dentro del tablero", + "crd:landing:intendedUse:title": "Uso previsto", + "crd:landing:intendedUse:introduction": "El Tablero de riesgo de corrupción fue diseñado con objetivo de permitir dos tipos de uso:", + "crd:landing:intendedUse:introduction:1": "1. Facilitar los esfuerzos de regulación de compras y de observadores informados para identificar contratos individuales que puedan requerir una investigación, y;", + "crd:landing:intendedUse:introduction:2": "2. Ayudar a monitorear el riesgo de corrupción en los mercados de contratación a través del tiempo.", + "crd:landing:distinguishing:title": "Distinguir entre \"Corrupción\" y \"Riesgo de corrupción\"", + "crd:landing:distinguishing:1": "Mientras que las advertencias asociadas con el proceso de contratación puedan indicar la posibilidad de que la corrupción ya haya tenido lugar, también puede ser atribuida por la falta de datos de calidad, debilidad sistémica, o prácticas que pueden ser apropiadas cuando han sido especificamente autorizadas por la autoridad de contratación o la institución que la regula. Por esta razón, esta herramienta debe verse como un mecanismo para identificar el \"riesgo\" de corrupción, y no como \"corrupción\" misma.", + "crd:landing:distinguishing:2": "Además, en algunos casos, una advertencia - como para la adjudicación de dos contratos a el mismo proveedor por una entidad contratadora - podría no morstrar evidencia de una anomalía, aunque la confluencia de indicadores mútiples sugiere una causa de preocupación.", + "crd:landing:feedback:title": "Sus comentarios son bienvenidos", + "crd:landing:feedback:text": "Agradecemos sus comentarios para este prototipo. Por favorcontacte a Andrew Mandelbaum de DG si tiene preguntas o sugerencias: amandelbaum@developmentgateway.org", + "crd:landing:enter": "¡Ingresar!" } From f41a3c07816bb9732bc522452e5c77645294b841 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 17:43:56 +0300 Subject: [PATCH 152/702] OCE-366 Fraud page translations --- web/public/languages/es_ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 47b0e1628..9a8d6ef9b 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -1,9 +1,9 @@ { - "crd:corruptionType:FRAUD:pageTitle": "Fraud Risk", + "crd:corruptionType:FRAUD:pageTitle": "Riesgo de fraude", "crd:corruptionType:FRAUD:name": "Fraude", - "crd:corruptionType:FRAUD:introduction": "Fraud is “Any act or omission, including a misrepresentation, that knowingly or recklessly misleads, or attempts to mislead, a party to obtain a financial or other benefit or to avoid an obligation,\" according to the International Financial Institutions (IFI) Anti-Corruption Task Force Guidelines. Suppliers may engage in a variety of fraudulent activities in attempt to increase their chances at winning contracts or in demonstration of progress in implementation. Some of these fraud schemes include: false invoicing, false bidding, product substitution, fictitious contracting, and shadow bidding. The flags represented in each tile below indicate the possible existance of fraud in a procurement process. To learn more about each flag, click on the associated tile.", - "crd:corruptionType:FRAUD:crosstabTitle": "Fraud Risk Crosstab", - "crd:corruptionType:FRAUD:crosstab": "The table below shows the overlap between any two flags for fraud risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at an increased risk for fraud and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged and the corresponding flags.", + "crd:corruptionType:FRAUD:introduction": "Según las Directrices del grupo de tareas contra la corrupción de las instituciones financieras internacionales (IFI por sus siglas en inglés), el fraude es \" Cuaquier acto u omisión, incluyendo una falsa representación, que de manera consciente o negligentemente engaña o intenta engañar a una parte para obtener un beneficio financiero o de otro tipo o para evitar una obligación \". Los proveedores pueden involucrarse en una variedad de actividades fraudulentas para aumentar sus probabilidades de ganar contratos o para demostrar progresos en la implementción. Algunos de estos esquemas de fraude incluyen: facturas falsas, ofertas falsas, sustitución de productos, contrataciones ficticias, y ofertas sombra. Las advertencias representadas en los cuadro siguientes indican la posibilidad de que exista un fraude en el proceso de contratación. Para saber más sobre cada advertencia, haga clic en el cuadro asociado.", + "crd:corruptionType:FRAUD:crosstabTitle": "Tabla cruzada de riesgo de fraude", + "crd:corruptionType:FRAUD:crosstab": "La siguiente tabla muestra el cruce entre dos advertencias por riesgo de fraude. Las celdas con relleno más oscuro indican un porcentaje más alto de cruce entre las advertencias correspondientes. Las contrataciones con un mayor número de advertencias pueden aumentar el riesgo de fraude y pueden requerir una investigación adicional. Ponga el cursor en la celda para más información sobre el número de procesos de contratación identificados por el sistema de advertencia junto con las advertencias correspondientes.", "crd:corruptionType:RIGGING:name": "Manipulación del proceso", "crd:corruptionType:RIGGING:pageTitle": "Risk of Process Rigging", "crd:corruptionType:RIGGING:introduction": "\"Process rigging\" refers to an effort to rig the bidding, awarding, contracting or implementation process in favor of a particular player or entity to the exclusion of other legitimate participants. Whether intentionally or unintentionally, this form of risk implies the possible that a government official intervenes in the process to the benefit of one or more participants. Specific forms of process rigging may include: information withholding or misinformation, rigged specifications, unjustified direct (sole source) contracting, split purchasing, bid manipulation, favoring/excluding qualified bidders, change order abuse, and contracting abuse.", From b5e8179f7f037cbbef60c1110dda7c73a8c4b979 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 17:50:31 +0300 Subject: [PATCH 153/702] OCE-366 Rigging and collusion translations --- web/public/languages/es_ES.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 9a8d6ef9b..8dd247dbd 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -5,15 +5,15 @@ "crd:corruptionType:FRAUD:crosstabTitle": "Tabla cruzada de riesgo de fraude", "crd:corruptionType:FRAUD:crosstab": "La siguiente tabla muestra el cruce entre dos advertencias por riesgo de fraude. Las celdas con relleno más oscuro indican un porcentaje más alto de cruce entre las advertencias correspondientes. Las contrataciones con un mayor número de advertencias pueden aumentar el riesgo de fraude y pueden requerir una investigación adicional. Ponga el cursor en la celda para más información sobre el número de procesos de contratación identificados por el sistema de advertencia junto con las advertencias correspondientes.", "crd:corruptionType:RIGGING:name": "Manipulación del proceso", - "crd:corruptionType:RIGGING:pageTitle": "Risk of Process Rigging", - "crd:corruptionType:RIGGING:introduction": "\"Process rigging\" refers to an effort to rig the bidding, awarding, contracting or implementation process in favor of a particular player or entity to the exclusion of other legitimate participants. Whether intentionally or unintentionally, this form of risk implies the possible that a government official intervenes in the process to the benefit of one or more participants. Specific forms of process rigging may include: information withholding or misinformation, rigged specifications, unjustified direct (sole source) contracting, split purchasing, bid manipulation, favoring/excluding qualified bidders, change order abuse, and contracting abuse.", - "crd:corruptionType:RIGGING:crosstabTitle": "Process Rigging Crosstab", - "crd:corruptionType:RIGGING:crosstab": "The table below shows the overlap between any two process rigging flags. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at an increased risk for process rigging and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurements that have been flagged and the corresponding flags.", + "crd:corruptionType:RIGGING:pageTitle": "Riesgo de manipulación en el proceso", + "crd:corruptionType:RIGGING:introduction": "\"Manipulación del proceso\" se refiere a un esfuerzo para manipular la licitación. Esto contempla la otorgación, contratación o procesos de implementación que favorecen una entidad o un competidor dejando de lado a otros participantes legítimos. Ya sea intesionalmente o no, esta forma de riesgo implica la posibilidad que un funcionario del gobierno haya intervenido en el proceso para beneficiar a uno o dos participantes. Algunas manipulaciones del proceso pueden ser: retención de información o información errónea, especificaciones manipuladas, contratación directa injustificada (única fuente), compra fraccionada, manipulación de ofertas, favorecimiento / exclusión de licitadores calificados, abuso de orden de cambio y abuso de contratación.", + "crd:corruptionType:RIGGING:crosstabTitle": "Tabla de cruce de manipulación", + "crd:corruptionType:RIGGING:crosstab": "La tabla siguiente muestra el cruce entre dos advertencias de manipulación.Las celdas de color mpas oscuro indican un porcentaje más alto de cruce entre las advertencias correspondientes. Las contrataciones con un mayor número de advertencias pueden aumentar el riesgo de manipulación en el proceso y pueden requerir una investigación adicional. Ponga el cursor en la celda para más información sobre el número de contrataciones identificadas por el sistema de advertencia junto con las advertencias correspondientes.", "crd:corruptionType:COLLUSION:name": "Colusión", - "crd:corruptionType:COLLUSION:pageTitle": "Collusion Risk", - "crd:corruptionType:COLLUSION:introduction": "IFI Guidelines define a collusive practice as: “…an arrangement between two or more parties designed to achieve an improper purpose, including to influence improperly the actions of another party.” Here, we focus on collusive behavior between and among bidders; not between bidders and government officials (for this type of collusion, visit the process rigging page).", - "crd:corruptionType:COLLUSION:crosstabTitle": "Collusion Risk Crosstab", - "crd:corruptionType:COLLUSION:crosstab": "The table below shows the overlap between any two flags for collusion risk. Darker colored cells indicate a higher percentage of overlap between the corresponding flags. Procurements with a greater number of flags may be at a higher risk for collusion and may warrant additional investigation. Hover the mouse over a cell to learn more about the number of procurement processes that have been flagged, along with the corresponding flags.", + "crd:corruptionType:COLLUSION:pageTitle": "Riesgo de colusión", + "crd:corruptionType:COLLUSION:introduction": "Los directrices de IFI definen práctica colusoria como: \"...un acuerdo entre dos o más partes diseñado para lograr un propósito impropio, incluso para influir indebidamente en las acciones de otra parte.\" Aquí, nos centramos en el comportamiento colusorio entre los licitadores; no entre licitadores y funcionarios gubernamentales (para este tipo de colusión, visite la página de manipulaió en el proceso)", + "crd:corruptionType:COLLUSION:crosstabTitle": "Tabla cruzada del riesgo de colusión", + "crd:corruptionType:COLLUSION:crosstab": "La siguiente tabla muestra el cruze entre dos advertencias por riesgo de colusión. Las celdas de color más oscuro indican un mayor porcentaje de cruze entre las advertencias correspondientes. Las adquisiciones con un mayor número de advertencias pueden estar en mayor riesgo de colusión y pueden requerir una investigación adicional. Ponga el cursor en la celda para más información sobre el número de procesos de adquisición identificados por el sistema de advertencia junto con las advertencias correspondientes.", "crd:indicators:general:indicator": "Indicador:", "crd:indicators:general:eligibility": "Elegibilidad:", "crd:indicators:general:thresholds": "Umbrales:", From 9628ceb8e583f3e241e25f05b1bb23f367e0f648 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 18:08:15 +0300 Subject: [PATCH 154/702] OCE-366 Made `Filter your data` translatable --- ui/oce-standalone/index.jsx | 5 +++-- web/public/languages/en_US.json | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/oce-standalone/index.jsx b/ui/oce-standalone/index.jsx index 22aefda57..6bf4053e3 100644 --- a/ui/oce-standalone/index.jsx +++ b/ui/oce-standalone/index.jsx @@ -136,7 +136,7 @@ class OCEChild extends OCApp{
- Filter your data + {this.t('filters:hint')}
{this.filters()} {this.comparison()} @@ -180,7 +180,8 @@ class OCEChild extends OCApp{ } const translations = { - en_US: require('../../web/public/languages/en_US.json') + en_US: require('../../web/public/languages/en_US.json'), + es_ES: require('../../web/public/languages/en_US.json') }; const BILLION = 1000000000; diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 7dd93609f..b4de415ae 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -34,6 +34,7 @@ "export:exporting": "Exporting...", "export:export": "Export", "filters:awardValue:title": "Award value", + "filters:hint": "Filter your data", "filters:title": "Filter the data", "filters:apply": "Apply", "filters:reset": "Reset", From 179a6ab1bafac60639c25a3e460cc5560021f491 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 18:55:02 +0300 Subject: [PATCH 155/702] OCE-366 Overview page translations --- ui/oce/corruption-risk/filters/date.jsx | 2 +- ui/oce/corruption-risk/filters/index.jsx | 29 ++++++++++--------- .../corruption-risk/filters/organizations.jsx | 2 +- .../filters/procurement-method.jsx | 2 +- .../corruption-risk/filters/value-amount.jsx | 2 +- ui/oce/filters/tabs/index.jsx | 4 +-- web/public/languages/en_US.json | 2 ++ web/public/languages/es_ES.json | 10 ++++++- 8 files changed, 32 insertions(+), 21 deletions(-) diff --git a/ui/oce/corruption-risk/filters/date.jsx b/ui/oce/corruption-risk/filters/date.jsx index 93a3e03cc..f47d4ef8c 100644 --- a/ui/oce/corruption-risk/filters/date.jsx +++ b/ui/oce/corruption-risk/filters/date.jsx @@ -11,7 +11,7 @@ class DateBox extends FilterBox{ } getTitle(){ - return 'Date'; + return this.t('filters:tabs:date:title'); } reset(){ diff --git a/ui/oce/corruption-risk/filters/index.jsx b/ui/oce/corruption-risk/filters/index.jsx index 535af82d5..840d84517 100644 --- a/ui/oce/corruption-risk/filters/index.jsx +++ b/ui/oce/corruption-risk/filters/index.jsx @@ -5,33 +5,34 @@ import ProcurementMethodBox from "./procurement-method"; import ValueAmount from "./value-amount"; import DateBox from "./date"; import {fetchJson, range, pluck} from "../../tools"; +import translatable from '../../translatable'; -class Filters extends React.Component{ +class Filters extends translatable(React.Component) { render(){ - const {onUpdate, translations, currentBoxIndex, requestNewBox, state, allYears, allMonths, onApply, appliedFilters} = this.props; + const {onUpdate, t, currentBoxIndex, requestNewBox, state, allYears, allMonths, onApply, appliedFilters} = this.props; const {BOXES} = this.constructor; return (
e.stopPropagation()}>
-
Filter your data
+
{this.t('filters:hint')}
{BOXES.map((Box, index) => { return ( requestNewBox(currentBoxIndex === index ? null : index)} - state={state} - onUpdate={(slug, newState) => onUpdate(state.set(slug, newState))} - translations={translations} - onApply={newState => onApply(newState)} - allYears={allYears} - allMonths={allMonths} - appliedFilters={appliedFilters} + key={index} + open={currentBoxIndex === index} + onClick={e => requestNewBox(currentBoxIndex === index ? null : index)} + state={state} + onUpdate={(slug, newState) => onUpdate(state.set(slug, newState))} + t={t} + onApply={newState => onApply(newState)} + allYears={allYears} + allMonths={allMonths} + appliedFilters={appliedFilters} /> ) - })} + })}
diff --git a/ui/oce/corruption-risk/filters/index.jsx b/ui/oce/corruption-risk/filters/index.jsx index 840d84517..05fb4591d 100644 --- a/ui/oce/corruption-risk/filters/index.jsx +++ b/ui/oce/corruption-risk/filters/index.jsx @@ -9,7 +9,7 @@ import translatable from '../../translatable'; class Filters extends translatable(React.Component) { render(){ - const {onUpdate, t, currentBoxIndex, requestNewBox, state, allYears, allMonths, onApply, appliedFilters} = this.props; + const {onUpdate, translations, currentBoxIndex, requestNewBox, state, allYears, allMonths, onApply, appliedFilters} = this.props; const {BOXES} = this.constructor; return (
e.stopPropagation()}> @@ -25,7 +25,7 @@ class Filters extends translatable(React.Component) { onClick={e => requestNewBox(currentBoxIndex === index ? null : index)} state={state} onUpdate={(slug, newState) => onUpdate(state.set(slug, newState))} - t={t} + translations={translations} onApply={newState => onApply(newState)} allYears={allYears} allMonths={allMonths} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 71b12e23d..edcc77fbd 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -83,8 +83,8 @@ class CorruptionRiskDashboard extends React.Component { getPage() { const { route, navigate } = this.props; + const translations = this.getTranslations(); const styling = this.constructor.STYLING || this.props.styling; - const t = this.t.bind(this); const [page] = route; const { appliedFilters, indicatorTypesMapping, width } = this.state; @@ -104,7 +104,7 @@ class CorruptionRiskDashboard extends React.Component { indicators={indicators} onGotoIndicator={individualIndicator => navigate('indicator', corruptionType, individualIndicator)} filters={filters} - t={t} + translations={translations} corruptionType={corruptionType} years={years} monthly={monthly} @@ -120,7 +120,7 @@ class CorruptionRiskDashboard extends React.Component { indicator={individualIndicator} corruptionType={corruptionType} filters={filters} - t={t} + translations={translations} years={years} monthly={monthly} months={months} @@ -132,7 +132,7 @@ class CorruptionRiskDashboard extends React.Component { return ( this.setState({ showLandingPopup: false })} - t={t} + translations={translations} /> }
@@ -260,7 +266,7 @@ class CorruptionRiskDashboard extends React.Component { appliedFilters: filtersToApply, currentFiltersState: filtersToApply, })} - t={t} + translations={translations} currentBoxIndex={filterBoxIndex} requestNewBox={index => this.setState({ filterBoxIndex: index })} state={currentFiltersState} @@ -301,7 +307,7 @@ class CorruptionRiskDashboard extends React.Component { filters={filters} requestNewData={(path, newData) => this.setState({ data: this.state.data.setIn(['totalFlags'].concat(path), newData) })} - t={t} + translations={translations} data={data.get('totalFlags', Map())} years={years} months={months} diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 9d9cc35f4..0dc5b53ff 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -149,8 +149,7 @@ class OverviewPage extends CRDPage{ render(){ const {corruptionType, topFlaggedContracts} = this.state; - const {filters, t, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; - + const {filters, translations, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; return (
@@ -158,7 +157,7 @@ class OverviewPage extends CRDPage{ this.setState({corruptionType})} - t={t} + translations={translations} data={corruptionType} years={years} monthly={monthly} @@ -174,7 +173,7 @@ class OverviewPage extends CRDPage{ -
Total Flags:
+
{this.t('crd:charts:totalFlags:title')}
{data.getIn([0, 'flaggedCount'], 0)}
diff --git a/ui/oce/filters/tabs/index.jsx b/ui/oce/filters/tabs/index.jsx index c32537511..8737b16e1 100644 --- a/ui/oce/filters/tabs/index.jsx +++ b/ui/oce/filters/tabs/index.jsx @@ -3,7 +3,7 @@ import {Set} from "immutable"; class Tab extends translatable(React.Component){ renderChild(Component, slug){ - let {onUpdate, t, state} = this.props; + let {onUpdate, translations, state} = this.props; let selected = state.get(slug, Set()); return } } diff --git a/ui/oce/translatable.jsx b/ui/oce/translatable.jsx index eea3a57ea..9b566e19c 100644 --- a/ui/oce/translatable.jsx +++ b/ui/oce/translatable.jsx @@ -7,16 +7,11 @@ var translatable = Class => class Translatable extends Class { __n(sg, pl, n) { console.warn('__n is deprecated, use t_n'); - return n + " " + this.__(1 === n ? sg : pl); + return n + ' ' + this.__(1 === n ? sg : pl); } t(key) { - const { translations, t } = this.props; - if (typeof t === 'function') { - return t(key); - } - console.warn('Passing translation objects is deprecated!'); - if(!translations) return key; + const { translations } = this.props; if(!translations) console.error('Missing translations', this.constructor.name); if(!translations[key]) console.error('Missing translation for key', key); return translations[key]; diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 94906b5b6..2f56265bc 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -260,5 +260,6 @@ "crd:landing:distinguishing:2": "Furthermore, in some instances, a single flag - such as for the awarding of two contracts to the same supplier by a single procuring entity - may not show any evidence of wrongdoing, though the confluence of multiple indicators suggests greater cause of concern. ", "crd:landing:feedback:title": "Your feedback is welcome", "crd:landing:feedback:text": "We welcome your feedback on this prototype. Please contact Andrew Mandelbaum at DG if you have any questions or suggestions: amandelbaum@developmentgateway.org", - "crd:landing:enter": "Enter!" + "crd:landing:enter": "Enter!", + "crd:charts:totalFlags:title": "Total Flags:" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 320776801..bda464255 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -105,6 +105,6 @@ "crd:landing:feedback:text": "Agradecemos sus comentarios para este prototipo. Por favorcontacte a Andrew Mandelbaum de DG si tiene preguntas o sugerencias: amandelbaum@developmentgateway.org", "crd:landing:enter": "¡Ingresar!", "crd:overview:overTimeChart:title": "Riesgo de fraude, colusión y manipulación del proceso a tavés del tiempo", - "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias" - + "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias", + "crd:charts:totalFlags:title": "Advertencias totales:" } From e80ac4fd03eaa3e903335b28381b3da85c72d4a6 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 21:06:31 +0300 Subject: [PATCH 157/702] OCE-366 Merged English translation into Spanish ones --- web/public/languages/es_ES.json | 163 +++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 4 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index bda464255..ebd3374ea 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -1,9 +1,148 @@ { + "general:comparison:other": "Other", + "general:amountInVND": "Amount", + "general:title": "e-Procurement", + "general:subtitle": "Toolkit", + "general:description:title": "Toolkit description", + "general:login": "Log in", + "general:logout": "Log out", + "general:viewOnGithub": "View code on Github", + "general:year": "Year", + "general:month": "Month", + "general:months:1": "Jan", + "general:months:2": "Feb", + "general:months:3": "Mar", + "general:months:4": "Apr", + "general:months:5": "May", + "general:months:6": "Jun", + "general:months:7": "Jul", + "general:months:8": "Aug", + "general:months:9": "Sep", + "general:months:10": "Oct", + "general:months:11": "Nov", + "general:months:12": "Dec", + "general:range:min": "Min", + "general:range:max": "Max", + "general:loading": "Loading...", + "header:comparison:title": "Compare", + "header:comparison:criteria": "Comparison criteria", + "header:comparison:criteria:none": "None", + "header:comparison:criteria:bidType": "Bid Type", + "header:comparison:criteria:bidSelectionMethod": "Bid Selection Method", + "header:comparison:criteria:procuringEntity": "Procuring Entity", + "export:error": "An error occurred during export!", + "export:exporting": "Exporting...", + "export:export": "Export", + "filters:awardValue:title": "Award value", "filters:hint": "Filtro de datos", + "filters:title": "Filter the data", + "filters:apply": "Apply", + "filters:reset": "Reset", + "filters:dashboard:save": "Save", + "filters:dashboard:saveForAdmin": "Save for admin", + "filters:dashboard:saveUnassigned": "Save unassigned", + "filters:dashboard:load": "Load", + "filters:dashboard:buttonTitle": "Dashboards", + "filters:dashboard:saveError": "Unexpected error. The dashboard was not saved.", + "filters:dashboard:defaultHint": "You can set your default dashboard in", + "filters:dashboard:profileSettings": "your profile settings", + "filters:dashboard:manageDashboards": "Manage dashboards", + "filters:dashboard:manageUsers": "Manage users", + "filters:procuringEntity:title": "Procuring entity", + "filters:supplier:title": "Supplier", + "filters:tenderPrice:title": "Tender price", + "filters:multipleSelect:selectAll": "Select all", + "filters:multipleSelect:selectNone": "Select none", + "filters:typeAhead:inputPlaceholder": "type search query", + "filters:typeAhead:result:sg": "result", + "filters:typeAhead:result:pl": "results", + "filters:tabs:amounts:title": "Amounts", "filters:tabs:organizations:title": "Organizaciones", "filters:tabs:procurementMethod:title": "Método de contratación", "filters:tabs:valueAmount:title": "Cantidad de valor", "filters:tabs:date:title": "Fecha", + "tabs:competitiveness:title": "Competitiveness", + "tabs:eProcurement:title": "E-Procurement", + "tabs:efficiency:title": "Efficiency", + "tabs:overview:title": "Overview", + "tabs:location:title": "Location", + "charts:avgNrBids:xAxisTitle": "Year", + "charts:avgNrBids:yAxisTitle": "Number", + "charts:avgNrBids:title": "Average Number of Bidders Attending One Bid", + "charts:bidPeriod:traces:tender": "Tender", + "charts:bidPeriod:traces:award": "Award", + "charts:bidPeriod:traces:total": "Total:", + "charts:bidPeriod:xAxisTitle": "Number of days", + "charts:bidPeriod:yAxisTitle": "Year", + "charts:bidPeriod:title": "Bid Timeline", + "charts:bidsByItem:title": "Number of invitations to bid by item", + "charts:bidsByItem:xAxisTitle": "Item", + "charts:bidsByItem:yAxisTitle": "Count", + "charts:amountsByItem:title": "Total Amount by Items", + "charts:amountsByItem:xAxisTitle": "Item", + "charts:amountsByItem:yAxisTitle": "Amount", + "charts:costEffectiveness:traces:awardPrice": "Award Price", + "charts:costEffectiveness:traces:tenderPrice": "Bid Price", + "charts:costEffectiveness:traces:difference": "Difference", + "charts:costEffectiveness:xAxisTitle": "Year", + "charts:costEffectiveness:yAxisTitle": "Amount", + "charts:costEffectiveness:title": "Cost effectiveness", + "charts:general:noData": "No data", + "charts:nrCancelled:title": "Number of cancelled invitations to bid", + "charts:nrCancelled:xAxisTitle": "Year", + "charts:nrCancelled:yAxisTitle": "Count", + "charts:nrEBid:xAxisTitle": "Year", + "charts:nrEBid:yAxisTitle": "Count", + "charts:nrEBid:title": "Number of eBid Awards", + "charts:overview:traces:award": "Award", + "charts:overview:traces:bidplan": "Bidplan", + "charts:overview:traces:tender": "Tender", + "charts:overview:xAxisName": "Year", + "charts:overview:yAxisName": "Count", + "charts:overview:title": "Procurement activity by year", + "charts:percentEBid:xAxisName": "Year", + "charts:percentEBid:yAxisName": "Percent", + "charts:percentEBid:title": "Percent of Tenders Using e-Bid", + "charts:procurementMethod:unspecified": "Unspecified", + "charts:procurementMethod:xAxisName": "Method", + "charts:procurementMethod:yAxisName": "Amount", + "charts:procurementMethod:title": "Procurement method", + "charts:cancelledAmounts:xAxisName": "Year", + "charts:cancelledAmounts:yAxisName": "Amount", + "charts:cancelledPercents:title": "Cancelled funding (%)", + "charts:cancelledAmounts:title": "Cancelled funding", + "charts:cancelledPercents:xAxisName": "Year", + "charts:cancelledPercents:yAxisName": "Percent", + "maps:tenderLocations:title": "Tender locations", + "maps:tenderLocations:tabs:overview:title": "Overview", + "maps:tenderLocations:tabs:overview:nrOfTenders": "Number of Tenders:", + "maps:tenderLocations:tabs:overview:totalFundingByLocation": "Total Funding for the location", + "tables:top10awards:number": "Number", + "tables:top10awards:date": "Date", + "tables:top10awards:supplier": "Supplier", + "tables:top10awards:value": "Value", + "tables:top10awards:title": "Top 10 largest awards", + "tables:top10tenders:number": "Number", + "tables:top10tenders:startDate": "Start date", + "tables:top10tenders:endDate": "End date", + "tables:top10tenders:procuringEntity": "Procuring entity", + "tables:top10tenders:estimatedValue": "Estimated value", + "tables:top10tenders:title": "Top 10 largest tenders", + "charts:percentWithTenders:title": "Percentage of plans with invitation to bid", + "charts:percentWithTenders:xAxisTitle": "Year", + "charts:percentWithTenders:yAxisTitle": "Percent", + "tables:frequentTenderers:supplier": "Supplier", + "tables:frequentTenderers:nrITB": "Number of ITBs in Common", + "tables:frequentTenderers:title": "Frequent Supplier Bidding Combinations", + "tables:showAll": "Show all", + "tables:frequentTenderers:supplier1wins": "Supplier #1 wins", + "tables:frequentTenderers:supplier2wins": "Supplier #2 wins", + "tables:top10suppliers:supplierName": "Supplier name", + "tables:top10suppliers:nrAwardsWon": "Number of awards won", + "tables:top10suppliers:nrPE": "Number of PEs from which an award has been received", + "tables:top10suppliers:totalAwardedValue": "Total awarded value", + "tables:top10suppliers:title": "Top 10 suppliers", + "yearsBar:ctrlClickHint": "To select a single year, hold 'shift' while clicking, or simply double click.", "crd:corruptionType:FRAUD:pageTitle": "Riesgo de fraude", "crd:corruptionType:FRAUD:name": "Fraude", "crd:corruptionType:FRAUD:introduction": "Según las Directrices del grupo de tareas contra la corrupción de las instituciones financieras internacionales (IFI por sus siglas en inglés), el fraude es \" Cuaquier acto u omisión, incluyendo una falsa representación, que de manera consciente o negligentemente engaña o intenta engañar a una parte para obtener un beneficio financiero o de otro tipo o para evitar una obligación \". Los proveedores pueden involucrarse en una variedad de actividades fraudulentas para aumentar sus probabilidades de ganar contratos o para demostrar progresos en la implementción. Algunos de estos esquemas de fraude incluyen: facturas falsas, ofertas falsas, sustitución de productos, contrataciones ficticias, y ofertas sombra. Las advertencias representadas en los cuadro siguientes indican la posibilidad de que exista un fraude en el proceso de contratación. Para saber más sobre cada advertencia, haga clic en el cuadro asociado.", @@ -34,10 +173,10 @@ "crd:indicators:i003:thresholds": "2 o más proveedores deben haber presentado ofertas.", "crd:indicators:i003:description": "Cuando un proceso competitivo de contratación recibe una sola oferta elegible entre otras ofertas no elegibles, existe la posibilidad de que las ofertas inelegibles sean ficticias, sirviendo sólo el propósito de dar la apariencia de un proceso competitivo. También podría sugerir que la oferta ha sido manipulada para favorecer a un licitador o excluir a otros, incluyendo la retención de información necesaria o la información errónea de algunos licitadores. Esto podría ser una señal de fraude o manipulación del proceso.", "crd:indicators:i004:name": "Adjudicación inelegible directa", + "crd:indicators:i004:description": "Esta advertencia de manipulación del proceso sugiere que las reglas de licitación competitivas han sido ignoradas para permitir que un proceso de licitación directa avance cuando se requiere un método competitivo.", "crd:indicators:i004:indicator": "Se otorga un contrato de única fuente por encima del umbral competitivo.", "crd:indicators:i004:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es la única fuente (directa).", "crd:indicators:i004:thresholds": "Umbrales de origen único utilizados de acuerdo con la legislación local.", - "crd:indicators:i004:description": "Esta advertencia de manipulación del proceso sugiere que las reglas de licitación competitivas han sido ignoradas para permitir que un proceso de licitación directa avance cuando se requiere un método competitivo.", "crd:indicators:i007:name": "Licitador único", "crd:indicators:i007:indicator": "Esta licitación adjudicada presenta una sola oferta.", "crd:indicators:i007:eligibility": "Se ha otorgado una adjudicación; El método de adquisición es competitivo.", @@ -54,10 +193,10 @@ "crd:indicators:i038:thresholds": "Período de 3 días de aofert.", "crd:indicators:i038:description": "Un período de licitación particularmente corto podría indicar que el plazo para que los proveedores presentaran sus ofertas se redujo para favorecer a ciertos proveedores en detrimento de otros.", "crd:indicators:i077:name": "Ganador de contratos múltiples", + "crd:indicators:i077:description": "Cuando un proveedor único gana múltiples contratos competitivos de una entidad contratante dentro de un período de tiempo condensado, puede indicar un esfuerzo para favorecer a un proveedor individual, lo que puede resultar en sobornos a las personas involucradas en el proceso de evaluación o adjudicación.", "crd:indicators:i077:indicator": "Se otorga un alto número de contratos competitivos a un proveedor dentro de un período de un año por una sola entidad adjudicadora.", "crd:indicators:i077:eligibility": "Se ha otorgado una adjudicación. El método de adquisición es competitivo.", "crd:indicators:i077:thresholds": "Mínimo de 2 adjudicaciones otorgadas a 1 proveedor por 1 entidad contratante.", - "crd:indicators:i077:description": "Cuando un proveedor único gana múltiples contratos competitivos de una entidad contratante dentro de un período de tiempo condensado, puede indicar un esfuerzo para favorecer a un proveedor individual, lo que puede resultar en sobornos a las personas involucradas en el proceso de evaluación o adjudicación.", "crd:indicators:i083:name": "Winner-Loser Pattern", "crd:indicators:i083:description": "When X supplier wins, same set of tenderers loses (at least twice)", "crd:indicators:i083:indicator": "When one supplier wins, the same tenderers always lose.", @@ -81,7 +220,25 @@ "crd:title": "Tablero de riesgo de corrupción", "crd:overview": "Resumen del Riesgo de corrupción", "crd:description": "Los tableros de Riesgo de corrupción emplean un enfoque de advertencia para que los usuarios entiendan la potencial presencia de fraude, colusión o manipulación en el proceso en las contrataciones públicas. Mientras que las advertencias pueden indicar la presencia de corrupción, también pueden atribuirse a problemas de calidad de los datos, violación de leyes o de buenas prácticas internacionales, o otros problemas.", + "crd:overview:overTimeChart:indicators": "Indicators", + "crd:overview:overTimeChart:totalFlags": "Total Flags", + "crd:overview:overTimeChart:totalProcurementsFlagged": "Total Procurements Flagged", + "crd:overview:overTimeChart:percentFlagged": "% Total Procurements Flagged", + "crd:overview:overTimeChart:title": "Riesgo de fraude, colusión y manipulación del proceso a tavés del tiempo", + "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias", + "crd:corruptionType:indicatorTile:procurementsFlagged": "Procurements Flagged", + "crd:corruptionType:indicatorTile:eligibleProcurements": "Eligible Procurements", + "crd:corruptionType:indicatorTile:percentEligibleFlagged": "% Eligible Procurements Flagged", + "crd:corruptionType:indicatorTile:percentProcurementsEligible": "% Procurements Eligible", + "crd:corruptionType:crosstab:popup:percents": "$#$% of procurements flagged for \"$#$\" are also flagged for \"$#$\"", + "crd:corruptionType:crosstab:popup:count": "$#$ Procurements flagged with both;", "crd:corruptionType:crosstab:popup:and": "y", + "crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged": "Procurements Flagged", + "crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements": "Eligible Procurements", + "crd:indicatorPage:individualIndicatorChart:popup:percentOfEligibleFlagged": "% Eligible Procurements Flagged", + "crd:indicatorPage:individualIndicatorChart:popup:percentEligible": "% Procurements Eligible", + "crd:indicatorPage:individualIndicatorChart:title": "Eligible Procurements and Flagged Procurements for $#$", + "crd:indicatorPage:projectTable:title": "List of Procurements Flagged for $#$", "crd:indicators:i002:shortDescription": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", "crd:indicators:i003:shortDescription": "Sólo el licitador ganador era elegible para obtener el contrato de licitación cuando 2+ licitadores aplican", "crd:indicators:i004:shortDescription": "La adjudicación de única fuente se otorga por encima del umbral competitivo a pesar de los requisitos legales", @@ -104,7 +261,5 @@ "crd:landing:feedback:title": "Sus comentarios son bienvenidos", "crd:landing:feedback:text": "Agradecemos sus comentarios para este prototipo. Por favorcontacte a Andrew Mandelbaum de DG si tiene preguntas o sugerencias: amandelbaum@developmentgateway.org", "crd:landing:enter": "¡Ingresar!", - "crd:overview:overTimeChart:title": "Riesgo de fraude, colusión y manipulación del proceso a tavés del tiempo", - "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias", "crd:charts:totalFlags:title": "Advertencias totales:" } From aee0d369da328f86acd32d0bb7e40c6a3af328ea Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 21:24:32 +0300 Subject: [PATCH 158/702] OCE-366 Replaced flags with en/es --- ui/oce/corruption-risk/index.jsx | 27 +++++++++++++++++++-------- ui/oce/corruption-risk/style.less | 10 ++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index edcc77fbd..3fa934357 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -64,16 +64,27 @@ class CorruptionRiskDashboard extends React.Component { languageSwitcher() { const { TRANSLATIONS } = this.constructor; + const { locale: selectedLocale } = this.state; if (Object.keys(TRANSLATIONS).length <= 1) return null; - return Object.keys(TRANSLATIONS).map(locale => - ({`${locale} ( + this.setLocale(locale)} - key={locale} - />), - ); + className={cn({active: locale === selectedLocale})} + > + {locale.split('_')[0]} + + )); + /* return Object.keys(TRANSLATIONS).map(locale => + * ( + * {`${locale}), + * );*/ } setLocale(locale) { diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 7170f70b2..dac4c3ada 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -549,4 +549,14 @@ z-index: 9998; background: rgba(0, 0, 0, .7); } + + .language-switcher{ + a{ + text-decoration: none!important; + font-weight: bolder; + display: block; + margin-right: 5px; + font-size: 1.5em; + } + } } From a22498322936240b0ea4bde8ad5e350c40c16c4c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 21:30:55 +0300 Subject: [PATCH 159/702] OCE-366 Added the language switcher to the landing popup --- ui/oce/corruption-risk/index.jsx | 1 + ui/oce/corruption-risk/landing-popup.jsx | 10 +++++++--- ui/oce/corruption-risk/style.less | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 3fa934357..b107148a3 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -240,6 +240,7 @@ class CorruptionRiskDashboard extends React.Component { redirectToLogin={!disabledApiSecurity} requestClosing={() => this.setState({ showLandingPopup: false })} translations={translations} + languageSwitcher={this.languageSwitcher.bind(this)} /> }
diff --git a/ui/oce/corruption-risk/landing-popup.jsx b/ui/oce/corruption-risk/landing-popup.jsx index c2339af1f..e7a105381 100644 --- a/ui/oce/corruption-risk/landing-popup.jsx +++ b/ui/oce/corruption-risk/landing-popup.jsx @@ -36,7 +36,8 @@ class LandingPopup extends translatable(React.Component) { } render(){ - const {top} = this.state; + const { top } = this.state; + const { languageSwitcher } = this.props; return (
@@ -47,10 +48,13 @@ class LandingPopup extends translatable(React.Component) {
-
+

{this.t('crd:title')}

-
+
+ {languageSwitcher()} +
+
diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index dac4c3ada..e9fff511e 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -557,6 +557,10 @@ display: block; margin-right: 5px; font-size: 1.5em; + padding: 0 5px; + &.active{ + border: 1px solid; + } } } } From c3203abecf3447871846b0c1a41f2e5d806fa7f2 Mon Sep 17 00:00:00 2001 From: Alexei Date: Mon, 11 Sep 2017 21:41:32 +0300 Subject: [PATCH 160/702] OCE-366 Removed the Spanish flag --- ui/assets/flags/es_ES.png | Bin 55042 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ui/assets/flags/es_ES.png diff --git a/ui/assets/flags/es_ES.png b/ui/assets/flags/es_ES.png deleted file mode 100644 index 70b4ec089bfa71f9ed6e1b383f3c4a7fec6f9b99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55042 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

atgRy%i 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 Date: Mon, 11 Sep 2017 21:47:39 +0300 Subject: [PATCH 161/702] OCE-366 Crosstab translations --- web/public/languages/es_ES.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index ebd3374ea..45f309545 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -230,8 +230,8 @@ "crd:corruptionType:indicatorTile:eligibleProcurements": "Eligible Procurements", "crd:corruptionType:indicatorTile:percentEligibleFlagged": "% Eligible Procurements Flagged", "crd:corruptionType:indicatorTile:percentProcurementsEligible": "% Procurements Eligible", - "crd:corruptionType:crosstab:popup:percents": "$#$% of procurements flagged for \"$#$\" are also flagged for \"$#$\"", - "crd:corruptionType:crosstab:popup:count": "$#$ Procurements flagged with both;", + "crd:corruptionType:crosstab:popup:percents": "El $#$% de contrataciones identificadas con advertencia por \"$#$\" también fue identificado por \"$#$\"", + "crd:corruptionType:crosstab:popup:count": "El $#$ de contrataciones identificadas con ambas advertencias:", "crd:corruptionType:crosstab:popup:and": "y", "crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged": "Procurements Flagged", "crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements": "Eligible Procurements", From 8ad12a4da80089f468a780959fbcd0b9bd3d4202 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Sep 2017 21:55:26 +0300 Subject: [PATCH 162/702] OCE-366 Individual indicators translations --- web/public/languages/es_ES.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 45f309545..465340e57 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -237,8 +237,8 @@ "crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements": "Eligible Procurements", "crd:indicatorPage:individualIndicatorChart:popup:percentOfEligibleFlagged": "% Eligible Procurements Flagged", "crd:indicatorPage:individualIndicatorChart:popup:percentEligible": "% Procurements Eligible", - "crd:indicatorPage:individualIndicatorChart:title": "Eligible Procurements and Flagged Procurements for $#$", - "crd:indicatorPage:projectTable:title": "List of Procurements Flagged for $#$", + "crd:indicatorPage:individualIndicatorChart:title": "Contrataciones eligibles y contrataciones con advertencias para $#$", + "crd:indicatorPage:projectTable:title": "Lista de contratación con advertencias para $#$", "crd:indicators:i002:shortDescription": "El proveedor ganador proporciona un precio de oferta sustancialmente menor que sus competidores", "crd:indicators:i003:shortDescription": "Sólo el licitador ganador era elegible para obtener el contrato de licitación cuando 2+ licitadores aplican", "crd:indicators:i004:shortDescription": "La adjudicación de única fuente se otorga por encima del umbral competitivo a pesar de los requisitos legales", From 3d7348a4789f50603c379e05f3f4055e98dc71fc Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 06:55:15 +0300 Subject: [PATCH 163/702] OCE-373 Over time chart popup translations --- web/public/languages/es_ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 465340e57..4b833dc52 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -220,10 +220,10 @@ "crd:title": "Tablero de riesgo de corrupción", "crd:overview": "Resumen del Riesgo de corrupción", "crd:description": "Los tableros de Riesgo de corrupción emplean un enfoque de advertencia para que los usuarios entiendan la potencial presencia de fraude, colusión o manipulación en el proceso en las contrataciones públicas. Mientras que las advertencias pueden indicar la presencia de corrupción, también pueden atribuirse a problemas de calidad de los datos, violación de leyes o de buenas prácticas internacionales, o otros problemas.", - "crd:overview:overTimeChart:indicators": "Indicators", - "crd:overview:overTimeChart:totalFlags": "Total Flags", - "crd:overview:overTimeChart:totalProcurementsFlagged": "Total Procurements Flagged", - "crd:overview:overTimeChart:percentFlagged": "% Total Procurements Flagged", + "crd:overview:overTimeChart:indicators": "Indicadores", + "crd:overview:overTimeChart:totalFlags": "Advertencias totales", + "crd:overview:overTimeChart:totalProcurementsFlagged": "Total de contrataciones con advertencias", + "crd:overview:overTimeChart:percentFlagged": "% total de contrataciones con advertencias", "crd:overview:overTimeChart:title": "Riesgo de fraude, colusión y manipulación del proceso a tavés del tiempo", "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias", "crd:corruptionType:indicatorTile:procurementsFlagged": "Procurements Flagged", From 7285f68c0defc6f7ad29ddacbdfdf3141af5c71b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 06:56:35 +0300 Subject: [PATCH 164/702] OCE-373 Indicator tiles translations --- web/public/languages/es_ES.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 4b833dc52..8d81456ff 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -226,10 +226,10 @@ "crd:overview:overTimeChart:percentFlagged": "% total de contrataciones con advertencias", "crd:overview:overTimeChart:title": "Riesgo de fraude, colusión y manipulación del proceso a tavés del tiempo", "crd:overview:topFlagged:title": "Los procesos de contratación con más advertencias", - "crd:corruptionType:indicatorTile:procurementsFlagged": "Procurements Flagged", - "crd:corruptionType:indicatorTile:eligibleProcurements": "Eligible Procurements", - "crd:corruptionType:indicatorTile:percentEligibleFlagged": "% Eligible Procurements Flagged", - "crd:corruptionType:indicatorTile:percentProcurementsEligible": "% Procurements Eligible", + "crd:corruptionType:indicatorTile:procurementsFlagged": "Contrataciones con advertencias", + "crd:corruptionType:indicatorTile:eligibleProcurements": "Contrataciones elegibles", + "crd:corruptionType:indicatorTile:percentEligibleFlagged": "% de contrataciones elegibles con advertencias", + "crd:corruptionType:indicatorTile:percentProcurementsEligible": "% de contrataciones elegibles", "crd:corruptionType:crosstab:popup:percents": "El $#$% de contrataciones identificadas con advertencia por \"$#$\" también fue identificado por \"$#$\"", "crd:corruptionType:crosstab:popup:count": "El $#$ de contrataciones identificadas con ambas advertencias:", "crd:corruptionType:crosstab:popup:and": "y", From b37fe4e6313c0669702cbbbbfe58052e1e069460 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 07:01:39 +0300 Subject: [PATCH 165/702] OCE-373 Filters translations --- web/public/languages/es_ES.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 8d81456ff..ff91244a1 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -4,8 +4,8 @@ "general:title": "e-Procurement", "general:subtitle": "Toolkit", "general:description:title": "Toolkit description", - "general:login": "Log in", - "general:logout": "Log out", + "general:login": "Iniciar sesión", + "general:logout": "Cerrar sesión", "general:viewOnGithub": "View code on Github", "general:year": "Year", "general:month": "Month", @@ -29,15 +29,15 @@ "header:comparison:criteria:none": "None", "header:comparison:criteria:bidType": "Bid Type", "header:comparison:criteria:bidSelectionMethod": "Bid Selection Method", - "header:comparison:criteria:procuringEntity": "Procuring Entity", + "header:comparison:criteria:procuringEntity": "Entidad contratante", "export:error": "An error occurred during export!", "export:exporting": "Exporting...", "export:export": "Export", - "filters:awardValue:title": "Award value", + "filters:awardValue:title": "Valor de la adjudicación", "filters:hint": "Filtro de datos", "filters:title": "Filter the data", - "filters:apply": "Apply", - "filters:reset": "Reset", + "filters:apply": "Aplicar", + "filters:reset": "Restablecer", "filters:dashboard:save": "Save", "filters:dashboard:saveForAdmin": "Save for admin", "filters:dashboard:saveUnassigned": "Save unassigned", @@ -48,11 +48,11 @@ "filters:dashboard:profileSettings": "your profile settings", "filters:dashboard:manageDashboards": "Manage dashboards", "filters:dashboard:manageUsers": "Manage users", - "filters:procuringEntity:title": "Procuring entity", - "filters:supplier:title": "Supplier", - "filters:tenderPrice:title": "Tender price", - "filters:multipleSelect:selectAll": "Select all", - "filters:multipleSelect:selectNone": "Select none", + "filters:procuringEntity:title": "Entidad contratante", + "filters:supplier:title": "Proveedor", + "filters:tenderPrice:title": "Precio de la licitación", + "filters:multipleSelect:selectAll": "Seleccionar todo", + "filters:multipleSelect:selectNone": "No seleccionar", "filters:typeAhead:inputPlaceholder": "type search query", "filters:typeAhead:result:sg": "result", "filters:typeAhead:result:pl": "results", @@ -119,19 +119,19 @@ "maps:tenderLocations:tabs:overview:totalFundingByLocation": "Total Funding for the location", "tables:top10awards:number": "Number", "tables:top10awards:date": "Date", - "tables:top10awards:supplier": "Supplier", + "tables:top10awards:supplier": "Proveedor", "tables:top10awards:value": "Value", "tables:top10awards:title": "Top 10 largest awards", "tables:top10tenders:number": "Number", "tables:top10tenders:startDate": "Start date", "tables:top10tenders:endDate": "End date", - "tables:top10tenders:procuringEntity": "Procuring entity", + "tables:top10tenders:procuringEntity": "Entidad contratante", "tables:top10tenders:estimatedValue": "Estimated value", "tables:top10tenders:title": "Top 10 largest tenders", "charts:percentWithTenders:title": "Percentage of plans with invitation to bid", "charts:percentWithTenders:xAxisTitle": "Year", "charts:percentWithTenders:yAxisTitle": "Percent", - "tables:frequentTenderers:supplier": "Supplier", + "tables:frequentTenderers:supplier": "Proveedor", "tables:frequentTenderers:nrITB": "Number of ITBs in Common", "tables:frequentTenderers:title": "Frequent Supplier Bidding Combinations", "tables:showAll": "Show all", @@ -142,7 +142,7 @@ "tables:top10suppliers:nrPE": "Number of PEs from which an award has been received", "tables:top10suppliers:totalAwardedValue": "Total awarded value", "tables:top10suppliers:title": "Top 10 suppliers", - "yearsBar:ctrlClickHint": "To select a single year, hold 'shift' while clicking, or simply double click.", + "yearsBar:ctrlClickHint": "Para seleccionar un solo año y poder seleccionar varios meses, presione "shift" mientras hace clic en el año.", "crd:corruptionType:FRAUD:pageTitle": "Riesgo de fraude", "crd:corruptionType:FRAUD:name": "Fraude", "crd:corruptionType:FRAUD:introduction": "Según las Directrices del grupo de tareas contra la corrupción de las instituciones financieras internacionales (IFI por sus siglas en inglés), el fraude es \" Cuaquier acto u omisión, incluyendo una falsa representación, que de manera consciente o negligentemente engaña o intenta engañar a una parte para obtener un beneficio financiero o de otro tipo o para evitar una obligación \". Los proveedores pueden involucrarse en una variedad de actividades fraudulentas para aumentar sus probabilidades de ganar contratos o para demostrar progresos en la implementción. Algunos de estos esquemas de fraude incluyen: facturas falsas, ofertas falsas, sustitución de productos, contrataciones ficticias, y ofertas sombra. Las advertencias representadas en los cuadro siguientes indican la posibilidad de que exista un fraude en el proceso de contratación. Para saber más sobre cada advertencia, haga clic en el cuadro asociado.", From e6aee1b225fca846747dc4ddda7d321232fdf0d7 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 11:31:32 +0300 Subject: [PATCH 166/702] OCE-373 Procurements table translations --- ui/oce/corruption-risk/procurements-table.jsx | 18 +++++++++--------- web/public/languages/en_US.json | 11 ++++++++++- web/public/languages/es_ES.json | 13 +++++++++++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index d922b07e7..a7b5fc9c7 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -112,15 +112,15 @@ class ProcurementsTable extends Table{ - - - - - - - - - + + + + + + + + + diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 2f56265bc..e4bc53627 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -261,5 +261,14 @@ "crd:landing:feedback:title": "Your feedback is welcome", "crd:landing:feedback:text": "We welcome your feedback on this prototype. Please contact Andrew Mandelbaum at DG if you have any questions or suggestions: amandelbaum@developmentgateway.org", "crd:landing:enter": "Enter!", - "crd:charts:totalFlags:title": "Total Flags:" + "crd:charts:totalFlags:title": "Total Flags:", + "crd:procurementsTable:status": "Status", + "crd:procurementsTable:contractID": "Contract ID", + "crd:procurementsTable:title": "Title", + "crd:procurementsTable:procuringEntity": "Procuring Entity", + "crd:procurementsTable:tenderAmount": "Tender Amount", + "crd:procurementsTable:awardsAmount": "Awards Amount", + "crd:procurementsTable:tenderDate": "Tender Date", + "crd:procurementsTable:flagType": "Flag Type", + "crd:procurementsTable:noOfFlags": "No. of Flags" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index ff91244a1..93c927182 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -142,7 +142,7 @@ "tables:top10suppliers:nrPE": "Number of PEs from which an award has been received", "tables:top10suppliers:totalAwardedValue": "Total awarded value", "tables:top10suppliers:title": "Top 10 suppliers", - "yearsBar:ctrlClickHint": "Para seleccionar un solo año y poder seleccionar varios meses, presione "shift" mientras hace clic en el año.", + "yearsBar:ctrlClickHint": "Para seleccionar un solo año y poder seleccionar varios meses, presione \"shift\" mientras hace clic en el año.", "crd:corruptionType:FRAUD:pageTitle": "Riesgo de fraude", "crd:corruptionType:FRAUD:name": "Fraude", "crd:corruptionType:FRAUD:introduction": "Según las Directrices del grupo de tareas contra la corrupción de las instituciones financieras internacionales (IFI por sus siglas en inglés), el fraude es \" Cuaquier acto u omisión, incluyendo una falsa representación, que de manera consciente o negligentemente engaña o intenta engañar a una parte para obtener un beneficio financiero o de otro tipo o para evitar una obligación \". Los proveedores pueden involucrarse en una variedad de actividades fraudulentas para aumentar sus probabilidades de ganar contratos o para demostrar progresos en la implementción. Algunos de estos esquemas de fraude incluyen: facturas falsas, ofertas falsas, sustitución de productos, contrataciones ficticias, y ofertas sombra. Las advertencias representadas en los cuadro siguientes indican la posibilidad de que exista un fraude en el proceso de contratación. Para saber más sobre cada advertencia, haga clic en el cuadro asociado.", @@ -261,5 +261,14 @@ "crd:landing:feedback:title": "Sus comentarios son bienvenidos", "crd:landing:feedback:text": "Agradecemos sus comentarios para este prototipo. Por favorcontacte a Andrew Mandelbaum de DG si tiene preguntas o sugerencias: amandelbaum@developmentgateway.org", "crd:landing:enter": "¡Ingresar!", - "crd:charts:totalFlags:title": "Advertencias totales:" + "crd:charts:totalFlags:title": "Advertencias totales:", + "crd:procurementsTable:status": "Estado", + "crd:procurementsTable:contractID": "Identificación del contrato", + "crd:procurementsTable:title": "Título", + "crd:procurementsTable:procuringEntity": "Entidad contratante", + "crd:procurementsTable:tenderAmount": "Cantidad de la licitación", + "crd:procurementsTable:awardsAmount": "Cantidad de adjudicaciones", + "crd:procurementsTable:tenderDate": "Fecha de licitación", + "crd:procurementsTable:flagType": "Tipo de advertencia", + "crd:procurementsTable:noOfFlags": "No. de advertencias" } From 172df34e5e9ae9350a50f0a455bbad0d4c6a88d8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 11:38:17 +0300 Subject: [PATCH 167/702] OCE-373 Procurements table popup translation --- ui/oce/corruption-risk/procurements-table.jsx | 2 +- web/public/languages/en_US.json | 3 ++- web/public/languages/es_ES.json | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index a7b5fc9c7..82e6d07a4 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -19,7 +19,7 @@ class Popup extends translatable(React.Component){
-
Associated {type[0] + type.substr(1).toLowerCase()} Flags
+
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}

diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index e4bc53627..b7494abb5 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -270,5 +270,6 @@ "crd:procurementsTable:awardsAmount": "Awards Amount", "crd:procurementsTable:tenderDate": "Tender Date", "crd:procurementsTable:flagType": "Flag Type", - "crd:procurementsTable:noOfFlags": "No. of Flags" + "crd:procurementsTable:noOfFlags": "No. of Flags", + "crd:procurementsTable:associatedFlags": "Associated $#$ flags" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 93c927182..c73af9b4c 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -270,5 +270,6 @@ "crd:procurementsTable:awardsAmount": "Cantidad de adjudicaciones", "crd:procurementsTable:tenderDate": "Fecha de licitación", "crd:procurementsTable:flagType": "Tipo de advertencia", - "crd:procurementsTable:noOfFlags": "No. de advertencias" + "crd:procurementsTable:noOfFlags": "No. de advertencias", + "crd:procurementsTable:associatedFlags": "Advertencias $#$ asociadas" } From 5be92b3f9eff49d3bc0f1da708ab14eeca856bbf Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 13:01:08 +0300 Subject: [PATCH 168/702] OCE-373 Overview chart trace translations --- ui/oce/corruption-risk/custom-popup-chart.jsx | 2 ++ ui/oce/corruption-risk/overview-page.jsx | 17 ++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/custom-popup-chart.jsx b/ui/oce/corruption-risk/custom-popup-chart.jsx index 33a79dcaa..a169831f8 100644 --- a/ui/oce/corruption-risk/custom-popup-chart.jsx +++ b/ui/oce/corruption-risk/custom-popup-chart.jsx @@ -26,6 +26,7 @@ class CustomPopupChart extends Chart { const point = data.points[0]; const year = point.x; const traceName = point.data.name; + const traceIndex = point.fullData.index; const POPUP_ARROW_SIZE = 8; const { xaxis, yaxis } = point; @@ -52,6 +53,7 @@ class CustomPopupChart extends Chart { left, year, traceName, + traceIndex }, }); } diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 0dc5b53ff..26c4e9c4e 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -8,13 +8,15 @@ import {colorLuminance} from "./tools"; const pluckObj = (field, obj) => Object.keys(obj).map(key => obj[key][field]); +const TRACES = ['COLLUSION', 'FRAUD', 'RIGGING']; + class CorruptionType extends CustomPopupChart{ groupData(data){ - let grouped = { - COLLUSION: {}, - FRAUD: {}, - RIGGING: {} - }; + let grouped = {}; + TRACES.forEach(trace => { + grouped[trace] = {}; + }); + const {monthly} = this.props; data.forEach(datum => { const type = datum.get('type'); @@ -63,7 +65,7 @@ class CorruptionType extends CustomPopupChart{ y: values, type: 'scatter', fill: 'tonexty', - name: type, + name: this.t(`crd:corruptionType:${type}:name`), fillcolor: styling.charts.traceColors[index], line: { color: colorLuminance(styling.charts.traceColors[index], -.3) @@ -91,7 +93,8 @@ class CorruptionType extends CustomPopupChart{ getPopup(){ const {popup} = this.state; - const {year, traceName: corruptionType} = popup; + const { year, traceIndex } = popup; + const corruptionType = TRACES[traceIndex]; const {indicatorTypesMapping} = this.props; const data = this.groupData(super.getData()); if(!data[corruptionType]) return null; From 6b646b5fb66f205418e38c9a34c231c2f90bd00f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 13:06:17 +0300 Subject: [PATCH 169/702] OCE-373 Individual indicator chart translations --- ui/oce/corruption-risk/individual-indicator.jsx | 4 ++-- web/public/languages/en_US.json | 4 +++- web/public/languages/es_ES.json | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx index 6cbaeaaa5..640d26631 100644 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ b/ui/oce/corruption-risk/individual-indicator.jsx @@ -41,7 +41,7 @@ class IndividualIndicatorChart extends CustomPopupChart{ y: totalTrueValues, type: 'scatter', fill: 'tonexty', - name: 'Flagged Procurements', + name: this.t('crd:individualIndicatorChart:flaggedProcurements'), hoverinfo: 'none', fillcolor: traceColors[0], line: { @@ -52,7 +52,7 @@ class IndividualIndicatorChart extends CustomPopupChart{ y: totalPrecondMetValues, type: 'scatter', fill: 'tonexty', - name: 'Eligible Procurements', + name: this.t('crd:individualIndicatorChart:eligibleProcurements'), hoverinfo: 'none', fillcolor: traceColors[1], line: { diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index b7494abb5..a63b45c7a 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -271,5 +271,7 @@ "crd:procurementsTable:tenderDate": "Tender Date", "crd:procurementsTable:flagType": "Flag Type", "crd:procurementsTable:noOfFlags": "No. of Flags", - "crd:procurementsTable:associatedFlags": "Associated $#$ flags" + "crd:procurementsTable:associatedFlags": "Associated $#$ flags", + "crd:individualIndicatorChart:flaggedProcurements": "Flagged Procurements", + "crd:individualIndicatorChart:eligibleProcurements": "Eligible Procurements" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index c73af9b4c..431b6b389 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -271,5 +271,8 @@ "crd:procurementsTable:tenderDate": "Fecha de licitación", "crd:procurementsTable:flagType": "Tipo de advertencia", "crd:procurementsTable:noOfFlags": "No. de advertencias", - "crd:procurementsTable:associatedFlags": "Advertencias $#$ asociadas" + "crd:procurementsTable:associatedFlags": "Advertencias $#$ asociadas", + "crd:individualIndicatorChart:flaggedProcurements": "Contrataciones con advertencias", + "crd:individualIndicatorChart:eligibleProcurements": "Contrataciones elegibles" + } From eae9f26046198b35c4ccfdbbc8fe574a03223551 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 14 Sep 2017 13:18:46 +0300 Subject: [PATCH 170/702] OCE-373 Remaining Spanish translations --- ui/oce/corruption-risk/filters/box.jsx | 4 ++-- ui/oce/corruption-risk/filters/date.jsx | 21 ++++++++++----------- ui/oce/filters/procurement-method.jsx | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ui/oce/corruption-risk/filters/box.jsx b/ui/oce/corruption-risk/filters/box.jsx index 6ab4d4fbd..e30de05c2 100644 --- a/ui/oce/corruption-risk/filters/box.jsx +++ b/ui/oce/corruption-risk/filters/box.jsx @@ -22,9 +22,9 @@ class FilterBox extends FilterTab{
e.stopPropagation()}> {this.getBox()}
- +   - +
} diff --git a/ui/oce/corruption-risk/filters/date.jsx b/ui/oce/corruption-risk/filters/date.jsx index f47d4ef8c..dbbc1fae1 100644 --- a/ui/oce/corruption-risk/filters/date.jsx +++ b/ui/oce/corruption-risk/filters/date.jsx @@ -49,17 +49,16 @@ class DateBox extends FilterBox{ return ( {year} ) - })} + })}

- To select a single year and be able to select months, - hold 'shift' while clicking on a year. + {this.t('yearsBar:ctrlClickHint')}

{selectedYears.count() == 1 && months.map(month => { @@ -73,14 +72,14 @@ class DateBox extends FilterBox{ } return ( + key={month} + className={cn('toggleable-item', {selected})} + onClick={toggleMonth} + > {this.t(`general:months:${month}`)} ) - })} + })}
) diff --git a/ui/oce/filters/procurement-method.jsx b/ui/oce/filters/procurement-method.jsx index ca048e322..aa8d08de0 100644 --- a/ui/oce/filters/procurement-method.jsx +++ b/ui/oce/filters/procurement-method.jsx @@ -2,7 +2,7 @@ import MultipleSelect from './inputs/multiple-select'; export default class ProcurementMethod extends MultipleSelect{ getTitle() { - return 'Procurement method'; + return this.t('filters:tabs:procurementMethod:title'); } getId(option) { From a70cf63d45e04acf3e35fc0cfbe34bde53c97971 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 15 Sep 2017 15:55:46 +0300 Subject: [PATCH 171/702] OCE-373 Translated login/logout and type ahead placeholder text --- ui/oce/corruption-risk/index.jsx | 8 ++++++-- web/public/languages/es_ES.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index b107148a3..517e725c5 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -159,12 +159,16 @@ class CorruptionRiskDashboard extends React.Component { if (this.state.user.loggedIn) { return ( - + ); } return ( - + ); } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 431b6b389..447c10aba 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -53,7 +53,7 @@ "filters:tenderPrice:title": "Precio de la licitación", "filters:multipleSelect:selectAll": "Seleccionar todo", "filters:multipleSelect:selectNone": "No seleccionar", - "filters:typeAhead:inputPlaceholder": "type search query", + "filters:typeAhead:inputPlaceholder": "Escriba el texto a buscar", "filters:typeAhead:result:sg": "result", "filters:typeAhead:result:pl": "results", "filters:tabs:amounts:title": "Amounts", From a5c09869efd188e6d53cae8638297d1afd9d6742 Mon Sep 17 00:00:00 2001 From: Alexei Date: Mon, 25 Sep 2017 13:11:36 +0300 Subject: [PATCH 172/702] OCUA-29 Fixed dashboard switcher not passing the event obj to the handler --- ui/oce/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 36b2ed803..5c5fa2a29 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -354,7 +354,7 @@ class OCApp extends React.Component { const { onSwitch } = this.props; return (
-

this.toggleDashboardSwitcher()}> +

this.toggleDashboardSwitcher(e)}> {this.t('general:title')} {this.t('general:subtitle')} From 1eab6741b51ea1ebc8a505f518ab24ad314e41a1 Mon Sep 17 00:00:00 2001 From: Alexei Date: Mon, 25 Sep 2017 13:48:57 +0300 Subject: [PATCH 173/702] OCUA-29 Locale defaults to 'en_US' if localstorage specifies and undefined locale --- ui/oce/corruption-risk/index.jsx | 8 +++++++- ui/oce/index.jsx | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 517e725c5..28c5d18ec 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -34,8 +34,14 @@ class CorruptionRiskDashboard extends React.Component { width: 0, data: Map(), showLandingPopup: !localStorage.alreadyVisited, - locale: localStorage.oceLocale || 'en_US', }; + const { oceLocale } = localStorage; + if (oceLocale && this.constructor.TRANSLATIONS[oceLocale]) { + this.state.locale = oceLocale; + } else { + this.state.locale = 'en_US'; + } + localStorage.alreadyVisited = true; this.destructFilters = cacheFn(filters => ({ diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 5c5fa2a29..9c0815205 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -19,7 +19,6 @@ class OCApp extends React.Component { this.state = { dashboardSwitcherOpen: false, exporting: false, - locale: localStorage.oceLocale || 'en_US', width: 0, currentTab: 0, menuBox: '', @@ -37,6 +36,12 @@ class OCApp extends React.Component { isAdmin: false, }, }; + const { oceLocale } = localStorage; + if (oceLocale && this.constructor.TRANSLATIONS[oceLocale]) { + this.state.locale = oceLocale; + } else { + this.state.locale = 'en_US'; + } } componentDidMount() { From c4dea02f1577e54e0a8786cb92956ec92800c633 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 25 Sep 2017 14:59:37 +0300 Subject: [PATCH 174/702] OCE-381 Implement release search based functionality endpoint for explorer --- .../mongo/constants/MongoConstants.java | 2 ++ .../AbstractMongoDatabaseConfiguration.java | 11 ++++++- .../controller/GenericOCDSController.java | 29 ++++++++++++------- .../web/rest/controller/OcdsController.java | 19 ++++++++---- .../request/DefaultFilterPagingRequest.java | 11 +++++++ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java index 37c214c0c..dee8572c8 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/constants/MongoConstants.java @@ -21,6 +21,8 @@ private MongoConstants() { public static final int IMPORT_ROW_BATCH = 1000; + public static final String MONGO_LANGUAGE = "english"; + public static final class FieldNames { public static final String TENDER_PERIOD_START_DATE = "tender.tenderPeriod.startDate"; public static final String TENDER_PERIOD_END_DATE = "tender.tenderPeriod.endDate"; diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java index f3414c1e7..57a4fbb28 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java @@ -68,6 +68,8 @@ public void createPostImportStructures() { createCorruptionFlagsIndexes(); + + // initialize some extra indexes getTemplate().indexOps(Release.class).ensureIndex(new Index().on("ocid", Direction.ASC).unique()); @@ -94,9 +96,16 @@ public void createPostImportStructures() { getTemplate().indexOps(Release.class).ensureIndex(new Index(). on("tender.items.deliveryLocation.geometry.coordinates", Direction.ASC)); - getTemplate().indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder().onField("name") + getTemplate().indexOps(Organization.class).ensureIndex(new TextIndexDefinitionBuilder() + .withDefaultLanguage(MongoConstants.MONGO_LANGUAGE) + .onField("name") .onField("id").onField("additionalIdentifiers._id").build()); + getTemplate().indexOps(Release.class).ensureIndex(new TextIndexDefinitionBuilder() + .withDefaultLanguage(MongoConstants.MONGO_LANGUAGE) + .onField("tender.title") + .onField("tender.description").onField("tender.procuringEntity.name").build()); + getLogger().info("Added extra Mongo indexes"); ScriptOperations scriptOps = getTemplate().scriptOps(); diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index 8d68b36db..8dd48c1cd 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -5,8 +5,20 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.PostConstruct; import org.apache.commons.lang3.ArrayUtils; import org.devgateway.ocds.persistence.mongo.Tender; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.web.rest.controller.request.DefaultFilterPagingRequest; import org.devgateway.ocds.web.rest.controller.request.GroupingFilterPagingRequest; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; @@ -20,18 +32,9 @@ import org.springframework.data.mongodb.core.aggregation.MatchOperation; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.data.mongodb.core.query.TextCriteria; -import javax.annotation.PostConstruct; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; @@ -333,6 +336,10 @@ protected Criteria getProcuringEntityIdCriteria(final DefaultFilterPagingRequest return createFilterCriteria("tender.procuringEntity._id", filter.getProcuringEntityId(), filter); } + protected CriteriaDefinition getTextCriteria(DefaultFilterPagingRequest filter) { + return TextCriteria.forLanguage(MongoConstants.MONGO_LANGUAGE).matchingAny(filter.getText()); + } + /** * Adds the filter by electronic submission criteria for tender.submissionMethod. * diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index e54503760..2cea363be 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -13,6 +13,10 @@ import com.fasterxml.jackson.annotation.JsonView; import io.swagger.annotations.ApiOperation; +import java.util.ArrayList; +import java.util.List; +import javax.validation.Valid; +import org.apache.commons.lang3.StringUtils; import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; @@ -23,15 +27,13 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; -import java.util.ArrayList; -import java.util.List; import static org.springframework.data.mongodb.core.query.Query.query; @@ -118,7 +120,7 @@ public ReleasePackage packagedReleaseByProjectId(@PathVariable final String proj * @return the release data */ @ApiOperation(value = "Resturns all available releases, filtered by the given criteria.") - @RequestMapping(value = "/api/ocds/release/all", method = { RequestMethod.POST, RequestMethod.GET }, + @RequestMapping(value = "/api/ocds/release/all", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { @@ -126,8 +128,13 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); - List find = mongoTemplate - .find(query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest), Release.class); + Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); + + if (StringUtils.isNotEmpty(releaseRequest.getText())) { + query.addCriteria(getTextCriteria(releaseRequest)); + } + + List find = mongoTemplate.find(query, Release.class); return find; diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java index 072a1ee1b..a8b0cb2f0 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java @@ -14,6 +14,9 @@ */ public class DefaultFilterPagingRequest extends GenericPagingRequest { + @ApiModelProperty(value = "Full text search of of release entities") + private String text; + @EachPattern(regexp = "^[a-zA-Z0-9]*$") @ApiModelProperty(value = "This corresponds to the tender.items.classification._id") private TreeSet bidTypeId; @@ -73,6 +76,14 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { @ApiModelProperty(value = "Only show the releases that were flagged by at least one indicator") private Boolean flagged; + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + public TreeSet getFlagType() { return flagType; } From c349db176ab858473174fd71ff21a6b14980a71d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Sep 2017 15:55:04 +0300 Subject: [PATCH 175/702] OCE-383 M&E is now English only --- ui/oce-standalone/index.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/oce-standalone/index.jsx b/ui/oce-standalone/index.jsx index 5d3159d18..7b1768a8a 100644 --- a/ui/oce-standalone/index.jsx +++ b/ui/oce-standalone/index.jsx @@ -180,8 +180,7 @@ class OCEChild extends OCApp{ } const translations = { - en_US: require('../../web/public/languages/en_US.json'), - es_ES: require('../../web/public/languages/es_ES.json') + en_US: require('../../web/public/languages/en_US.json') }; const BILLION = 1000000000; From 1684cc132f18da7e77c6ff244b4d8695a8807225 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Sep 2017 15:58:36 +0300 Subject: [PATCH 176/702] OCE-383 Reverse compatible redirect from old CRD url to the new one --- ui/oce/index.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index 9c0815205..6a98a8bfb 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -11,6 +11,10 @@ const MENU_BOX_COMPARISON = 'menu-box'; const MENU_BOX_FILTERS = 'filters'; const ROLE_ADMIN = 'ROLE_ADMIN'; +if (location.search === '?corruption-risk-dashboard') { + location = '/ui/index.html#!/crd' +} + // eslint-disable-next-line no-undef class OCApp extends React.Component { constructor(props) { From a4b0d13c3177dbfe494a6e41bfcfb7bdfc1f1792 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 25 Sep 2017 17:55:41 +0300 Subject: [PATCH 177/702] OCE-381 Implement release search based functionality endpoint for explorer - added more text search properties --- .../mongo/spring/AbstractMongoDatabaseConfiguration.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java index 57a4fbb28..fd0556635 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java +++ b/persistence-mongodb/src/main/java/org/devgateway/toolkit/persistence/mongo/spring/AbstractMongoDatabaseConfiguration.java @@ -102,9 +102,13 @@ public void createPostImportStructures() { .onField("id").onField("additionalIdentifiers._id").build()); getTemplate().indexOps(Release.class).ensureIndex(new TextIndexDefinitionBuilder() + .named("text_search") .withDefaultLanguage(MongoConstants.MONGO_LANGUAGE) - .onField("tender.title") - .onField("tender.description").onField("tender.procuringEntity.name").build()); + .onFields("tender.title", "tender.description", + "tender.procuringEntity.name", "tender.id", "tender.procuringEntity.description", + "awards.id", "awards.description", "awards.suppliers.name", "awards.suppliers.description", + "ocid", "buyer.name", "buyer.id" + ).build()); getLogger().info("Added extra Mongo indexes"); From a10f025a27473bccfaab0c127dfd2fc32a10c303 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Sep 2017 15:35:32 +0300 Subject: [PATCH 178/702] OCE-377 OCE-375 Contracts listing page. First listing page out of listables --- ui/oce/corruption-risk/contracts.jsx | 51 ++++++++++++++++++++++++++++ ui/oce/corruption-risk/index.jsx | 8 +++++ 2 files changed, 59 insertions(+) create mode 100644 ui/oce/corruption-risk/contracts.jsx diff --git a/ui/oce/corruption-risk/contracts.jsx b/ui/oce/corruption-risk/contracts.jsx new file mode 100644 index 000000000..a4815f83a --- /dev/null +++ b/ui/oce/corruption-risk/contracts.jsx @@ -0,0 +1,51 @@ +import CRDPage from './page'; +import Visualization from '../visualization'; +import { List } from 'immutable'; + +class CList extends Visualization { + render() { + const { data, navigate } = this.props; + return ( +
+ {data.map(contract => { + const id = contract.get('ocid'); + return ( +

+ navigate('contract', id)} + key={id} + > + {contract.getIn(['tender', 'title'], id)} + +

+ ) + })} +
+ ); + } +} + +CList.endpoint = 'ocds/release/all'; + +export default class Contracts extends CRDPage { + constructor(...args){ + super(...args); + this.state = { + list: List() + } + } + + render() { + const { list } = this.state; + const { filters, navigate } = this.props; + return ( + this.setState({list})} + navigate={navigate} + /> + ); + } +} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 28c5d18ec..9965b7b51 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -6,6 +6,7 @@ import { fetchJson, debounce, cacheFn, range, pluck, callFunc } from '../tools'; import OverviewPage from './overview-page'; import CorruptionTypePage from './corruption-type'; import IndividualIndicatorPage from './individual-indicator'; +import ContractsPage from './contracts'; import Filters from './filters'; import TotalFlags from './total-flags'; import LandingPopup from './landing-popup'; @@ -145,6 +146,13 @@ class CorruptionRiskDashboard extends React.Component { styling={styling} /> ); + } else if (page === 'contracts') { + return ( + + ); } else { return ( Date: Wed, 27 Sep 2017 18:57:52 +0300 Subject: [PATCH 179/702] OCE-378 Contract page stub --- ui/oce/corruption-risk/contract.jsx | 58 +++++++++++++++++++++++++++++ ui/oce/corruption-risk/index.jsx | 8 ++++ 2 files changed, 66 insertions(+) create mode 100644 ui/oce/corruption-risk/contract.jsx diff --git a/ui/oce/corruption-risk/contract.jsx b/ui/oce/corruption-risk/contract.jsx new file mode 100644 index 000000000..55101b684 --- /dev/null +++ b/ui/oce/corruption-risk/contract.jsx @@ -0,0 +1,58 @@ +import CRDPage from './page'; +import Visualization from '../visualization'; +import { Map } from 'immutable'; + +class Info extends Visualization { + getCustomEP(){ + const { id } = this.props; + return `ocds/release/ocid/${id}`; + } + + render() { + const { data } = this.props; + const title = data.getIn(['tender', 'title']); + /* + Contract Description //where do I find it? + Procuring Entity + Buyer + Supplier + Status + Amounts + Dates + */ + return ( +
+
+
Contract ID
+
{data.get('ocid')}
+ {title &&
Contract Title
} + {title &&
{title}
} +
+
+ ); + } +} + +export default class Contract extends CRDPage { + constructor(...args){ + super(...args); + this.state = { + contract: Map() + } + } + + render() { + const { contract } = this.state; + const { id } = this.props; + return ( +
+ this.setState({contract})} + /> +
+ ); + } +} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 9965b7b51..2246e3f6c 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -7,6 +7,7 @@ import OverviewPage from './overview-page'; import CorruptionTypePage from './corruption-type'; import IndividualIndicatorPage from './individual-indicator'; import ContractsPage from './contracts'; +import ContractPage from './contract'; import Filters from './filters'; import TotalFlags from './total-flags'; import LandingPopup from './landing-popup'; @@ -153,6 +154,13 @@ class CorruptionRiskDashboard extends React.Component { navigate={navigate} /> ); + } else if (page === 'contract') { + const [, contractId] = route; + return ( + + ) } else { return ( Date: Wed, 27 Sep 2017 19:13:54 +0300 Subject: [PATCH 180/702] OCE-378 Translations for contract pages --- ui/oce/corruption-risk/contract.jsx | 10 ++++++---- ui/oce/corruption-risk/index.jsx | 12 +++++++++++- web/public/languages/en_US.json | 4 +++- web/public/languages/es_ES.json | 5 +++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contract.jsx b/ui/oce/corruption-risk/contract.jsx index 55101b684..1715e0ff8 100644 --- a/ui/oce/corruption-risk/contract.jsx +++ b/ui/oce/corruption-risk/contract.jsx @@ -1,8 +1,9 @@ import CRDPage from './page'; import Visualization from '../visualization'; import { Map } from 'immutable'; +import translatable from '../translatable'; -class Info extends Visualization { +class Info extends translatable(Visualization) { getCustomEP(){ const { id } = this.props; return `ocds/release/ocid/${id}`; @@ -23,9 +24,9 @@ class Info extends Visualization { return (
-
Contract ID
+
{this.t('crd:procurementsTable:contractID')}
{data.get('ocid')}
- {title &&
Contract Title
} + {title &&
{this.t('crd:general:contract:title')}
} {title &&
{title}
}
@@ -43,7 +44,7 @@ export default class Contract extends CRDPage { render() { const { contract } = this.state; - const { id } = this.props; + const { id, translations } = this.props; return (
this.setState({contract})} + translations={translations} />
); diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 2246e3f6c..611fd224d 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -159,6 +159,7 @@ class CorruptionRiskDashboard extends React.Component { return ( ) } else { @@ -334,12 +335,21 @@ class CorruptionRiskDashboard extends React.Component { onClick={() => navigate('type', slug)} className={cn({ active: page === 'type' && slug === corruptionType })} key={slug} - > + > Tab icon {this.t(`crd:corruptionType:${slug}:name`)} ({count}) ); })} + navigate('contracts')} + className={cn({ active: page === 'contracts' })} + key="contracts" + > + Contracts icon + {this.t('crd:general:contracts')} (0) + Date: Thu, 28 Sep 2017 18:05:23 +0300 Subject: [PATCH 181/702] fixes #181 color picker --- .../ColorPickerBootstrapFormComponent.html | 5 ++ .../ColorPickerBootstrapFormComponent.java | 69 +++++++++++++++++++ .../forms/wicket/page/EditTestFormPage.html | 1 + .../forms/wicket/page/EditTestFormPage.java | 15 ++-- .../wicket/page/EditTestFormPage.properties | 1 + .../toolkit/persistence/dao/TestForm.java | 10 +++ 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html new file mode 100644 index 000000000..dcc4a01c3 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java new file mode 100644 index 000000000..24b5b094e --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/ColorPickerBootstrapFormComponent.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +/** + * + */ +package org.devgateway.toolkit.forms.wicket.components.form; + +import de.agilecoders.wicket.core.util.Attributes; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.ColorPickerTextField; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.model.IModel; + +/** + * @author mpostelnicu + * + */ +public class ColorPickerBootstrapFormComponent extends GenericBootstrapFormComponent { + + private Boolean isFloatedInput = false; + + public ColorPickerBootstrapFormComponent(final String id, final IModel labelModel, + final IModel model) { + super(id, labelModel, model); + } + + public ColorPickerBootstrapFormComponent(final String id, final IModel model) { + super(id, model); + } + + /** + * @param id + */ + public ColorPickerBootstrapFormComponent(final String id) { + super(id); + } + + @Override + protected ColorPickerTextField inputField(final String id, final IModel model) { + return new ColorPickerTextField(id, initFieldModel()); + } + + @Override + protected void onComponentTag(final ComponentTag tag) { + super.onComponentTag(tag); + + if (getIsFloatedInput()) { + Attributes.addClass(tag, "floated-input"); + } + } + + + public Boolean getIsFloatedInput() { + return isFloatedInput; + } + + public void setIsFloatedInput(final Boolean isFloatedInput) { + this.isFloatedInput = isFloatedInput; + } + +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html index 10e527d6d..6187932b4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.html @@ -18,6 +18,7 @@
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java index ea817b21d..d844ae101 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java @@ -1,16 +1,16 @@ -/******************************************************************************* +/** * Copyright (c) 2015 Development Gateway, Inc and others. - * + *

* All rights reserved. This program and the accompanying materials * are made available under the terms of the MIT License (MIT) * which accompanies this distribution, and is available at * https://opensource.org/licenses/MIT - * + *

* Contributors: * Development Gateway - initial API and implementation - *******************************************************************************/ + */ /** - * + * */ package org.devgateway.toolkit.forms.wicket.page; @@ -21,6 +21,7 @@ import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxPickerBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.ColorPickerBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.DateFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.DateTimeFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.components.form.FileInputBootstrapFormComponent; @@ -133,6 +134,10 @@ protected void onInitialize() { "preloadedEntitySelect", new GenericChoiceProvider<>(groupRepository.findAll())); preloadedEntitySelect.required(); editForm.add(preloadedEntitySelect); + + ColorPickerBootstrapFormComponent colorPicker = new ColorPickerBootstrapFormComponent("colorPicker"); + colorPicker.required(); + editForm.add(colorPicker); } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties index 5c7e00de5..252f53f1a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.properties @@ -23,3 +23,4 @@ dateTime.label=Datetime fileInput.label=File Input summernote.label=Summernote Editor preloadedEntitySelect.label=Preloaded Entity Select +colorPicker.label=Color Picker diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java index 720b694b3..f6aeb1f0a 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java @@ -58,6 +58,8 @@ public class TestForm extends AbstractAuditableEntity implements Serializable { private Boolean checkboxToggle; + private String colorPicker; + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @ManyToOne private Group entitySelect; @@ -182,4 +184,12 @@ public Group getPreloadedEntitySelect() { public void setPreloadedEntitySelect(final Group preloadedEntitySelect) { this.preloadedEntitySelect = preloadedEntitySelect; } + + public String getColorPicker() { + return colorPicker; + } + + public void setColorPicker(final String colorPicker) { + this.colorPicker = colorPicker; + } } From d2368adc5e5a1141e3202dce83c1f07354f310f2 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 28 Sep 2017 18:22:04 +0300 Subject: [PATCH 182/702] OCE-364 --- .../page/edit/EditColorIndicatorPairPage.html | 12 +++ .../page/edit/EditColorIndicatorPairPage.java | 81 +++++++++++++++++++ .../EditColorIndicatorPairPage.properties | 20 +++++ .../persistence/dao/ColorIndicatorPair.java | 57 +++++++++++++ .../ColorIndicatorPairRepository.java | 53 ++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html create mode 100644 forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java create mode 100644 forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties create mode 100644 persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java create mode 100644 persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html new file mode 100644 index 000000000..ed679add1 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html @@ -0,0 +1,12 @@ + + + + +EditUserDashboardPage + + + +

+ + + diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java new file mode 100644 index 000000000..02702a01c --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.edit; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; +import org.devgateway.ocds.forms.wicket.providers.LabelPersistableJpaRepositoryTextChoiceProvider; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.ocds.persistence.repository.ColorIndicatorPairRepository; +import org.devgateway.toolkit.forms.wicket.components.form.Select2MultiChoiceBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextAreaFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; +import org.devgateway.toolkit.persistence.dao.Person; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.devgateway.toolkit.web.security.SecurityConstants; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath("/editColorIndicatorPairPage") +public class EditColorIndicatorPairPage extends AbstractEditPage { + + private static final long serialVersionUID = -6069250112046118104L; + + @Override + protected ColorIndicatorPair newInstance() { + return new ColorIndicatorPair(); + } + + @SpringBean + private ColorIndicatorPairRepository userDashboardRepository; + + @SpringBean + private PersonRepository personRepository; + + public EditColorIndicatorPairPage(final PageParameters parameters) { + super(parameters); + this.jpaRepository = userDashboardRepository; + this.listPageClass = ListAllDashboardsPage.class; + + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + TextFieldBootstrapFormComponent name = new TextFieldBootstrapFormComponent<>("name"); + name.required(); + editForm.add(name); + + TextAreaFieldBootstrapFormComponent formUrlEncodedBody = + new TextAreaFieldBootstrapFormComponent<>("formUrlEncodedBody"); + formUrlEncodedBody.required(); + formUrlEncodedBody.getField().setEnabled(false); + editForm.add(formUrlEncodedBody); + + Select2MultiChoiceBootstrapFormComponent defaultDashboardUsers = + new Select2MultiChoiceBootstrapFormComponent<>("defaultDashboardUsers", + new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); + defaultDashboardUsers.setEnabled(false); + editForm.add(defaultDashboardUsers); + + Select2MultiChoiceBootstrapFormComponent users = + new Select2MultiChoiceBootstrapFormComponent<>("users", + new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); + editForm.add(users); + + + } +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties new file mode 100644 index 000000000..2522f3134 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties @@ -0,0 +1,20 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=Edit User Dashboard +name.label=Name +formUrlEncodedBody.label=Filter body +formUrlEncodedBody.help=This is a list of saved filters from the UI and cannot be edited here. +defaultDashboardUsers.label=Default Dashboard For Users +defaultDashboardUsers.help=This is a user setting and can be changed from the User form +users.label=Assigned Users +delete_error_message=Cannot delete this dashboard. It is assigned as the default dashboard for at least one user.\ + Please assign a different default dashboard for the assigned users or select no default dashboard, and then try the delete again. \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java new file mode 100644 index 000000000..a65377fdd --- /dev/null +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java @@ -0,0 +1,57 @@ +/** + * + */ +package org.devgateway.ocds.persistence.dao; + +import java.io.Serializable; +import javax.persistence.Entity; +import org.devgateway.toolkit.persistence.dao.AbstractAuditableEntity; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.envers.Audited; + +/** + * @author mpostelnicu + * + */ +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +@Entity +@Audited +public class ColorIndicatorPair extends AbstractAuditableEntity implements Serializable { + + private String first; + + private String second; + + private String color; + + + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + public String getSecond() { + return second; + } + + public void setSecond(String second) { + this.second = second; + } + + @Override + public AbstractAuditableEntity getParent() { + return null; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java new file mode 100644 index 000000000..750f117bd --- /dev/null +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.persistence.repository; + +import java.util.List; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.toolkit.persistence.repository.category.TextSearchableRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; + +/** + * + * @author mpostelnicu + * + */ +@Transactional +@RepositoryRestResource +public interface ColorIndicatorPairRepository extends TextSearchableRepository { + + @Override + List findAll(); + + @Override + Page findAll(Pageable pageable); + + @Override + @RestResource(exported = false) + @PreAuthorize("hasRole('ROLE_ADMIN')") + void delete(Long id); + + @Override + @RestResource(exported = false) + @PreAuthorize("hasRole('ROLE_ADMIN')") + void deleteAll(); + + @RestResource(exported = true) + @Override + ColorIndicatorPair getOne(Long id); + +} From c0857fb5cbcbf367a18e8dee28f04c6c00faabfb Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 29 Sep 2017 11:47:03 +0300 Subject: [PATCH 183/702] fixes #183 --- forms/pom.xml | 2 +- ui/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forms/pom.xml b/forms/pom.xml index 246c2c026..b9be00a5d 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -29,7 +29,7 @@ UTF-8 1.8 - 7.8.0 + 7.9.0 0.10.16 1.12 0.5.6 diff --git a/ui/pom.xml b/ui/pom.xml index 5b258eb00..8695fbeed 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -84,7 +84,7 @@ com.github.eirslett frontend-maven-plugin - 1.5 + 1.6 @@ -94,7 +94,7 @@ generate-resources - v6.11.2 + v6.11.3 3.10.10 From 025c683c115449610beec71c7ef5cc3dffca8e38 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Sep 2017 13:04:32 +0300 Subject: [PATCH 184/702] OCE-378 Styling for contract --- .../{contract.jsx => contract/index.jsx} | 9 +++++---- ui/oce/corruption-risk/contract/style.less | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) rename ui/oce/corruption-risk/{contract.jsx => contract/index.jsx} (85%) create mode 100644 ui/oce/corruption-risk/contract/style.less diff --git a/ui/oce/corruption-risk/contract.jsx b/ui/oce/corruption-risk/contract/index.jsx similarity index 85% rename from ui/oce/corruption-risk/contract.jsx rename to ui/oce/corruption-risk/contract/index.jsx index 1715e0ff8..c6e300932 100644 --- a/ui/oce/corruption-risk/contract.jsx +++ b/ui/oce/corruption-risk/contract/index.jsx @@ -1,7 +1,8 @@ -import CRDPage from './page'; -import Visualization from '../visualization'; +import CRDPage from '../page'; +import Visualization from '../../visualization'; import { Map } from 'immutable'; -import translatable from '../translatable'; +import translatable from '../../translatable'; +import styles from './style.less'; class Info extends translatable(Visualization) { getCustomEP(){ @@ -46,7 +47,7 @@ export default class Contract extends CRDPage { const { contract } = this.state; const { id, translations } = this.props; return ( -
+
Date: Fri, 29 Sep 2017 14:44:41 +0300 Subject: [PATCH 185/702] OCE-364 Change shading system on crosstabs to highlight priorities --- .../page/edit/EditColorIndicatorPairPage.html | 4 +- .../page/edit/EditColorIndicatorPairPage.java | 107 +++++++++++++----- .../EditColorIndicatorPairPage.properties | 15 +-- .../page/list/ListAllColorIndicatorPage.java | 50 ++++++++ .../list/ListAllColorIndicatorPage.properties | 15 +++ .../toolkit/forms/wicket/page/BasePage.java | 6 + .../forms/wicket/page/BasePage.properties | 3 +- .../persistence/dao/ColorIndicatorPair.java | 27 ++--- .../ColorIndicatorPairRepository.java | 18 ++- 9 files changed, 191 insertions(+), 54 deletions(-) create mode 100644 forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java create mode 100644 forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html index ed679add1..eb8bec1d8 100644 --- a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.html @@ -6,7 +6,9 @@ -
+
+
+
diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java index 02702a01c..422bdc644 100644 --- a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.java @@ -12,17 +12,20 @@ package org.devgateway.ocds.forms.wicket.page.edit; import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.validation.AbstractFormValidator; +import org.apache.wicket.model.IModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; -import org.devgateway.ocds.forms.wicket.providers.LabelPersistableJpaRepositoryTextChoiceProvider; +import org.devgateway.ocds.forms.wicket.page.list.ListAllColorIndicatorPage; import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; import org.devgateway.ocds.persistence.repository.ColorIndicatorPairRepository; -import org.devgateway.toolkit.forms.wicket.components.form.Select2MultiChoiceBootstrapFormComponent; -import org.devgateway.toolkit.forms.wicket.components.form.TextAreaFieldBootstrapFormComponent; -import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.ColorPickerBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; -import org.devgateway.toolkit.persistence.dao.Person; +import org.devgateway.toolkit.forms.wicket.providers.GenericChoiceProvider; import org.devgateway.toolkit.persistence.repository.PersonRepository; import org.devgateway.toolkit.web.security.SecurityConstants; import org.wicketstuff.annotation.mount.MountPath; @@ -39,43 +42,93 @@ protected ColorIndicatorPair newInstance() { } @SpringBean - private ColorIndicatorPairRepository userDashboardRepository; + private ColorIndicatorPairRepository colorIndicatorPairRepository; @SpringBean private PersonRepository personRepository; + private Select2ChoiceBootstrapFormComponent firstIndicator; + + private Select2ChoiceBootstrapFormComponent secondIndicator; + public EditColorIndicatorPairPage(final PageParameters parameters) { super(parameters); - this.jpaRepository = userDashboardRepository; - this.listPageClass = ListAllDashboardsPage.class; + this.jpaRepository = colorIndicatorPairRepository; + this.listPageClass = ListAllColorIndicatorPage.class; } + @Override protected void onInitialize() { super.onInitialize(); - TextFieldBootstrapFormComponent name = new TextFieldBootstrapFormComponent<>("name"); - name.required(); - editForm.add(name); + firstIndicator = + new Select2ChoiceBootstrapFormComponent("firstIndicator", + new GenericChoiceProvider(FlagsConstants.FLAGS_LIST)); + firstIndicator.required(); + editForm.add(firstIndicator); + + secondIndicator = + new Select2ChoiceBootstrapFormComponent("secondIndicator", + new GenericChoiceProvider(FlagsConstants.FLAGS_LIST)); + secondIndicator.required(); + editForm.add(secondIndicator); + + ColorPickerBootstrapFormComponent color = new ColorPickerBootstrapFormComponent("color"); + color.required(); + editForm.add(color); + editForm.add(new ColorIndicatorDistinctFormValidator()); + editForm.add(new ColorIndicatorUniquePairFormValidator(compoundModel)); + + } + + + private class ColorIndicatorDistinctFormValidator extends AbstractFormValidator { + + @Override + public FormComponent[] getDependentFormComponents() { + return new FormComponent[]{firstIndicator.getField(), secondIndicator.getField()}; + } + + @Override + public void validate(Form form) { + if (firstIndicator.getField().getValue() != null && secondIndicator.getField().getValue() != null + && firstIndicator.getField().getValue().equals(secondIndicator.getField().getValue())) { + error(firstIndicator.getField()); + error(secondIndicator.getField()); + } + } + } + + + private class ColorIndicatorUniquePairFormValidator extends AbstractFormValidator { + + private final IModel masterModel; - TextAreaFieldBootstrapFormComponent formUrlEncodedBody = - new TextAreaFieldBootstrapFormComponent<>("formUrlEncodedBody"); - formUrlEncodedBody.required(); - formUrlEncodedBody.getField().setEnabled(false); - editForm.add(formUrlEncodedBody); + ColorIndicatorUniquePairFormValidator(IModel masterModel) { + this.masterModel = masterModel; + } - Select2MultiChoiceBootstrapFormComponent defaultDashboardUsers = - new Select2MultiChoiceBootstrapFormComponent<>("defaultDashboardUsers", - new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); - defaultDashboardUsers.setEnabled(false); - editForm.add(defaultDashboardUsers); + @Override + public FormComponent[] getDependentFormComponents() { + return new FormComponent[]{firstIndicator.getField(), secondIndicator.getField()}; + } - Select2MultiChoiceBootstrapFormComponent users = - new Select2MultiChoiceBootstrapFormComponent<>("users", - new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); - editForm.add(users); + @Override + public void validate(Form form) { + if (firstIndicator.getField().getValue() != null && secondIndicator.getField().getValue() != null) { + ColorIndicatorPair indicator = colorIndicatorPairRepository. + findByFirstIndicatorAndSecondIndicator(firstIndicator.getField().getValue(), + secondIndicator.getField().getValue()); - + if ((masterModel.getObject().isNew() && indicator != null) + || (!masterModel.getObject().isNew() && indicator != null + && !indicator.getId().equals(masterModel.getObject().getId()))) { + error(firstIndicator.getField()); + error(secondIndicator.getField()); + } + } + } } } diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties index 2522f3134..35fb0cdfc 100644 --- a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditColorIndicatorPairPage.properties @@ -9,12 +9,9 @@ # Contributors: # Development Gateway - initial API and implementation ############################################################################### -page.title=Edit User Dashboard -name.label=Name -formUrlEncodedBody.label=Filter body -formUrlEncodedBody.help=This is a list of saved filters from the UI and cannot be edited here. -defaultDashboardUsers.label=Default Dashboard For Users -defaultDashboardUsers.help=This is a user setting and can be changed from the User form -users.label=Assigned Users -delete_error_message=Cannot delete this dashboard. It is assigned as the default dashboard for at least one user.\ - Please assign a different default dashboard for the assigned users or select no default dashboard, and then try the delete again. \ No newline at end of file +page.title=Edit Color Indicator Pair +firstIndicator.label=First Indicator +secondIndicator.label=Second Indicator +color.label=Color +ColorIndicatorDistinctFormValidator=Please select two distinct indicators +ColorIndicatorUniquePairFormValidator=A similar pair of indicators was already saved with a color. You can add only distinct pairs. \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java new file mode 100644 index 000000000..b8ae91a18 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.list; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.edit.EditColorIndicatorPairPage; +import org.devgateway.ocds.persistence.dao.ColorIndicatorPair; +import org.devgateway.ocds.persistence.repository.ColorIndicatorPairRepository; +import org.devgateway.toolkit.forms.wicket.page.lists.AbstractListPage; +import org.devgateway.toolkit.web.security.SecurityConstants; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath(value = "/listColorIndicators") +public class ListAllColorIndicatorPage extends AbstractListPage { + + @SpringBean + protected ColorIndicatorPairRepository colorIndicatorPairRepository; + + + + public ListAllColorIndicatorPage(final PageParameters pageParameters) { + super(pageParameters); + this.jpaRepository = colorIndicatorPairRepository; + this.editPageClass = EditColorIndicatorPairPage.class; + columns.add(new PropertyColumn(new Model("First Indicator"), + "firstIndicator", "firstIndicator")); + + columns.add(new PropertyColumn(new Model("Second Indicator"), + "secondIndicator", "secondIndicator")); + + columns.add(new PropertyColumn(new Model("Color"), + "color", "color")); + + } + +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties new file mode 100644 index 000000000..ed3f01ed0 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllColorIndicatorPage.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=All Color Indicator Pairs +defaultDashboardUsers=Default Dashboard For Users +users=Users +view=View \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java index 3e0b29814..885c6330a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java @@ -47,6 +47,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.resource.JQueryResourceReference; import org.apache.wicket.util.string.StringValue; +import org.devgateway.ocds.forms.wicket.page.list.ListAllColorIndicatorPage; import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; import org.devgateway.ocds.forms.wicket.page.list.ListMyDashboardsPage; import org.devgateway.toolkit.forms.WebConstants; @@ -284,6 +285,11 @@ protected List newSubMenuButtons(final String arg0) { list.add(new MenuBookmarkablePageLink(ListUserPage.class, null, new StringResourceModel("navbar.users", this, null)).setIconType(FontAwesomeIconType.users)); + list.add(new MenuBookmarkablePageLink(ListAllColorIndicatorPage.class, null, + new StringResourceModel("navbar.colorindicators", this, null)) + .setIconType(FontAwesomeIconType.paint_brush)); + + list.add(new MenuBookmarkablePageLink(SpringEndpointsPage.class, null, new StringResourceModel("navbar.springendpoints", this, null)) .setIconType(FontAwesomeIconType.anchor)); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties index affdef1c6..42185791c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties @@ -23,4 +23,5 @@ home=Home navbar.lang=Language navbar.jminix=JMX Console navbar.allDashboard=All Saved Dashboards -mydashboards=My Dashboards \ No newline at end of file +mydashboards=My Dashboards +navbar.colorindicators=Color Indicator Pairs \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java index a65377fdd..158baec3d 100644 --- a/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/dao/ColorIndicatorPair.java @@ -1,10 +1,12 @@ /** - * + * */ package org.devgateway.ocds.persistence.dao; import java.io.Serializable; import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; import org.devgateway.toolkit.persistence.dao.AbstractAuditableEntity; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -12,34 +14,33 @@ /** * @author mpostelnicu - * */ @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @Entity @Audited +@Table(indexes = {@Index(columnList = "firstIndicator"), @Index(columnList = "secondIndicator")}) public class ColorIndicatorPair extends AbstractAuditableEntity implements Serializable { - private String first; + private String firstIndicator; - private String second; + private String secondIndicator; private String color; - - public String getFirst() { - return first; + public String getFirstIndicator() { + return firstIndicator; } - public void setFirst(String first) { - this.first = first; + public void setFirstIndicator(String firstIndicator) { + this.firstIndicator = firstIndicator; } - public String getSecond() { - return second; + public String getSecondIndicator() { + return secondIndicator; } - public void setSecond(String second) { - this.second = second; + public void setSecondIndicator(String secondIndicator) { + this.secondIndicator = secondIndicator; } @Override diff --git a/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java index 750f117bd..d77940bc7 100644 --- a/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java +++ b/persistence/src/main/java/org/devgateway/ocds/persistence/repository/ColorIndicatorPairRepository.java @@ -16,15 +16,15 @@ import org.devgateway.toolkit.persistence.repository.category.TextSearchableRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; /** - * * @author mpostelnicu - * */ @Transactional @RepositoryRestResource @@ -41,11 +41,23 @@ public interface ColorIndicatorPairRepository extends TextSearchableRepository searchText(@Param("txt") String txt, Pageable page); + @Override @RestResource(exported = false) @PreAuthorize("hasRole('ROLE_ADMIN')") void deleteAll(); - + @RestResource(exported = true) @Override ColorIndicatorPair getOne(Long id); From b7fbcf4693b6890c967aff7d76c8509477e61ccb Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 2 Oct 2017 18:14:19 +0300 Subject: [PATCH 186/702] OCE-382 Create endpoint for the new flag information section in contract page --- .../PercentageAmountAwardedController.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java new file mode 100644 index 000000000..1ca12a779 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.web.rest.controller; + +import com.mongodb.DBObject; +import io.swagger.annotations.ApiOperation; +import java.util.List; +import javax.validation.Valid; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.facet; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * + * @author mpostelnicu + * + */ +@RestController +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") +@Cacheable +public class PercentageAmountAwardedController extends GenericOCDSController { + + public static final class Keys { + + } + + @ApiOperation("") + @RequestMapping(value = "/api/percentageAmountAwarded", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List percentTendersCancelled(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + +// DBObject project1 = new BasicDBObject(); +// addYearlyMonthlyProjection(filter, project1, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE_REF); +// project1.put("tender.status", 1); +// +// DBObject group = new BasicDBObject(); +// addYearlyMonthlyReferenceToGroup(filter, group); +// group.put(Keys.TOTAL_TENDERS, new BasicDBObject("$sum", 1)); +// group.put(Keys.TOTAL_CANCELLED, new BasicDBObject("$sum", new BasicDBObject("$cond", +// Arrays.asList(new BasicDBObject("$eq", Arrays.asList("$tender.status", "cancelled")), 1, 0)))); +// +// DBObject project2 = new BasicDBObject(); +// project2.put(Keys.TOTAL_TENDERS, 1); +// project2.put(Keys.TOTAL_CANCELLED, 1); +// project2.put(Keys.PERCENT_CANCELLED, new BasicDBObject("$multiply", +// Arrays.asList(new BasicDBObject("$divide", Arrays.asList("$totalCancelled", "$totalTenders")), 100))); + + + + Aggregation agg = newAggregation( + match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) + .andOperator(getYearDefaultFilterCriteria(filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), + unwind("awards"), + match(where("awards.status").is("active")), + facet().and(group().sum("awards.value.amount").as("sum")).as("totalAwarded"), + facet().and(match(where(("awards.suppliers._id")).is("0304504425")), + group().sum("awards.value.amount").as("sum")).as("totalAwardedTo") + ); + + AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); + List list = results.getMappedResults(); + return list; + } + + + +} \ No newline at end of file From d534c03364167e1fa04357c22652b17de90ff62e Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 3 Oct 2017 14:37:31 +0300 Subject: [PATCH 187/702] fixes #180 --- .../form/GenericBootstrapFormComponent.java | 3 ++ .../forms/wicket/page/user/LoginPage.java | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java index 93817b35b..6526ac9b4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java @@ -113,6 +113,9 @@ protected FormComponent updatingBehaviorComponent() { } protected void getAjaxFormComponentUpdatingBehavior() { + if (getUpdateEvent() == null) { + return; + } updatingBehaviorComponent().add(new AjaxFormComponentUpdatingBehavior(getUpdateEvent()) { private static final long serialVersionUID = -2696538086634114609L; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java index ac108d5da..e23c3451a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java @@ -64,6 +64,9 @@ class LoginForm extends BootstrapForm { private String password; private String referrer; + + private TextFieldBootstrapFormComponent usernameField; + private PasswordFieldBootstrapFormComponent passwordField; protected void retrieveReferrerFromSavedRequestIfPresent() { StringValue referrerParam = RequestCycle.get().getRequest().getRequestParameters() @@ -98,15 +101,22 @@ protected void onInitialize() { notificationPanel.setOutputMarkupId(true); add(notificationPanel); - final TextFieldBootstrapFormComponent username = new TextFieldBootstrapFormComponent<>("username", - new StringResourceModel("user", LoginPage.this, null), new PropertyModel(this, "username")); - username.required(); - add(username); - final PasswordFieldBootstrapFormComponent password = - new PasswordFieldBootstrapFormComponent("password", new PropertyModel<>(this, "password")); - password.getField().setResetPassword(false); - add(password); + usernameField = new TextFieldBootstrapFormComponent("username", + new StringResourceModel("user", LoginPage.this, null), + new PropertyModel(this, "username")) { + @Override + public String getUpdateEvent() { + return null; + } + }; + usernameField.required(); + add(usernameField); + + + passwordField = new PasswordFieldBootstrapFormComponent("password", new PropertyModel<>(this, "password")); + passwordField.getField().setResetPassword(false); + add(passwordField); final IndicatingAjaxButton submit = new IndicatingAjaxButton("submit", new StringResourceModel("submit.label", LoginPage.this, null)) { @@ -140,6 +150,9 @@ protected void onSubmit(final AjaxRequestTarget target, final Form form) { @Override protected void onError(final AjaxRequestTarget target, final Form form) { target.add(notificationPanel); + target.add(notificationPanel); + target.add(usernameField); + target.add(passwordField); } }; add(submit); From eeb5944ed158b46306507a8696148ac6bfff27a3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 4 Oct 2017 15:44:27 +0300 Subject: [PATCH 188/702] OCE-378 Base info table --- ui/oce/corruption-risk/contract/index.jsx | 76 +++++++++++++++++++--- ui/oce/corruption-risk/contract/style.less | 2 +- web/public/languages/en_US.json | 3 +- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/ui/oce/corruption-risk/contract/index.jsx b/ui/oce/corruption-risk/contract/index.jsx index c6e300932..89ef22781 100644 --- a/ui/oce/corruption-risk/contract/index.jsx +++ b/ui/oce/corruption-risk/contract/index.jsx @@ -1,6 +1,6 @@ import CRDPage from '../page'; import Visualization from '../../visualization'; -import { Map } from 'immutable'; +import { Map, List } from 'immutable'; import translatable from '../../translatable'; import styles from './style.less'; @@ -13,15 +13,10 @@ class Info extends translatable(Visualization) { render() { const { data } = this.props; const title = data.getIn(['tender', 'title']); - /* - Contract Description //where do I find it? - Procuring Entity - Buyer - Supplier - Status - Amounts - Dates - */ + const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); + const startDate = data.getIn(['tender', 'tenderPeriod', 'startDate']); + const endDate = data.getIn(['tender', 'tenderPeriod', 'endDate']); + console.log(data.toJS()); return (
@@ -30,6 +25,67 @@ class Info extends translatable(Visualization) { {title &&
{this.t('crd:general:contract:title')}
} {title &&
{title}
}
+

StatusContract IDTitleProcuring EntityTender AmountAwards AmountTender DateFlag TypeNo. of Flags{this.t('crd:procurementsTable:status')}{this.t('crd:procurementsTable:contractID')}{this.t('crd:procurementsTable:title')}{this.t('crd:procurementsTable:procuringEntity')}{this.t('crd:procurementsTable:tenderAmount')}{this.t('crd:procurementsTable:awardsAmount')}{this.t('crd:procurementsTable:tenderDate')}{this.t('crd:procurementsTable:flagType')}{this.t('crd:procurementsTable:noOfFlags')}
+ + + + + + + + + + + + +
+
+
Procuring entity name
+
{data.getIn(['tender', 'procuringEntity', 'name'], this.t('general:undefined'))}
+
+
+
+
Buyer
+
{data.getIn(['buyer', 'name'], this.t('general:undefined'))}
+
+
+
+
Suppliers
+
+ {suppliers.count() ? + suppliers.map(supplier =>

{supplier.get('name')}

) : + this.t('general:undefined') + } +
+
+
+
+
Status
+
{data.getIn(['tender', 'status'], this.t('general:undefined'))}
+
+
+
+
Amounts
+
+ {data.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} +   + {data.getIn(['tender', 'value', 'currency'])} +
+
+
+
+
Dates
+
+ {startDate && + new Date(startDate).toLocaleDateString() + } + {startDate && endDate ? : this.t('general:undefined')} + {startDate && + new Date(endDate).toLocaleDateString() + } +
+
+

); } diff --git a/ui/oce/corruption-risk/contract/style.less b/ui/oce/corruption-risk/contract/style.less index 6498879e8..71c8b0d87 100644 --- a/ui/oce/corruption-risk/contract/style.less +++ b/ui/oce/corruption-risk/contract/style.less @@ -7,7 +7,7 @@ font-weight: normal; } dd{ - font-size: 1.8em; + font-size: 1.5em; } } } diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 7320b41d6..2f5bcee84 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -275,5 +275,6 @@ "crd:individualIndicatorChart:flaggedProcurements": "Flagged Procurements", "crd:individualIndicatorChart:eligibleProcurements": "Eligible Procurements", "crd:general:contracts": "Contracts", - "crd:general:contract:title": "Contract title" + "crd:general:contract:title": "Contract title", + "general:undefined": "Undefined" } From 240390dcdf769572ea53287dbda12c8e92b7701f Mon Sep 17 00:00:00 2001 From: Alexei Date: Wed, 4 Oct 2017 15:45:22 +0300 Subject: [PATCH 189/702] OCE-378 Removed a log --- ui/oce/corruption-risk/contract/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/oce/corruption-risk/contract/index.jsx b/ui/oce/corruption-risk/contract/index.jsx index 89ef22781..f089e672d 100644 --- a/ui/oce/corruption-risk/contract/index.jsx +++ b/ui/oce/corruption-risk/contract/index.jsx @@ -16,7 +16,6 @@ class Info extends translatable(Visualization) { const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); const startDate = data.getIn(['tender', 'tenderPeriod', 'startDate']); const endDate = data.getIn(['tender', 'tenderPeriod', 'endDate']); - console.log(data.toJS()); return (
From 1bb66895e3ebd703ce405e79220b9434aa683ae8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 4 Oct 2017 18:37:09 +0300 Subject: [PATCH 190/702] OCE-378 Removed (0) from contracts --- ui/oce/corruption-risk/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 611fd224d..60e547f37 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -348,7 +348,7 @@ class CorruptionRiskDashboard extends React.Component { key="contracts" > Contracts icon - {this.t('crd:general:contracts')} (0) + {this.t('crd:general:contracts')}
Date: Wed, 4 Oct 2017 18:38:44 +0300 Subject: [PATCH 191/702] OCE-378 'Show more' for suppliers --- ui/oce/corruption-risk/contract/index.jsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contract/index.jsx b/ui/oce/corruption-risk/contract/index.jsx index f089e672d..bcef147ff 100644 --- a/ui/oce/corruption-risk/contract/index.jsx +++ b/ui/oce/corruption-risk/contract/index.jsx @@ -5,11 +5,25 @@ import translatable from '../../translatable'; import styles from './style.less'; class Info extends translatable(Visualization) { + constructor(...args){ + super(...args); + this.state.showAllSuppliers = false; + } + getCustomEP(){ const { id } = this.props; return `ocds/release/ocid/${id}`; } + getSuppliers(){ + const { data } = this.props; + const { showAllSuppliers } = this.state; + const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); + return showAllSuppliers ? + suppliers : + suppliers.slice(0, 2); + } + render() { const { data } = this.props; const title = data.getIn(['tender', 'title']); @@ -44,10 +58,11 @@ class Info extends translatable(Visualization) {
Suppliers
{suppliers.count() ? - suppliers.map(supplier =>

{supplier.get('name')}

) : + this.getSuppliers().map(supplier =>

{supplier.get('name')}

) : this.t('general:undefined') }
+ this.setState({showAllSuppliers: true})}> From 21163bbe52dea7229786e3474a121ec4d7d0eda4 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 4 Oct 2017 18:39:14 +0300 Subject: [PATCH 192/702] OCE-378 Translations --- ui/oce/corruption-risk/contract/index.jsx | 21 +++++++++++++++------ web/public/languages/en_US.json | 8 +++++++- web/public/languages/es_ES.json | 9 ++++++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contract/index.jsx b/ui/oce/corruption-risk/contract/index.jsx index bcef147ff..a71c92c95 100644 --- a/ui/oce/corruption-risk/contract/index.jsx +++ b/ui/oce/corruption-risk/contract/index.jsx @@ -43,19 +43,19 @@ class Info extends translatable(Visualization) {
-
Procuring entity name
+
{this.t('crd:contracts:baseInfo:procuringEntityName')}
{data.getIn(['tender', 'procuringEntity', 'name'], this.t('general:undefined'))}
-
Buyer
+
{this.t('crd:contracts:baseInfo:buyer')}
{data.getIn(['buyer', 'name'], this.t('general:undefined'))}
-
Suppliers
+
{this.t('crd:contracts:baseInfo:suppliers')}
{suppliers.count() ? this.getSuppliers().map(supplier =>

{supplier.get('name')}

) : @@ -69,13 +69,13 @@ class Info extends translatable(Visualization) {
-
Status
+
{this.t('crd:contracts:baseInfo:status')}
{data.getIn(['tender', 'status'], this.t('general:undefined'))}
-
Amounts
+
{this.t('crd:contracts:baseInfo:amounts')}
{data.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))}   @@ -85,7 +85,7 @@ class Info extends translatable(Visualization) {
-
Dates
+
{this.t('crd:contracts:baseInfo:dates')}
{startDate && new Date(startDate).toLocaleDateString() @@ -113,6 +113,15 @@ export default class Contract extends CRDPage { } } + getSuppliers(){ + const { data } = this.props; + const { showAllSuppliers } = this.state; + const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); + return showAllSuppliers ? + suppliers : + suppliers.slice(0, 2); + } + render() { const { contract } = this.state; const { id, translations } = this.props; diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 2f5bcee84..a9efdff26 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -276,5 +276,11 @@ "crd:individualIndicatorChart:eligibleProcurements": "Eligible Procurements", "crd:general:contracts": "Contracts", "crd:general:contract:title": "Contract title", - "general:undefined": "Undefined" + "general:undefined": "Undefined", + "crd:contracts:baseInfo:procuringEntityName": "Procuring entity name", + "crd:contracts:baseInfo:buyer": "Buyer", + "crd:contracts:baseInfo:suppliers": "Suppliers", + "crd:contracts:baseInfo:status": "Status", + "crd:contracts:baseInfo:amounts": "Amounts", + "crd:contracts:baseInfo:dates": "Dates" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 716eac511..fb77f308f 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -275,5 +275,12 @@ "crd:individualIndicatorChart:flaggedProcurements": "Contrataciones con advertencias", "crd:individualIndicatorChart:eligibleProcurements": "Contrataciones elegibles", "crd:general:contracts": "Contratos", - "crd:general:contract:title": "Título del contrato" + "crd:general:contract:title": "Título del contrato", + "general:undefined": "Undefined", + "crd:contracts:baseInfo:procuringEntityName": "Procuring entity name", + "crd:contracts:baseInfo:buyer": "Buyer", + "crd:contracts:baseInfo:suppliers": "Proveedores", + "crd:contracts:baseInfo:status": "Estado", + "crd:contracts:baseInfo:amounts": "Amounts", + "crd:contracts:baseInfo:dates": "Fechas" } From 759ed60732b92380dbafd05080f31da890342e42 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 4 Oct 2017 18:39:24 +0300 Subject: [PATCH 193/702] OCE-378 Keep contract info cells 33% wide --- ui/oce/corruption-risk/contract/style.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/oce/corruption-risk/contract/style.less b/ui/oce/corruption-risk/contract/style.less index 71c8b0d87..739a46265 100644 --- a/ui/oce/corruption-risk/contract/style.less +++ b/ui/oce/corruption-risk/contract/style.less @@ -10,4 +10,10 @@ font-size: 1.5em; } } + + table{ + th, td{ + width: 33%; + } + } } From c5c02a2bd9c59d26cc0c044e5c42d29bc638a4ee Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Thu, 5 Oct 2017 14:41:40 +0300 Subject: [PATCH 194/702] OCE-382 --- .../PercentageAmountAwardedController.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java index 1ca12a779..9a220b803 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java @@ -15,7 +15,6 @@ import io.swagger.annotations.ApiOperation; import java.util.List; import javax.validation.Valid; -import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; @@ -72,14 +71,16 @@ public List percentTendersCancelled(@ModelAttribute @Valid final YearF Aggregation agg = newAggregation( - match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) - .andOperator(getYearDefaultFilterCriteria(filter, - MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), - unwind("awards"), - match(where("awards.status").is("active")), - facet().and(group().sum("awards.value.amount").as("sum")).as("totalAwarded"), - facet().and(match(where(("awards.suppliers._id")).is("0304504425")), - group().sum("awards.value.amount").as("sum")).as("totalAwardedTo") + match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) + .andOperator(getProcuringEntityIdCriteria(filter))), + unwind("awards"), + match(where("awards.status").is("active")), + facet().and( + // project("awards.suppliers._id").and("awards.value.amount").as("awards.value.amount") + match(getSupplierIdCriteria(filter)) +// group().sum("awards.value.amount").as("sum") + ).as("totalAwardedTo"), + facet().and(group().sum("awards.value.amount").as("sum")).as("totalAwarded") ); AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); From 837f275e871b3127d45d6417ff9522713950aef2 Mon Sep 17 00:00:00 2001 From: Alexei Date: Fri, 6 Oct 2017 13:45:38 +0300 Subject: [PATCH 195/702] OCE-377 Intermediary commit --- ui/oce/corruption-risk/contracts.jsx | 51 -------------- ui/oce/corruption-risk/contracts/index.jsx | 69 +++++++++++++++++++ .../index.jsx => contracts/single.jsx} | 0 .../{contract => contracts}/style.less | 0 ui/oce/corruption-risk/index.jsx | 3 +- web/public/languages/en_US.json | 4 +- web/public/languages/es_ES.json | 4 +- 7 files changed, 77 insertions(+), 54 deletions(-) delete mode 100644 ui/oce/corruption-risk/contracts.jsx create mode 100644 ui/oce/corruption-risk/contracts/index.jsx rename ui/oce/corruption-risk/{contract/index.jsx => contracts/single.jsx} (100%) rename ui/oce/corruption-risk/{contract => contracts}/style.less (100%) diff --git a/ui/oce/corruption-risk/contracts.jsx b/ui/oce/corruption-risk/contracts.jsx deleted file mode 100644 index a4815f83a..000000000 --- a/ui/oce/corruption-risk/contracts.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import CRDPage from './page'; -import Visualization from '../visualization'; -import { List } from 'immutable'; - -class CList extends Visualization { - render() { - const { data, navigate } = this.props; - return ( -
- {data.map(contract => { - const id = contract.get('ocid'); - return ( -

- navigate('contract', id)} - key={id} - > - {contract.getIn(['tender', 'title'], id)} - -

- ) - })} -
- ); - } -} - -CList.endpoint = 'ocds/release/all'; - -export default class Contracts extends CRDPage { - constructor(...args){ - super(...args); - this.state = { - list: List() - } - } - - render() { - const { list } = this.state; - const { filters, navigate } = this.props; - return ( - this.setState({list})} - navigate={navigate} - /> - ); - } -} diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx new file mode 100644 index 000000000..414c93d46 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -0,0 +1,69 @@ +import CRDPage from '../page'; +import Visualization from '../../visualization'; +import { List } from 'immutable'; + +class CList extends Visualization { + render() { + const { data, navigate } = this.props; + return ( + + {data.map(contract => { + const id = contract.get('ocid'); +console.log(contract.toJS()); + return ( + + {data.getIn(['tender', 'status'])} + + navigate('contract', id)} + key={id} + > + {contract.getIn(['tender', 'title'], id)} + + + + ) + })} + + ); + } +} + +CList.endpoint = 'ocds/release/all'; + +export default class Contracts extends CRDPage { + constructor(...args){ + super(...args); + this.state = { + list: List() + } + } + + render() { + const { list } = this.state; + const { filters, navigate } = this.props; + return ( +
+ + + + + + + + + + + + this.setState({list})} + navigate={navigate} + /> +
{this.t('crd:contracts:baseInfo:status')}{this.t('crd:procurementsTable:contractID')}{this.t('crd:general:contract:title')}{this.t('crd:contracts:list:procuringEntity')}{this.t('crd:procurementsTable:tenderAmount')}{this.t('crd:procurementsTable:awardAmount')}{this.t('crd:procurementsTable:tenderDate')}{this.t('crd:procurementsTable:flagType')}
+
+ ); + } +} diff --git a/ui/oce/corruption-risk/contract/index.jsx b/ui/oce/corruption-risk/contracts/single.jsx similarity index 100% rename from ui/oce/corruption-risk/contract/index.jsx rename to ui/oce/corruption-risk/contracts/single.jsx diff --git a/ui/oce/corruption-risk/contract/style.less b/ui/oce/corruption-risk/contracts/style.less similarity index 100% rename from ui/oce/corruption-risk/contract/style.less rename to ui/oce/corruption-risk/contracts/style.less diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 60e547f37..83ffa98ca 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -7,7 +7,7 @@ import OverviewPage from './overview-page'; import CorruptionTypePage from './corruption-type'; import IndividualIndicatorPage from './individual-indicator'; import ContractsPage from './contracts'; -import ContractPage from './contract'; +import ContractPage from './contracts/single'; import Filters from './filters'; import TotalFlags from './total-flags'; import LandingPopup from './landing-popup'; @@ -152,6 +152,7 @@ class CorruptionRiskDashboard extends React.Component { ); } else if (page === 'contract') { diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index a9efdff26..414bfe898 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -282,5 +282,7 @@ "crd:contracts:baseInfo:suppliers": "Suppliers", "crd:contracts:baseInfo:status": "Status", "crd:contracts:baseInfo:amounts": "Amounts", - "crd:contracts:baseInfo:dates": "Dates" + "crd:contracts:baseInfo:dates": "Dates", + "crd:contracts:list:procuringEntity": "Procuring entity", + "crd:contracts:list:awardAmount": "Award Amount" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index fb77f308f..d933929f2 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -282,5 +282,7 @@ "crd:contracts:baseInfo:suppliers": "Proveedores", "crd:contracts:baseInfo:status": "Estado", "crd:contracts:baseInfo:amounts": "Amounts", - "crd:contracts:baseInfo:dates": "Fechas" + "crd:contracts:baseInfo:dates": "Fechas", + "crd:contracts:list:procuringEntity": "Entidad contratante", + "crd:procurementsTable:awardAmount": "Cantidad de adjudicaciones" } From 9619d76ab61003e3ac7858aa8620aab3d216b196 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 6 Oct 2017 14:21:57 +0300 Subject: [PATCH 196/702] OCE-382 --- .../controller/GenericOCDSController.java | 19 +++++++ .../PercentageAmountAwardedController.java | 51 +++++++------------ 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index 8d68b36db..85c5456a6 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -71,6 +71,25 @@ protected Date getStartDate(final int year) { return start; } + /** + * Creates a mongo $cond that calculates percentage of expression1/expression2. + * Checks for division by zero. + * + * @param expression1 + * @param expression2 + * @return + */ + protected DBObject getPercentageMongoOp(String expression1, String expression2) { + return new BasicDBObject("$cond", + Arrays.asList(new BasicDBObject("$eq", Arrays.asList(ref(expression2), + 0)), new BasicDBObject("$literal", 0), + new BasicDBObject("$multiply", + Arrays.asList(new BasicDBObject("$divide", + Arrays.asList(ref(expression1), + ref(expression2))), 100)))); + + } + /** * This is used to build the start date filter query when a monthly filter is used. * diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java index 9a220b803..17af30a62 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java @@ -11,15 +11,18 @@ *******************************************************************************/ package org.devgateway.ocds.web.rest.controller; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import io.swagger.annotations.ApiOperation; import java.util.List; import javax.validation.Valid; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.devgateway.toolkit.persistence.mongo.aggregate.CustomProjectionOperation; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.util.Assert; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,9 +37,7 @@ import static org.springframework.data.mongodb.core.query.Criteria.where; /** - * * @author mpostelnicu - * */ @RestController @CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") @@ -49,38 +50,25 @@ public static final class Keys { @ApiOperation("") @RequestMapping(value = "/api/percentageAmountAwarded", - method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") public List percentTendersCancelled(@ModelAttribute @Valid final YearFilterPagingRequest filter) { - -// DBObject project1 = new BasicDBObject(); -// addYearlyMonthlyProjection(filter, project1, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE_REF); -// project1.put("tender.status", 1); -// -// DBObject group = new BasicDBObject(); -// addYearlyMonthlyReferenceToGroup(filter, group); -// group.put(Keys.TOTAL_TENDERS, new BasicDBObject("$sum", 1)); -// group.put(Keys.TOTAL_CANCELLED, new BasicDBObject("$sum", new BasicDBObject("$cond", -// Arrays.asList(new BasicDBObject("$eq", Arrays.asList("$tender.status", "cancelled")), 1, 0)))); -// -// DBObject project2 = new BasicDBObject(); -// project2.put(Keys.TOTAL_TENDERS, 1); -// project2.put(Keys.TOTAL_CANCELLED, 1); -// project2.put(Keys.PERCENT_CANCELLED, new BasicDBObject("$multiply", -// Arrays.asList(new BasicDBObject("$divide", Arrays.asList("$totalCancelled", "$totalTenders")), 100))); - - - + Assert.notEmpty(filter.getProcuringEntityId(), "Must provide at least one procuringEntity!"); + Assert.notEmpty(filter.getSupplierId(), "Must provide at least one supplierId!"); Aggregation agg = newAggregation( - match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) + match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) .andOperator(getProcuringEntityIdCriteria(filter))), - unwind("awards"), - match(where("awards.status").is("active")), - facet().and( - // project("awards.suppliers._id").and("awards.value.amount").as("awards.value.amount") - match(getSupplierIdCriteria(filter)) -// group().sum("awards.value.amount").as("sum") - ).as("totalAwardedTo"), - facet().and(group().sum("awards.value.amount").as("sum")).as("totalAwarded") + unwind("awards"), + match(where("awards.status").is("active")), + facet().and(match(getSupplierIdCriteria(filter)), + group().sum("awards.value.amount").as("sum") + ).as("totalAwardedToSuppliers") + .and(group().sum("awards.value.amount").as("sum")).as("totalAwarded"), + unwind("totalAwardedToSuppliers"), + unwind("totalAwarded"), + new CustomProjectionOperation(new BasicDBObject("percentage", + getPercentageMongoOp("totalAwardedToSuppliers.sum", + "totalAwarded.sum")).append("totalAwardedToSuppliers.sum", 1) + .append("totalAwarded.sum", 1)) ); AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); @@ -89,5 +77,4 @@ public List percentTendersCancelled(@ModelAttribute @Valid final YearF } - } \ No newline at end of file From 8c258ed67fba35767a365539b1c62248a014a5f2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 6 Oct 2017 15:33:32 +0300 Subject: [PATCH 197/702] OCE-377 Implemented contract listing --- ui/oce/corruption-risk/contracts/index.jsx | 74 +++++++++++++++------ ui/oce/corruption-risk/contracts/single.jsx | 2 +- ui/oce/corruption-risk/contracts/style.less | 15 +++++ web/public/languages/es_ES.json | 2 +- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 414c93d46..e891f3d4f 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -1,29 +1,56 @@ +import { List } from 'immutable'; import CRDPage from '../page'; import Visualization from '../../visualization'; -import { List } from 'immutable'; class CList extends Visualization { render() { const { data, navigate } = this.props; return ( - {data.map(contract => { + {data.map((contract) => { const id = contract.get('ocid'); -console.log(contract.toJS()); + const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); return ( - - {data.getIn(['tender', 'status'])} + + {contract.getIn(['tender', 'status'], this.t('general:undefined'))} + + navigate('contract', this.t('general:undefined'))} + > + {id} + + navigate('contract', id)} - key={id} > {contract.getIn(['tender', 'title'], id)} + + {contract.getIn(['tender', 'procuringEntity', 'name'], this.t('general:undefined'))} + + + {contract.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} +   + {contract.getIn(['tender', 'value', 'currency'])} + + + {contract.getIn(['awards', 0, 'value', 'amount'], this.t('general:undefined'))} +   + {contract.getIn(['awards', 0, 'value', 'currency'])} + + + {startDate ? + new Date(startDate).toLocaleDateString() : + this.t('general:undefined') + } + + - ) + ); })} ); @@ -33,34 +60,37 @@ console.log(contract.toJS()); CList.endpoint = 'ocds/release/all'; export default class Contracts extends CRDPage { - constructor(...args){ + constructor(...args) { super(...args); this.state = { - list: List() - } + list: List(), + }; } render() { const { list } = this.state; - const { filters, navigate } = this.props; + const { filters, navigate, translations } = this.props; return ( -
- +
+
- - - - - - - - + + + + + + + + + + this.setState({list})} + requestNewData={(_, newList) => this.setState({ list: newList })} navigate={navigate} + translations={translations} />
{this.t('crd:contracts:baseInfo:status')}{this.t('crd:procurementsTable:contractID')}{this.t('crd:general:contract:title')}{this.t('crd:contracts:list:procuringEntity')}{this.t('crd:procurementsTable:tenderAmount')}{this.t('crd:procurementsTable:awardAmount')}{this.t('crd:procurementsTable:tenderDate')}{this.t('crd:procurementsTable:flagType')}
{this.t('crd:contracts:baseInfo:status')}{this.t('crd:procurementsTable:contractID')}{this.t('crd:general:contract:title')}{this.t('crd:contracts:list:procuringEntity')}{this.t('crd:procurementsTable:tenderAmount')}{this.t('crd:contracts:list:awardAmount')}{this.t('crd:procurementsTable:tenderDate')}{this.t('crd:procurementsTable:flagType')}
diff --git a/ui/oce/corruption-risk/contracts/single.jsx b/ui/oce/corruption-risk/contracts/single.jsx index a71c92c95..dc369df00 100644 --- a/ui/oce/corruption-risk/contracts/single.jsx +++ b/ui/oce/corruption-risk/contracts/single.jsx @@ -91,7 +91,7 @@ class Info extends translatable(Visualization) { new Date(startDate).toLocaleDateString() } {startDate && endDate ? : this.t('general:undefined')} - {startDate && + {endDate && new Date(endDate).toLocaleDateString() }
diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 739a46265..04328ec64 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -17,3 +17,18 @@ } } } + +.contracts-page{ + table{ + th, td{ + width: 12.5%; + } + th{ + color: #a1adbc; + } + td{ + font-weight: 600; + font-size: .9em; + } + } +} diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index d933929f2..c94a010ff 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -284,5 +284,5 @@ "crd:contracts:baseInfo:amounts": "Amounts", "crd:contracts:baseInfo:dates": "Fechas", "crd:contracts:list:procuringEntity": "Entidad contratante", - "crd:procurementsTable:awardAmount": "Cantidad de adjudicaciones" + "crd:contracts:list:awardAmount": "Cantidad de adjudicaciones" } From bfbd78ad43caf9921aa70e0006329c4c11397d02 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 6 Oct 2017 15:45:52 +0300 Subject: [PATCH 198/702] OCE-390 --- .../web/rest/controller/OcdsController.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index e54503760..69b675e91 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -13,6 +13,10 @@ import com.fasterxml.jackson.annotation.JsonView; import io.swagger.annotations.ApiOperation; +import java.util.ArrayList; +import java.util.List; +import javax.validation.Valid; +import org.devgateway.ocds.persistence.mongo.FlaggedRelease; import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; @@ -29,9 +33,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; -import java.util.ArrayList; -import java.util.List; import static org.springframework.data.mongodb.core.query.Query.query; @@ -133,6 +134,30 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR } + + + /** + * Returns a list of OCDS FlaggedReleases, order by Id, using pagination + * + * @return the release data + */ + @ApiOperation(value = "Resturns all available releases with flags, filtered by the given criteria.") + @RequestMapping(value = "/api/flaggedRelease/all", method = { RequestMethod.POST, RequestMethod.GET }, + produces = "application/json") + @JsonView(Views.Internal.class) + public List flaggedOcdsReleases(@ModelAttribute @Valid + final YearFilterPagingRequest releaseRequest) { + + Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), + Direction.ASC, "id"); + + List find = mongoTemplate + .find(query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest), FlaggedRelease.class); + + return find; + + } + @ApiOperation(value = "Returns all available packages, filtered by the given criteria." + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") @RequestMapping(value = "/api/ocds/package/all", method = { RequestMethod.POST, RequestMethod.GET }, From 6d5a18351d78495d7c268f2fb7326c4e5b07607a Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 6 Oct 2017 17:02:47 +0300 Subject: [PATCH 199/702] OCE-381 OCE-390 --- .../ocds/web/rest/controller/OcdsController.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index 9fb44d863..fa0ce7feb 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -17,9 +17,6 @@ import java.util.List; import javax.validation.Valid; import org.apache.commons.lang3.StringUtils; -import java.util.ArrayList; -import java.util.List; -import javax.validation.Valid; import org.devgateway.ocds.persistence.mongo.FlaggedRelease; import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; @@ -145,14 +142,13 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR } - /** * Returns a list of OCDS FlaggedReleases, order by Id, using pagination * * @return the release data */ @ApiOperation(value = "Resturns all available releases with flags, filtered by the given criteria.") - @RequestMapping(value = "/api/flaggedRelease/all", method = { RequestMethod.POST, RequestMethod.GET }, + @RequestMapping(value = "/api/flaggedRelease/all", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Internal.class) public List flaggedOcdsReleases(@ModelAttribute @Valid @@ -161,11 +157,15 @@ public List flaggedOcdsReleases(@ModelAttribute @Valid Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); - List find = mongoTemplate - .find(query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest), FlaggedRelease.class); + Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); - return find; + if (StringUtils.isNotEmpty(releaseRequest.getText())) { + query.addCriteria(getTextCriteria(releaseRequest)); + } + List find = mongoTemplate.find(query, FlaggedRelease.class); + + return find; } @ApiOperation(value = "Returns all available packages, filtered by the given criteria." From 235c6ea14393314c5c6c53a3e9ac5a1adb8dd54f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 13:14:03 +0300 Subject: [PATCH 200/702] OCE-377 Flag type --- ui/oce/corruption-risk/contracts/index.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index e891f3d4f..58350bdec 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -10,6 +10,7 @@ class CList extends Visualization { {data.map((contract) => { const id = contract.get('ocid'); const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); + const flagType = contract.get('flags').toList().getIn([0, 'types', 0]); return ( {contract.getIn(['tender', 'status'], this.t('general:undefined'))} @@ -48,7 +49,9 @@ class CList extends Visualization { this.t('general:undefined') } - + + {this.t(`crd:corruptionType:${flagType}:name`)} + ); })} @@ -57,7 +60,7 @@ class CList extends Visualization { } } -CList.endpoint = 'ocds/release/all'; +CList.endpoint = 'flaggedRelease/all'; export default class Contracts extends CRDPage { constructor(...args) { From ae9dba618dfa168d0f1c3155a58d396cbadb3778 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 15:42:54 +0300 Subject: [PATCH 201/702] OCE-377 Top search --- ui/oce/corruption-risk/contracts/index.jsx | 14 ++++++++++++-- ui/oce/corruption-risk/contracts/style.less | 15 +++++++++++++++ web/public/languages/en_US.json | 3 ++- web/public/languages/es_ES.json | 3 ++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 58350bdec..50e68eef3 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -10,7 +10,7 @@ class CList extends Visualization { {data.map((contract) => { const id = contract.get('ocid'); const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); - const flagType = contract.get('flags').toList().getIn([0, 'types', 0]); + const flagType = contract.getIn(['flags', 'flaggedStats', 0, 'type']); return ( {contract.getIn(['tender', 'status'], this.t('general:undefined'))} @@ -50,7 +50,9 @@ class CList extends Visualization { } - {this.t(`crd:corruptionType:${flagType}:name`)} + {flagType ? + this.t(`crd:corruptionType:${flagType}:name`) : + this.t('general:undefined')} ); @@ -75,6 +77,14 @@ export default class Contracts extends CRDPage { const { filters, navigate, translations } = this.props; return (
+
+
+ +
+ +
+
+
diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 04328ec64..803090aec 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -31,4 +31,19 @@ font-size: .9em; } } + + .top-search { + margin: 20px; + input.form-control { + border-radius: 4px; + } + .input-group-addon { + background: transparent; + border: none; + .glyphicon { + margin-left: -60px; + z-index: 3; + } + } + } } diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 414bfe898..e6e1840f4 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -284,5 +284,6 @@ "crd:contracts:baseInfo:amounts": "Amounts", "crd:contracts:baseInfo:dates": "Dates", "crd:contracts:list:procuringEntity": "Procuring entity", - "crd:contracts:list:awardAmount": "Award Amount" + "crd:contracts:list:awardAmount": "Award Amount", + "crd:contracts:top-search": "Search within contracts" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index c94a010ff..188ff8306 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -284,5 +284,6 @@ "crd:contracts:baseInfo:amounts": "Amounts", "crd:contracts:baseInfo:dates": "Fechas", "crd:contracts:list:procuringEntity": "Entidad contratante", - "crd:contracts:list:awardAmount": "Cantidad de adjudicaciones" + "crd:contracts:list:awardAmount": "Cantidad de adjudicaciones", + "crd:contracts:top-search": "Search within contracts" } From a734b1de2cd51a55c9d07806e8ecd31076184731 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 18:28:39 +0300 Subject: [PATCH 202/702] OCE-377 Search component --- .../corruption-risk/contracts/top-search.jsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 ui/oce/corruption-risk/contracts/top-search.jsx diff --git a/ui/oce/corruption-risk/contracts/top-search.jsx b/ui/oce/corruption-risk/contracts/top-search.jsx new file mode 100644 index 000000000..40d4c762a --- /dev/null +++ b/ui/oce/corruption-risk/contracts/top-search.jsx @@ -0,0 +1,31 @@ +import translatable from '../../translatable'; + +class TopSearch extends translatable(React.Component) { + componentDidMount(){ + const { searchQuery } = this.props; + if (searchQuery) this.input.value = searchQuery; + } + + render() { + const { doSearch } = this.props; + return ( +
+
doSearch(this.input.value)}> +
+ this.input = c} + /> +
+ +
+
+ +
+ ) + } +} + +export default TopSearch; From 9ea947f11205a51d0593151a368bf41139eb5e9d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 18:30:47 +0300 Subject: [PATCH 203/702] OCE-377 Search results page --- ui/oce/corruption-risk/contracts/index.jsx | 47 +++++++++++++++++----- ui/oce/corruption-risk/index.jsx | 3 ++ web/public/languages/en_US.json | 4 +- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 50e68eef3..b8a214154 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -1,8 +1,28 @@ import { List } from 'immutable'; +import cn from 'classnames'; +import URI from 'urijs'; import CRDPage from '../page'; import Visualization from '../../visualization'; +import TopSearch from './top-search'; + +const API_ROOT = '/api'; class CList extends Visualization { + buildUrl(ep) { + let { filters, searchQuery } = this.props; + let uri = new URI(API_ROOT + '/' + ep).addSearch(filters.toJS()); + return searchQuery ? + uri.addSearch('text', searchQuery) : + uri; + } + + componentDidUpdate(prevProps){ + const { filters, searchQuery } = this.props; + if (filters != prevProps.filters || searchQuery != prevProps.searchQuery) { + this.fetch(); + } + } + render() { const { data, navigate } = this.props; return ( @@ -74,18 +94,20 @@ export default class Contracts extends CRDPage { render() { const { list } = this.state; - const { filters, navigate, translations } = this.props; + const { filters, navigate, translations, searchQuery, doSearch } = this.props; return (
-
-
- -
- -
-
-
-
+ + + {searchQuery &&

+ {this.t('crd:contracts:top-search:resultsFor').replace('$#$', searchQuery)} +

} + +
@@ -104,9 +126,12 @@ export default class Contracts extends CRDPage { requestNewData={(_, newList) => this.setState({ list: newList })} navigate={navigate} translations={translations} + searchQuery={searchQuery} />
{this.t('crd:contracts:baseInfo:status')}
+ + {searchQuery && !list.count() ? {this.t('crd:contracts:top-search:nothingFound')} : null}
- ); + ); } } diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 83ffa98ca..6883124d8 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -148,11 +148,14 @@ class CorruptionRiskDashboard extends React.Component { /> ); } else if (page === 'contracts') { + const [, searchQuery] = route; return ( navigate('contracts', query)} /> ); } else if (page === 'contract') { diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index e6e1840f4..324a19e7a 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -285,5 +285,7 @@ "crd:contracts:baseInfo:dates": "Dates", "crd:contracts:list:procuringEntity": "Procuring entity", "crd:contracts:list:awardAmount": "Award Amount", - "crd:contracts:top-search": "Search within contracts" + "crd:contracts:top-search": "Search within contracts", + "crd:contracts:top-search:resultsFor": "Search results for \"$#$\"", + "crd:contracts:top-search:nothingFound": "Nothing found" } From ea3a2cd4f134b4440985da34f8b5d956cbb736e2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 18:42:22 +0300 Subject: [PATCH 204/702] OCE-377 Added search to single contract page --- ui/oce/corruption-risk/contracts/index.jsx | 4 ++-- ui/oce/corruption-risk/contracts/single.jsx | 7 ++++++- ui/oce/corruption-risk/contracts/style.less | 5 +++-- ui/oce/corruption-risk/index.jsx | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index b8a214154..0ff89a300 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -37,7 +37,7 @@ class CList extends Visualization { navigate('contract', this.t('general:undefined'))} + onClick={() => navigate('contract', id)} > {id} @@ -132,6 +132,6 @@ export default class Contracts extends CRDPage { {searchQuery && !list.count() ? {this.t('crd:contracts:top-search:nothingFound')} : null}
- ); + ); } } diff --git a/ui/oce/corruption-risk/contracts/single.jsx b/ui/oce/corruption-risk/contracts/single.jsx index dc369df00..6a1b8b23d 100644 --- a/ui/oce/corruption-risk/contracts/single.jsx +++ b/ui/oce/corruption-risk/contracts/single.jsx @@ -3,6 +3,7 @@ import Visualization from '../../visualization'; import { Map, List } from 'immutable'; import translatable from '../../translatable'; import styles from './style.less'; +import TopSearch from './top-search'; class Info extends translatable(Visualization) { constructor(...args){ @@ -124,9 +125,13 @@ export default class Contract extends CRDPage { render() { const { contract } = this.state; - const { id, translations } = this.props; + const { id, translations, doSearch } = this.props; return (
+ navigate('contracts', query)} /> ) } else { From fb0153c86a3556e3938b8f92e128d784fe5f97d3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 9 Oct 2017 18:50:47 +0300 Subject: [PATCH 205/702] OCE-377 Spanish placeholder strings --- web/public/languages/es_ES.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 188ff8306..ec75a4ea0 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -285,5 +285,7 @@ "crd:contracts:baseInfo:dates": "Fechas", "crd:contracts:list:procuringEntity": "Entidad contratante", "crd:contracts:list:awardAmount": "Cantidad de adjudicaciones", - "crd:contracts:top-search": "Search within contracts" + "crd:contracts:top-search": "Search within contracts", + "crd:contracts:top-search:resultsFor": "Search results for \"$#$\"", + "crd:contracts:top-search:nothingFound": "Nothing found" } From 24030043fedd602a6c109e87a6cd095ce44db9a9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 23 Oct 2017 19:32:21 +0300 Subject: [PATCH 206/702] OCE-378 New general info --- ui/oce/corruption-risk/contracts/single.jsx | 74 ++++++++++++++------- ui/oce/corruption-risk/contracts/style.less | 11 ++- web/public/languages/en_US.json | 9 ++- web/public/languages/es_ES.json | 9 ++- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single.jsx b/ui/oce/corruption-risk/contracts/single.jsx index 6a1b8b23d..5c550398b 100644 --- a/ui/oce/corruption-risk/contracts/single.jsx +++ b/ui/oce/corruption-risk/contracts/single.jsx @@ -31,6 +31,9 @@ class Info extends translatable(Visualization) { const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); const startDate = data.getIn(['tender', 'tenderPeriod', 'startDate']); const endDate = data.getIn(['tender', 'tenderPeriod', 'endDate']); + const award = data.get('awards', List()).find(award => + award.get('status') != 'unsuccessful') || Map(); + return (
@@ -39,7 +42,7 @@ class Info extends translatable(Visualization) { {title &&
{this.t('crd:general:contract:title')}
} {title &&
{title}
}
- +
+ +
@@ -67,38 +70,59 @@ class Info extends translatable(Visualization) {
+ + + + + + + + +
-
-
{this.t('crd:contracts:baseInfo:status')}
-
{data.getIn(['tender', 'status'], this.t('general:undefined'))}
-
+ {this.t('crd:contracts:baseInfo:tenderAmount')} +   + + {data.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} +   + {data.getIn(['tender', 'value', 'currency'])} +
-
-
{this.t('crd:contracts:baseInfo:amounts')}
-
- {data.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} -   - {data.getIn(['tender', 'value', 'currency'])} -
-
+ {this.t('crd:contracts:baseInfo:tenderDates')} +   + + {startDate ? + + new Date(startDate).toLocaleDateString() + – + : + this.t('general:undefined')} + + {endDate && new Date(endDate).toLocaleDateString()} +
-
-
{this.t('crd:contracts:baseInfo:dates')}
-
- {startDate && - new Date(startDate).toLocaleDateString() - } - {startDate && endDate ? : this.t('general:undefined')} - {endDate && - new Date(endDate).toLocaleDateString() - } -
-
+ {this.t('crd:contracts:list:awardAmount')} +   + + {award.getIn(['value', 'amount'], this.t('general:undefined'))} +   + {award.getIn(['value', 'currency'])} + +
+ {this.t('crd:contracts:baseInfo:awardDate')} +   + + {award.has('date') ? + new Date(award.get('date')).toLocaleDateString() : + this.t('general:undefined')} +
{this.t('crd:contracts:baseInfo:contractAmount')}{this.t('crd:contracts:baseInfo:contractDates')}
diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 5006ddf59..abd07c4c3 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -9,7 +9,14 @@ } } - table{ + table { + &.join-bottom { + margin-bottom: 0; + &, td { + border-bottom: none; + } + } + th, td{ width: 33%; } @@ -17,7 +24,7 @@ } .contracts-page{ - table{ + table { th, td{ width: 12.5%; } diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 324a19e7a..ac29b1057 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -281,11 +281,14 @@ "crd:contracts:baseInfo:buyer": "Buyer", "crd:contracts:baseInfo:suppliers": "Suppliers", "crd:contracts:baseInfo:status": "Status", - "crd:contracts:baseInfo:amounts": "Amounts", - "crd:contracts:baseInfo:dates": "Dates", "crd:contracts:list:procuringEntity": "Procuring entity", "crd:contracts:list:awardAmount": "Award Amount", "crd:contracts:top-search": "Search within contracts", "crd:contracts:top-search:resultsFor": "Search results for \"$#$\"", - "crd:contracts:top-search:nothingFound": "Nothing found" + "crd:contracts:top-search:nothingFound": "Nothing found", + "crd:contracts:baseInfo:tenderAmount": "Tender Amount", + "crd:contracts:baseInfo:tenderDates": "Tender Open/Close Dates", + "crd:contracts:baseInfo:awardDate": "Award Date", + "crd:contracts:baseInfo:contractAmount": "Contract Amount", + "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index ec75a4ea0..69166539f 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -281,11 +281,14 @@ "crd:contracts:baseInfo:buyer": "Buyer", "crd:contracts:baseInfo:suppliers": "Proveedores", "crd:contracts:baseInfo:status": "Estado", - "crd:contracts:baseInfo:amounts": "Amounts", - "crd:contracts:baseInfo:dates": "Fechas", "crd:contracts:list:procuringEntity": "Entidad contratante", "crd:contracts:list:awardAmount": "Cantidad de adjudicaciones", "crd:contracts:top-search": "Search within contracts", "crd:contracts:top-search:resultsFor": "Search results for \"$#$\"", - "crd:contracts:top-search:nothingFound": "Nothing found" + "crd:contracts:top-search:nothingFound": "Nothing found", + "crd:contracts:baseInfo:tenderAmount": "Cantidad de la licitación", + "crd:contracts:baseInfo:tenderDates": "Tender Open/Close Dates", + "crd:contracts:baseInfo:awardDate": "Award Date", + "crd:contracts:baseInfo:contractAmount": "Contract Amount", + "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates" } From 9cf6234932f99964c4db5f914929ddeb276bd924 Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Sun, 29 Oct 2017 12:32:52 +0200 Subject: [PATCH 207/702] added legend orientation. --- .../wicket/components/charts/Layout.java | 10 +++++++ .../wicket/components/charts/Legend.java | 28 +++++++++++++++++++ .../wicket/components/charts/PlotlyChart.java | 7 ++++- .../PlotlyChartDashboardExamplesPage.java | 4 +++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java index 699a896c8..6e48b0bec 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Layout.java @@ -22,6 +22,8 @@ public final class Layout implements Serializable { private final Boolean showlegend; + private final Legend legend; + private final Xaxis xaxis; private final Yaxis yaxis; @@ -36,6 +38,7 @@ public Layout(final LayoutBuilder layoutBuilder) { this.annotations = layoutBuilder.annotations; this.font = layoutBuilder.font; this.showlegend = layoutBuilder.showlegend; + this.legend = layoutBuilder.legend; this.xaxis = layoutBuilder.xaxis; this.yaxis = layoutBuilder.yaxis; this.bargap = layoutBuilder.bargap; @@ -56,6 +59,8 @@ public static class LayoutBuilder { private Boolean showlegend; + private Legend legend; + private Xaxis xaxis; private Yaxis yaxis; @@ -97,6 +102,11 @@ public LayoutBuilder setShowlegend(final Boolean showlegend) { return this; } + public LayoutBuilder setLegend(final Legend legend) { + this.legend = legend; + return this; + } + public LayoutBuilder setXaxis(final Xaxis xaxis) { this.xaxis = xaxis; return this; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java new file mode 100644 index 000000000..af372dbb7 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/Legend.java @@ -0,0 +1,28 @@ +package org.devgateway.toolkit.forms.wicket.components.charts; + +import java.io.Serializable; + +/** + * @author idobre + * @since 29/10/2017 + */ +public class Legend implements Serializable { + private final String orientation; + + public Legend(final LegendBuilder legendBuilder) { + this.orientation = legendBuilder.orientation; + } + + public static class LegendBuilder { + private String orientation; + + public LegendBuilder setOrientation(final String orientation) { + this.orientation = orientation; + return this; + } + + public Legend build() { + return new Legend(this); + } + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java index f7a3dd567..5f40773b9 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/PlotlyChart.java @@ -13,10 +13,12 @@ /** * @author idobre * @since 4/19/17 + * + * Component that renders a chart using Plot.ly JS library. */ public final class PlotlyChart extends Panel { private static final String JS_FILE = "plotlyChart.js"; - private static final String INIT_FUNCTION = "init"; + private static final String INIT_FUNCTION = "initChart"; public static final String CHART_TYPE_PIE = "pie"; public static final String CHART_TYPE_BAR = "bar"; @@ -37,6 +39,9 @@ public final class PlotlyChart extends Panel { public static final String AXIS_TYPE_CATEGORY = "category"; + public static final String LEGEND_ORIENTATION_H = "h"; + public static final String LEGEND_ORIENTATION_V = "v"; + private final WebMarkupContainer chart; private final ChartParameters parameters; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java index 0a7fcfddc..35022dcb7 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/PlotlyChartDashboardExamplesPage.java @@ -7,6 +7,7 @@ import org.devgateway.toolkit.forms.wicket.components.charts.Data; import org.devgateway.toolkit.forms.wicket.components.charts.Font; import org.devgateway.toolkit.forms.wicket.components.charts.Layout; +import org.devgateway.toolkit.forms.wicket.components.charts.Legend; import org.devgateway.toolkit.forms.wicket.components.charts.Line; import org.devgateway.toolkit.forms.wicket.components.charts.Marker; import org.devgateway.toolkit.forms.wicket.components.charts.PlotlyChart; @@ -113,6 +114,9 @@ private void addPieCharts() { .setWidth(WIDTH) .setHeight(HEIGHT) .setTitle("Global Emissions 1990-2011") + .setLegend(new Legend.LegendBuilder() + .setOrientation(PlotlyChart.LEGEND_ORIENTATION_H) + .build()) .setAnnotations( new ArrayList<>(Arrays.asList( new Annotation.AnnotationBuilder() From 78c888997b9cae3ebc09c209ab8d623cb005f31d Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Mon, 30 Oct 2017 11:28:47 +0200 Subject: [PATCH 208/702] update js code. --- .../forms/wicket/components/charts/plotlyChart.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js index 37f23efd9..f1e0fe93f 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/charts/plotlyChart.js @@ -1,9 +1,16 @@ /** * init function that is called from the Wicket Component */ -var init = function (parameters) { +var initChart = function (parameters) { 'use strict'; + // check if we have the 'colors' property for a marker and copy it to 'color' property as well. + for(var i = 0; i < parameters.data.length; i++) { + if (parameters.data[i].marker !== undefined && parameters.data[i].marker.colors !== undefined) { + parameters.data[i].marker.color = parameters.data[i].marker.colors; + } + } + var chart = new PlotlyChart(parameters); chart.render(); }; From b53b29e650063cf0d5c6339f8f0aa14fef0130dd Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 30 Oct 2017 11:30:43 +0200 Subject: [PATCH 209/702] fixes #180 --- .../toolkit/forms/wicket/page/user/LoginPage.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java index e23c3451a..4a9c06b07 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java @@ -114,7 +114,13 @@ public String getUpdateEvent() { add(usernameField); - passwordField = new PasswordFieldBootstrapFormComponent("password", new PropertyModel<>(this, "password")); + passwordField = new PasswordFieldBootstrapFormComponent("password", + new PropertyModel<>(this, "password")) { + @Override + public String getUpdateEvent() { + return null; + } + }; passwordField.getField().setResetPassword(false); add(passwordField); From 6e962ab7091b72e4480eb421f8a16e7bcefa1985 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 30 Oct 2017 11:47:44 +0200 Subject: [PATCH 210/702] fixes #191 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 27428a5be..ccacc2ab7 100644 --- a/pom.xml +++ b/pom.xml @@ -22,8 +22,8 @@ UTF-8 1.8 3.5.3 - 1.5.6.RELEASE - 10.13.1.1 + 1.5.8.RELEASE + 10.14.1.0 3.14 3.12 devgateway/toolkit From 699d3dbbe81ce47bb148c885d90097f22b154a4d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 13:49:39 +0200 Subject: [PATCH 211/702] OCE-379 New donuts --- .../single/donuts/nr-contract-with-pe.jsx | 31 ++++++++++++ .../contracts/single/donuts/nr-of-bidders.jsx | 31 ++++++++++++ .../single/donuts/percent-pe-spending.jsx | 31 ++++++++++++ .../{single.jsx => single/index.jsx} | 47 ++++++++++++++++--- ui/oce/corruption-risk/contracts/style.less | 4 ++ 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx rename ui/oce/corruption-risk/contracts/{single.jsx => single/index.jsx} (76%) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx new file mode 100644 index 000000000..3cbaa25e6 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -0,0 +1,31 @@ +import backendYearFilterable from '../../../../backend-year-filterable'; +import Chart from '../../../../visualizations/charts/index.jsx'; + +class NrOfContractsWithPE extends backendYearFilterable(Chart) { + getData() { + const data = super.getData(); + if (!data || !data.count()) return []; + return [{ + values: [5, 10], + labels: ['this', 'total'], + textinfo: 'value', + hole: 0.85, + type: 'pie', + marker: { + colors: ['#72c47e', '#2e833a'], + }, + }]; + } + + getLayout() { + return { + title: 'Number of contracts with this procuring entity', + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +} + +NrOfContractsWithPE.endpoint = 'totalFlags'; + +export default NrOfContractsWithPE; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx new file mode 100644 index 000000000..402a49f9e --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -0,0 +1,31 @@ +import backendYearFilterable from '../../../../backend-year-filterable'; +import Chart from '../../../../visualizations/charts/index.jsx'; + +class NrOfBidders extends backendYearFilterable(Chart) { + getData() { + const data = super.getData(); + if (!data || !data.count()) return []; + return [{ + values: [5, 10], + labels: ['this', 'total'], + textinfo: 'value', + hole: 0.85, + type: 'pie', + marker: { + colors: ['#289df5', '#fac329'], + }, + }]; + } + + getLayout() { + return { + title: 'Number of bidders vs average', + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +} + +NrOfBidders.endpoint = 'totalFlags'; + +export default NrOfBidders; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx new file mode 100644 index 000000000..ef21cbf25 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx @@ -0,0 +1,31 @@ +import backendYearFilterable from '../../../../backend-year-filterable'; +import Chart from '../../../../visualizations/charts/index.jsx'; + +class PercentPESpending extends backendYearFilterable(Chart) { + getData() { + const data = super.getData(); + if (!data || !data.count()) return []; + return [{ + values: [5, 10], + labels: ['this', 'total'], + textinfo: 'value', + hole: 0.85, + type: 'pie', + marker: { + colors: ['#40557d', '#289df5'], + }, + }]; + } + + getLayout() { + return { + title: '% of procuring entity spending to this supplier', + showlegend: false, + paper_bgcolor: 'rgba(0,0,0,0)', + }; + } +} + +PercentPESpending.endpoint = 'totalFlags'; + +export default PercentPESpending; diff --git a/ui/oce/corruption-risk/contracts/single.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx similarity index 76% rename from ui/oce/corruption-risk/contracts/single.jsx rename to ui/oce/corruption-risk/contracts/single/index.jsx index 5c550398b..781d8b900 100644 --- a/ui/oce/corruption-risk/contracts/single.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -1,9 +1,12 @@ -import CRDPage from '../page'; -import Visualization from '../../visualization'; import { Map, List } from 'immutable'; -import translatable from '../../translatable'; -import styles from './style.less'; -import TopSearch from './top-search'; +import CRDPage from '../../page'; +import Visualization from '../../../visualization'; +import translatable from '../../../translatable'; +import styles from '../style.less'; +import TopSearch from '../top-search'; +import NrOfBidders from './donuts/nr-of-bidders'; +import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; +import PercentPESpending from './donuts/percent-pe-spending'; class Info extends translatable(Visualization) { constructor(...args){ @@ -90,7 +93,7 @@ class Info extends translatable(Visualization) { {startDate ? - new Date(startDate).toLocaleDateString() + {new Date(startDate).toLocaleDateString()} – : this.t('general:undefined')} @@ -148,7 +151,7 @@ export default class Contract extends CRDPage { } render() { - const { contract } = this.state; + const { contract, nrOfBidders, nrContracts, percentPESpending } = this.state; const { id, translations, doSearch } = this.props; return (
@@ -163,6 +166,36 @@ export default class Contract extends CRDPage { requestNewData={(_, contract) => this.setState({contract})} translations={translations} /> +
+

6 Flags

+
+ this.setState({ nrOfBidders })} + translations={translations} + /> +
+
+ this.setState({ nrContracts })} + translations={translations} + /> +
+
+ this.setState({ percentPESpending })} + translations={translations} + /> +
+
); } diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index abd07c4c3..b5a4ffb80 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -21,6 +21,10 @@ width: 33%; } } + + section h2 { + color: #ce4747; + } } .contracts-page{ From dbc17667a530bfdb28b45ebde9ae521f1df1b45b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 14:01:44 +0200 Subject: [PATCH 212/702] OCE-375 Description is now shown only on hover --- ui/oce/corruption-risk/style.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index e9fff511e..d0560f491 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -260,8 +260,14 @@ } .crd-overview-link{ cursor: pointer; + &+p { + display: none; + } &:hover { color: #35ae46; + &+p { + display: block; + } } } [role=navigation]{ From 4f2a11d2ee43036ca21db213964f374fbb4a4224 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 14:40:37 +0200 Subject: [PATCH 213/702] OCE-375 Missing menu items + translations(no icons yet) --- ui/dll/manifest.json | 929 ++++++++++++++++++++++++++++++- ui/index.html | 3 +- ui/oce/corruption-risk/index.jsx | 18 + web/public/languages/en_US.json | 3 +- web/public/languages/es_ES.json | 3 +- 5 files changed, 952 insertions(+), 4 deletions(-) diff --git a/ui/dll/manifest.json b/ui/dll/manifest.json index 0967ef424..9278c9012 100644 --- a/ui/dll/manifest.json +++ b/ui/dll/manifest.json @@ -1 +1,928 @@ -{} +{ + "name": "lib", + "content": { + "./node_modules/react/react.js": 1, + "./node_modules/react/lib/React.js": 2, + "./node_modules/process/browser.js": 3, + "./node_modules/object-assign/index.js": 4, + "./node_modules/react/lib/ReactBaseClasses.js": 5, + "./node_modules/react/lib/reactProdInvariant.js": 6, + "./node_modules/react/lib/ReactNoopUpdateQueue.js": 7, + "./node_modules/fbjs/lib/warning.js": 8, + "./node_modules/fbjs/lib/emptyFunction.js": 9, + "./node_modules/react/lib/canDefineProperty.js": 10, + "./node_modules/fbjs/lib/emptyObject.js": 11, + "./node_modules/fbjs/lib/invariant.js": 12, + "./node_modules/react/lib/lowPriorityWarning.js": 13, + "./node_modules/react/lib/ReactChildren.js": 14, + "./node_modules/react/lib/PooledClass.js": 15, + "./node_modules/react/lib/ReactElement.js": 16, + "./node_modules/react/lib/ReactCurrentOwner.js": 17, + "./node_modules/react/lib/ReactElementSymbol.js": 18, + "./node_modules/react/lib/traverseAllChildren.js": 19, + "./node_modules/react/lib/getIteratorFn.js": 20, + "./node_modules/react/lib/KeyEscapeUtils.js": 21, + "./node_modules/react/lib/ReactDOMFactories.js": 22, + "./node_modules/react/lib/ReactElementValidator.js": 23, + "./node_modules/react/lib/ReactComponentTreeHook.js": 24, + "./node_modules/react/lib/checkReactTypeSpec.js": 25, + "./node_modules/react/lib/ReactPropTypeLocationNames.js": 26, + "./node_modules/react/lib/ReactPropTypesSecret.js": 27, + "./node_modules/react/lib/ReactPropTypes.js": 28, + "./node_modules/prop-types/factory.js": 29, + "./node_modules/prop-types/factoryWithTypeCheckers.js": 30, + "./node_modules/prop-types/node_modules/fbjs/lib/emptyFunction.js": 31, + "./node_modules/prop-types/node_modules/fbjs/lib/invariant.js": 32, + "./node_modules/prop-types/node_modules/fbjs/lib/warning.js": 33, + "./node_modules/prop-types/lib/ReactPropTypesSecret.js": 34, + "./node_modules/prop-types/checkPropTypes.js": 35, + "./node_modules/react/lib/ReactVersion.js": 36, + "./node_modules/react/lib/createClass.js": 37, + "./node_modules/react/node_modules/create-react-class/factory.js": 38, + "./node_modules/react/node_modules/create-react-class/node_modules/object-assign/index.js": 39, + "./node_modules/react/lib/onlyChild.js": 40, + "./node_modules/react-dom/index.js": 41, + "./node_modules/react-dom/lib/ReactDOM.js": 42, + "./node_modules/react-dom/lib/ReactDOMComponentTree.js": 43, + "./node_modules/react-dom/lib/reactProdInvariant.js": 44, + "./node_modules/react-dom/lib/DOMProperty.js": 45, + "./node_modules/react-dom/lib/ReactDOMComponentFlags.js": 46, + "./node_modules/react-dom/lib/ReactDefaultInjection.js": 47, + "./node_modules/react-dom/lib/ARIADOMPropertyConfig.js": 48, + "./node_modules/react-dom/lib/BeforeInputEventPlugin.js": 49, + "./node_modules/react-dom/lib/EventPropagators.js": 50, + "./node_modules/react-dom/lib/EventPluginHub.js": 51, + "./node_modules/react-dom/lib/EventPluginRegistry.js": 52, + "./node_modules/react-dom/lib/EventPluginUtils.js": 53, + "./node_modules/react-dom/lib/ReactErrorUtils.js": 54, + "./node_modules/react-dom/lib/accumulateInto.js": 55, + "./node_modules/react-dom/lib/forEachAccumulated.js": 56, + "./node_modules/fbjs/lib/ExecutionEnvironment.js": 57, + "./node_modules/react-dom/lib/FallbackCompositionState.js": 58, + "./node_modules/react-dom/lib/PooledClass.js": 59, + "./node_modules/react-dom/lib/getTextContentAccessor.js": 60, + "./node_modules/react-dom/lib/SyntheticCompositionEvent.js": 61, + "./node_modules/react-dom/lib/SyntheticEvent.js": 62, + "./node_modules/react-dom/lib/SyntheticInputEvent.js": 63, + "./node_modules/react-dom/lib/ChangeEventPlugin.js": 64, + "./node_modules/react-dom/lib/ReactUpdates.js": 65, + "./node_modules/react-dom/lib/CallbackQueue.js": 66, + "./node_modules/react-dom/lib/ReactFeatureFlags.js": 67, + "./node_modules/react-dom/lib/ReactReconciler.js": 68, + "./node_modules/react-dom/lib/ReactRef.js": 69, + "./node_modules/react-dom/lib/ReactOwner.js": 70, + "./node_modules/react-dom/lib/ReactInstrumentation.js": 71, + "./node_modules/react-dom/lib/ReactDebugTool.js": 72, + "./node_modules/react-dom/lib/ReactInvalidSetStateWarningHook.js": 73, + "./node_modules/react-dom/lib/ReactHostOperationHistoryHook.js": 74, + "./node_modules/fbjs/lib/performanceNow.js": 75, + "./node_modules/fbjs/lib/performance.js": 76, + "./node_modules/react-dom/lib/Transaction.js": 77, + "./node_modules/react-dom/lib/inputValueTracking.js": 78, + "./node_modules/react-dom/lib/getEventTarget.js": 79, + "./node_modules/react-dom/lib/isEventSupported.js": 80, + "./node_modules/react-dom/lib/isTextInputElement.js": 81, + "./node_modules/react-dom/lib/DefaultEventPluginOrder.js": 82, + "./node_modules/react-dom/lib/EnterLeaveEventPlugin.js": 83, + "./node_modules/react-dom/lib/SyntheticMouseEvent.js": 84, + "./node_modules/react-dom/lib/SyntheticUIEvent.js": 85, + "./node_modules/react-dom/lib/ViewportMetrics.js": 86, + "./node_modules/react-dom/lib/getEventModifierState.js": 87, + "./node_modules/react-dom/lib/HTMLDOMPropertyConfig.js": 88, + "./node_modules/react-dom/lib/ReactComponentBrowserEnvironment.js": 89, + "./node_modules/react-dom/lib/DOMChildrenOperations.js": 90, + "./node_modules/react-dom/lib/DOMLazyTree.js": 91, + "./node_modules/react-dom/lib/DOMNamespaces.js": 92, + "./node_modules/react-dom/lib/setInnerHTML.js": 93, + "./node_modules/react-dom/lib/createMicrosoftUnsafeLocalFunction.js": 94, + "./node_modules/react-dom/lib/setTextContent.js": 95, + "./node_modules/react-dom/lib/escapeTextContentForBrowser.js": 96, + "./node_modules/react-dom/lib/Danger.js": 97, + "./node_modules/fbjs/lib/createNodesFromMarkup.js": 98, + "./node_modules/fbjs/lib/createArrayFromMixed.js": 99, + "./node_modules/fbjs/lib/getMarkupWrap.js": 100, + "./node_modules/react-dom/lib/ReactDOMIDOperations.js": 101, + "./node_modules/react-dom/lib/ReactDOMComponent.js": 102, + "./node_modules/react-dom/lib/AutoFocusUtils.js": 103, + "./node_modules/fbjs/lib/focusNode.js": 104, + "./node_modules/react-dom/lib/CSSPropertyOperations.js": 105, + "./node_modules/react-dom/lib/CSSProperty.js": 106, + "./node_modules/fbjs/lib/camelizeStyleName.js": 107, + "./node_modules/fbjs/lib/camelize.js": 108, + "./node_modules/react-dom/lib/dangerousStyleValue.js": 109, + "./node_modules/fbjs/lib/hyphenateStyleName.js": 110, + "./node_modules/fbjs/lib/hyphenate.js": 111, + "./node_modules/fbjs/lib/memoizeStringOnly.js": 112, + "./node_modules/react-dom/lib/DOMPropertyOperations.js": 113, + "./node_modules/react-dom/lib/quoteAttributeValueForBrowser.js": 114, + "./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": 115, + "./node_modules/react-dom/lib/ReactEventEmitterMixin.js": 116, + "./node_modules/react-dom/lib/getVendorPrefixedEventName.js": 117, + "./node_modules/react-dom/lib/ReactDOMInput.js": 118, + "./node_modules/react-dom/lib/LinkedValueUtils.js": 119, + "./node_modules/react-dom/lib/ReactPropTypesSecret.js": 120, + "./node_modules/react-dom/lib/ReactDOMOption.js": 121, + "./node_modules/react-dom/lib/ReactDOMSelect.js": 122, + "./node_modules/react-dom/lib/ReactDOMTextarea.js": 123, + "./node_modules/react-dom/lib/ReactMultiChild.js": 124, + "./node_modules/react-dom/lib/ReactComponentEnvironment.js": 125, + "./node_modules/react-dom/lib/ReactInstanceMap.js": 126, + "./node_modules/react-dom/lib/ReactChildReconciler.js": 127, + "./node_modules/react-dom/lib/instantiateReactComponent.js": 128, + "./node_modules/react-dom/lib/ReactCompositeComponent.js": 129, + "./node_modules/react-dom/lib/ReactNodeTypes.js": 130, + "./node_modules/react-dom/lib/checkReactTypeSpec.js": 131, + "./node_modules/react-dom/lib/ReactPropTypeLocationNames.js": 132, + "./node_modules/fbjs/lib/shallowEqual.js": 133, + "./node_modules/react-dom/lib/shouldUpdateReactComponent.js": 134, + "./node_modules/react-dom/lib/ReactEmptyComponent.js": 135, + "./node_modules/react-dom/lib/ReactHostComponent.js": 136, + "./node_modules/react/lib/getNextDebugID.js": 137, + "./node_modules/react-dom/lib/KeyEscapeUtils.js": 138, + "./node_modules/react-dom/lib/traverseAllChildren.js": 139, + "./node_modules/react-dom/lib/ReactElementSymbol.js": 140, + "./node_modules/react-dom/lib/getIteratorFn.js": 141, + "./node_modules/react-dom/lib/flattenChildren.js": 142, + "./node_modules/react-dom/lib/ReactServerRenderingTransaction.js": 143, + "./node_modules/react-dom/lib/ReactServerUpdateQueue.js": 144, + "./node_modules/react-dom/lib/ReactUpdateQueue.js": 145, + "./node_modules/react-dom/lib/validateDOMNesting.js": 146, + "./node_modules/react-dom/lib/ReactDOMEmptyComponent.js": 147, + "./node_modules/react-dom/lib/ReactDOMTreeTraversal.js": 148, + "./node_modules/react-dom/lib/ReactDOMTextComponent.js": 149, + "./node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js": 150, + "./node_modules/react-dom/lib/ReactEventListener.js": 151, + "./node_modules/fbjs/lib/EventListener.js": 152, + "./node_modules/fbjs/lib/getUnboundedScrollPosition.js": 153, + "./node_modules/react-dom/lib/ReactInjection.js": 154, + "./node_modules/react-dom/lib/ReactReconcileTransaction.js": 155, + "./node_modules/react-dom/lib/ReactInputSelection.js": 156, + "./node_modules/react-dom/lib/ReactDOMSelection.js": 157, + "./node_modules/react-dom/lib/getNodeForCharacterOffset.js": 158, + "./node_modules/fbjs/lib/containsNode.js": 159, + "./node_modules/fbjs/lib/isTextNode.js": 160, + "./node_modules/fbjs/lib/isNode.js": 161, + "./node_modules/fbjs/lib/getActiveElement.js": 162, + "./node_modules/react-dom/lib/SVGDOMPropertyConfig.js": 163, + "./node_modules/react-dom/lib/SelectEventPlugin.js": 164, + "./node_modules/react-dom/lib/SimpleEventPlugin.js": 165, + "./node_modules/react-dom/lib/SyntheticAnimationEvent.js": 166, + "./node_modules/react-dom/lib/SyntheticClipboardEvent.js": 167, + "./node_modules/react-dom/lib/SyntheticFocusEvent.js": 168, + "./node_modules/react-dom/lib/SyntheticKeyboardEvent.js": 169, + "./node_modules/react-dom/lib/getEventCharCode.js": 170, + "./node_modules/react-dom/lib/getEventKey.js": 171, + "./node_modules/react-dom/lib/SyntheticDragEvent.js": 172, + "./node_modules/react-dom/lib/SyntheticTouchEvent.js": 173, + "./node_modules/react-dom/lib/SyntheticTransitionEvent.js": 174, + "./node_modules/react-dom/lib/SyntheticWheelEvent.js": 175, + "./node_modules/react-dom/lib/ReactMount.js": 176, + "./node_modules/react-dom/lib/ReactDOMContainerInfo.js": 177, + "./node_modules/react-dom/lib/ReactDOMFeatureFlags.js": 178, + "./node_modules/react-dom/lib/ReactMarkupChecksum.js": 179, + "./node_modules/react-dom/lib/adler32.js": 180, + "./node_modules/react-dom/lib/ReactVersion.js": 181, + "./node_modules/react-dom/lib/findDOMNode.js": 182, + "./node_modules/react-dom/lib/getHostComponentFromComposite.js": 183, + "./node_modules/react-dom/lib/renderSubtreeIntoContainer.js": 184, + "./node_modules/react-dom/lib/ReactDOMUnknownPropertyHook.js": 185, + "./node_modules/react-dom/lib/ReactDOMNullInputValuePropHook.js": 186, + "./node_modules/react-dom/lib/ReactDOMInvalidARIAHook.js": 187, + "./node_modules/react-bootstrap/lib/index.js": 188, + "./node_modules/react-bootstrap/lib/Accordion.js": 189, + "./node_modules/babel-runtime/helpers/extends.js": 190, + "./node_modules/babel-runtime/core-js/object/assign.js": 191, + "./node_modules/core-js/library/fn/object/assign.js": 192, + "./node_modules/core-js/library/modules/es6.object.assign.js": 193, + "./node_modules/core-js/library/modules/_export.js": 194, + "./node_modules/core-js/library/modules/_global.js": 195, + "./node_modules/core-js/library/modules/_core.js": 196, + "./node_modules/core-js/library/modules/_ctx.js": 197, + "./node_modules/core-js/library/modules/_a-function.js": 198, + "./node_modules/core-js/library/modules/_hide.js": 199, + "./node_modules/core-js/library/modules/_object-dp.js": 200, + "./node_modules/core-js/library/modules/_an-object.js": 201, + "./node_modules/core-js/library/modules/_is-object.js": 202, + "./node_modules/core-js/library/modules/_ie8-dom-define.js": 203, + "./node_modules/core-js/library/modules/_descriptors.js": 204, + "./node_modules/core-js/library/modules/_fails.js": 205, + "./node_modules/core-js/library/modules/_dom-create.js": 206, + "./node_modules/core-js/library/modules/_to-primitive.js": 207, + "./node_modules/core-js/library/modules/_property-desc.js": 208, + "./node_modules/core-js/library/modules/_object-assign.js": 209, + "./node_modules/core-js/library/modules/_object-keys.js": 210, + "./node_modules/core-js/library/modules/_object-keys-internal.js": 211, + "./node_modules/core-js/library/modules/_has.js": 212, + "./node_modules/core-js/library/modules/_to-iobject.js": 213, + "./node_modules/core-js/library/modules/_iobject.js": 214, + "./node_modules/core-js/library/modules/_cof.js": 215, + "./node_modules/core-js/library/modules/_defined.js": 216, + "./node_modules/core-js/library/modules/_array-includes.js": 217, + "./node_modules/core-js/library/modules/_to-length.js": 218, + "./node_modules/core-js/library/modules/_to-integer.js": 219, + "./node_modules/core-js/library/modules/_to-index.js": 220, + "./node_modules/core-js/library/modules/_shared-key.js": 221, + "./node_modules/core-js/library/modules/_shared.js": 222, + "./node_modules/core-js/library/modules/_uid.js": 223, + "./node_modules/core-js/library/modules/_enum-bug-keys.js": 224, + "./node_modules/core-js/library/modules/_object-gops.js": 225, + "./node_modules/core-js/library/modules/_object-pie.js": 226, + "./node_modules/core-js/library/modules/_to-object.js": 227, + "./node_modules/babel-runtime/helpers/classCallCheck.js": 228, + "./node_modules/babel-runtime/helpers/possibleConstructorReturn.js": 229, + "./node_modules/babel-runtime/helpers/typeof.js": 230, + "./node_modules/babel-runtime/core-js/symbol/iterator.js": 231, + "./node_modules/core-js/library/fn/symbol/iterator.js": 232, + "./node_modules/core-js/library/modules/es6.string.iterator.js": 233, + "./node_modules/core-js/library/modules/_string-at.js": 234, + "./node_modules/core-js/library/modules/_iter-define.js": 235, + "./node_modules/core-js/library/modules/_library.js": 236, + "./node_modules/core-js/library/modules/_redefine.js": 237, + "./node_modules/core-js/library/modules/_iterators.js": 238, + "./node_modules/core-js/library/modules/_iter-create.js": 239, + "./node_modules/core-js/library/modules/_object-create.js": 240, + "./node_modules/core-js/library/modules/_object-dps.js": 241, + "./node_modules/core-js/library/modules/_html.js": 242, + "./node_modules/core-js/library/modules/_set-to-string-tag.js": 243, + "./node_modules/core-js/library/modules/_wks.js": 244, + "./node_modules/core-js/library/modules/_object-gpo.js": 245, + "./node_modules/core-js/library/modules/web.dom.iterable.js": 246, + "./node_modules/core-js/library/modules/es6.array.iterator.js": 247, + "./node_modules/core-js/library/modules/_add-to-unscopables.js": 248, + "./node_modules/core-js/library/modules/_iter-step.js": 249, + "./node_modules/core-js/library/modules/_wks-ext.js": 250, + "./node_modules/babel-runtime/core-js/symbol.js": 251, + "./node_modules/core-js/library/fn/symbol/index.js": 252, + "./node_modules/core-js/library/modules/es6.symbol.js": 253, + "./node_modules/core-js/library/modules/_meta.js": 254, + "./node_modules/core-js/library/modules/_wks-define.js": 255, + "./node_modules/core-js/library/modules/_keyof.js": 256, + "./node_modules/core-js/library/modules/_enum-keys.js": 257, + "./node_modules/core-js/library/modules/_is-array.js": 258, + "./node_modules/core-js/library/modules/_object-gopn-ext.js": 259, + "./node_modules/core-js/library/modules/_object-gopn.js": 260, + "./node_modules/core-js/library/modules/_object-gopd.js": 261, + "./node_modules/core-js/library/modules/es6.object.to-string.js": 262, + "./node_modules/core-js/library/modules/es7.symbol.async-iterator.js": 263, + "./node_modules/core-js/library/modules/es7.symbol.observable.js": 264, + "./node_modules/babel-runtime/helpers/inherits.js": 265, + "./node_modules/babel-runtime/core-js/object/set-prototype-of.js": 266, + "./node_modules/core-js/library/fn/object/set-prototype-of.js": 267, + "./node_modules/core-js/library/modules/es6.object.set-prototype-of.js": 268, + "./node_modules/core-js/library/modules/_set-proto.js": 269, + "./node_modules/babel-runtime/core-js/object/create.js": 270, + "./node_modules/core-js/library/fn/object/create.js": 271, + "./node_modules/core-js/library/modules/es6.object.create.js": 272, + "./node_modules/react-bootstrap/lib/PanelGroup.js": 273, + "./node_modules/babel-runtime/helpers/objectWithoutProperties.js": 274, + "./node_modules/classnames/index.js": 275, + "./node_modules/react-bootstrap/lib/utils/bootstrapUtils.js": 276, + "./node_modules/babel-runtime/core-js/object/entries.js": 277, + "./node_modules/core-js/library/fn/object/entries.js": 278, + "./node_modules/core-js/library/modules/es7.object.entries.js": 279, + "./node_modules/core-js/library/modules/_object-to-array.js": 280, + "./node_modules/invariant/browser.js": 281, + "./node_modules/react-bootstrap/lib/utils/StyleConfig.js": 282, + "./node_modules/react-bootstrap/lib/utils/createChainedFunction.js": 283, + "./node_modules/react-bootstrap/lib/utils/ValidComponentChildren.js": 284, + "./node_modules/react-bootstrap/lib/Alert.js": 285, + "./node_modules/babel-runtime/core-js/object/values.js": 286, + "./node_modules/core-js/library/fn/object/values.js": 287, + "./node_modules/core-js/library/modules/es7.object.values.js": 288, + "./node_modules/react-bootstrap/lib/Badge.js": 289, + "./node_modules/react-bootstrap/lib/Breadcrumb.js": 290, + "./node_modules/react-bootstrap/lib/BreadcrumbItem.js": 291, + "./node_modules/react-bootstrap/lib/SafeAnchor.js": 292, + "./node_modules/react-prop-types/lib/elementType.js": 293, + "./node_modules/react-prop-types/lib/utils/createChainableTypeChecker.js": 294, + "./node_modules/react-bootstrap/lib/Button.js": 295, + "./node_modules/react-bootstrap/lib/ButtonGroup.js": 296, + "./node_modules/react-prop-types/lib/all.js": 297, + "./node_modules/react-bootstrap/lib/ButtonToolbar.js": 298, + "./node_modules/react-bootstrap/lib/Carousel.js": 299, + "./node_modules/react-bootstrap/lib/CarouselCaption.js": 300, + "./node_modules/react-bootstrap/lib/CarouselItem.js": 301, + "./node_modules/react-bootstrap/lib/utils/TransitionEvents.js": 302, + "./node_modules/react-bootstrap/lib/Glyphicon.js": 303, + "./node_modules/react-bootstrap/lib/Checkbox.js": 304, + "./node_modules/warning/browser.js": 305, + "./node_modules/react-bootstrap/lib/Clearfix.js": 306, + "./node_modules/react-bootstrap/lib/utils/capitalize.js": 307, + "./node_modules/react-bootstrap/lib/ControlLabel.js": 308, + "./node_modules/react-bootstrap/lib/Col.js": 309, + "./node_modules/react-bootstrap/lib/Collapse.js": 310, + "./node_modules/dom-helpers/style/index.js": 311, + "./node_modules/dom-helpers/util/camelizeStyle.js": 312, + "./node_modules/dom-helpers/util/camelize.js": 313, + "./node_modules/dom-helpers/util/hyphenateStyle.js": 314, + "./node_modules/dom-helpers/util/hyphenate.js": 315, + "./node_modules/dom-helpers/style/getComputedStyle.js": 316, + "./node_modules/dom-helpers/util/babelHelpers.js": 317, + "./node_modules/dom-helpers/style/removeStyle.js": 318, + "./node_modules/react-overlays/lib/Transition.js": 319, + "./node_modules/dom-helpers/transition/properties.js": 320, + "./node_modules/dom-helpers/util/inDOM.js": 321, + "./node_modules/dom-helpers/events/on.js": 322, + "./node_modules/react-bootstrap/lib/Dropdown.js": 323, + "./node_modules/dom-helpers/activeElement.js": 324, + "./node_modules/dom-helpers/ownerDocument.js": 325, + "./node_modules/dom-helpers/query/contains.js": 326, + "./node_modules/keycode/index.js": 327, + "./node_modules/react-prop-types/lib/isRequiredForA11y.js": 328, + "./node_modules/uncontrollable/index.js": 329, + "./node_modules/uncontrollable/createUncontrollable.js": 330, + "./node_modules/uncontrollable/utils.js": 331, + "./node_modules/react-bootstrap/lib/DropdownMenu.js": 332, + "./node_modules/babel-runtime/core-js/array/from.js": 333, + "./node_modules/core-js/library/fn/array/from.js": 334, + "./node_modules/core-js/library/modules/es6.array.from.js": 335, + "./node_modules/core-js/library/modules/_iter-call.js": 336, + "./node_modules/core-js/library/modules/_is-array-iter.js": 337, + "./node_modules/core-js/library/modules/_create-property.js": 338, + "./node_modules/core-js/library/modules/core.get-iterator-method.js": 339, + "./node_modules/core-js/library/modules/_classof.js": 340, + "./node_modules/core-js/library/modules/_iter-detect.js": 341, + "./node_modules/react-overlays/lib/RootCloseWrapper.js": 342, + "./node_modules/react-overlays/lib/utils/addEventListener.js": 343, + "./node_modules/dom-helpers/events/off.js": 344, + "./node_modules/react-overlays/lib/utils/ownerDocument.js": 345, + "./node_modules/react-bootstrap/lib/DropdownToggle.js": 346, + "./node_modules/react-bootstrap/lib/utils/PropTypes.js": 347, + "./node_modules/react-bootstrap/lib/DropdownButton.js": 348, + "./node_modules/react-bootstrap/lib/utils/splitComponentProps.js": 349, + "./node_modules/react-bootstrap/lib/Fade.js": 350, + "./node_modules/react-bootstrap/lib/Form.js": 351, + "./node_modules/react-bootstrap/lib/FormControl.js": 352, + "./node_modules/react-bootstrap/lib/FormControlFeedback.js": 353, + "./node_modules/react-bootstrap/lib/FormControlStatic.js": 354, + "./node_modules/react-bootstrap/lib/FormGroup.js": 355, + "./node_modules/react-bootstrap/lib/Grid.js": 356, + "./node_modules/react-bootstrap/lib/HelpBlock.js": 357, + "./node_modules/react-bootstrap/lib/Image.js": 358, + "./node_modules/react-bootstrap/lib/InputGroup.js": 359, + "./node_modules/react-bootstrap/lib/InputGroupAddon.js": 360, + "./node_modules/react-bootstrap/lib/InputGroupButton.js": 361, + "./node_modules/react-bootstrap/lib/Jumbotron.js": 362, + "./node_modules/react-bootstrap/lib/Label.js": 363, + "./node_modules/react-bootstrap/lib/ListGroup.js": 364, + "./node_modules/react-bootstrap/lib/ListGroupItem.js": 365, + "./node_modules/react-bootstrap/lib/Media.js": 366, + "./node_modules/react-bootstrap/lib/MediaBody.js": 367, + "./node_modules/react-bootstrap/lib/MediaHeading.js": 368, + "./node_modules/react-bootstrap/lib/MediaLeft.js": 369, + "./node_modules/react-bootstrap/lib/MediaList.js": 370, + "./node_modules/react-bootstrap/lib/MediaListItem.js": 371, + "./node_modules/react-bootstrap/lib/MediaRight.js": 372, + "./node_modules/react-bootstrap/lib/MenuItem.js": 373, + "./node_modules/react-bootstrap/lib/Modal.js": 374, + "./node_modules/dom-helpers/events/index.js": 375, + "./node_modules/dom-helpers/events/filter.js": 376, + "./node_modules/dom-helpers/query/querySelectorAll.js": 377, + "./node_modules/dom-helpers/util/scrollbarSize.js": 378, + "./node_modules/react-overlays/lib/Modal.js": 379, + "./node_modules/react-prop-types/lib/componentOrElement.js": 380, + "./node_modules/react-overlays/lib/Portal.js": 381, + "./node_modules/react-overlays/lib/utils/getContainer.js": 382, + "./node_modules/react-overlays/lib/ModalManager.js": 383, + "./node_modules/dom-helpers/class/index.js": 384, + "./node_modules/dom-helpers/class/addClass.js": 385, + "./node_modules/dom-helpers/class/hasClass.js": 386, + "./node_modules/dom-helpers/class/removeClass.js": 387, + "./node_modules/react-overlays/lib/utils/isOverflowing.js": 388, + "./node_modules/dom-helpers/query/isWindow.js": 389, + "./node_modules/react-overlays/lib/utils/manageAriaHidden.js": 390, + "./node_modules/react-overlays/lib/utils/addFocusListener.js": 391, + "./node_modules/react-bootstrap/lib/ModalBody.js": 392, + "./node_modules/react-bootstrap/lib/ModalDialog.js": 393, + "./node_modules/react-bootstrap/lib/ModalFooter.js": 394, + "./node_modules/react-bootstrap/lib/ModalHeader.js": 395, + "./node_modules/react-bootstrap/lib/ModalTitle.js": 396, + "./node_modules/react-bootstrap/lib/Nav.js": 397, + "./node_modules/react-bootstrap/lib/Navbar.js": 398, + "./node_modules/react-bootstrap/lib/NavbarBrand.js": 399, + "./node_modules/react-bootstrap/lib/NavbarCollapse.js": 400, + "./node_modules/react-bootstrap/lib/NavbarHeader.js": 401, + "./node_modules/react-bootstrap/lib/NavbarToggle.js": 402, + "./node_modules/react-bootstrap/lib/NavDropdown.js": 403, + "./node_modules/react-bootstrap/lib/NavItem.js": 404, + "./node_modules/react-bootstrap/lib/Overlay.js": 405, + "./node_modules/react-overlays/lib/Overlay.js": 406, + "./node_modules/react-overlays/lib/Position.js": 407, + "./node_modules/react-overlays/lib/utils/calculatePosition.js": 408, + "./node_modules/dom-helpers/query/offset.js": 409, + "./node_modules/dom-helpers/query/position.js": 410, + "./node_modules/dom-helpers/query/offsetParent.js": 411, + "./node_modules/dom-helpers/query/scrollTop.js": 412, + "./node_modules/dom-helpers/query/scrollLeft.js": 413, + "./node_modules/react-bootstrap/lib/OverlayTrigger.js": 414, + "./node_modules/react-bootstrap/lib/PageHeader.js": 415, + "./node_modules/react-bootstrap/lib/PageItem.js": 416, + "./node_modules/react-bootstrap/lib/PagerItem.js": 417, + "./node_modules/react-bootstrap/lib/utils/deprecationWarning.js": 418, + "./node_modules/react-bootstrap/lib/Pager.js": 419, + "./node_modules/react-bootstrap/lib/Pagination.js": 420, + "./node_modules/react-bootstrap/lib/PaginationButton.js": 421, + "./node_modules/react-bootstrap/lib/Panel.js": 422, + "./node_modules/react-bootstrap/lib/Popover.js": 423, + "./node_modules/react-bootstrap/lib/ProgressBar.js": 424, + "./node_modules/react-bootstrap/lib/Radio.js": 425, + "./node_modules/react-bootstrap/lib/ResponsiveEmbed.js": 426, + "./node_modules/react-bootstrap/lib/Row.js": 427, + "./node_modules/react-bootstrap/lib/SplitButton.js": 428, + "./node_modules/react-bootstrap/lib/SplitToggle.js": 429, + "./node_modules/react-bootstrap/lib/Tab.js": 430, + "./node_modules/react-bootstrap/lib/TabContainer.js": 431, + "./node_modules/react-bootstrap/lib/TabContent.js": 432, + "./node_modules/react-bootstrap/lib/TabPane.js": 433, + "./node_modules/react-bootstrap/lib/Table.js": 434, + "./node_modules/react-bootstrap/lib/Tabs.js": 435, + "./node_modules/react-bootstrap/lib/Thumbnail.js": 436, + "./node_modules/react-bootstrap/lib/Tooltip.js": 437, + "./node_modules/react-bootstrap/lib/Well.js": 438, + "./node_modules/react-bootstrap/lib/utils/index.js": 439, + "./node_modules/plotly.js/dist/plotly.js": 440, + "./node_modules/buffer/index.js": 441, + "./node_modules/base64-js/index.js": 442, + "./node_modules/ieee754/index.js": 443, + "./node_modules/isarray/index.js": 444, + "./node_modules/react-leaflet/lib/index.js": 445, + "./node_modules/leaflet/dist/leaflet-src.js": 446, + "./node_modules/react-leaflet/lib/types/index.js": 447, + "./node_modules/react-leaflet/lib/types/bounds.js": 448, + "./node_modules/react-leaflet/lib/types/latlngList.js": 449, + "./node_modules/react-leaflet/lib/types/latlng.js": 450, + "./node_modules/react-leaflet/lib/types/controlPosition.js": 451, + "./node_modules/react-leaflet/lib/AttributionControl.js": 452, + "./node_modules/react-leaflet/lib/MapControl.js": 453, + "./node_modules/react-leaflet/lib/BaseTileLayer.js": 454, + "./node_modules/react-leaflet/lib/MapLayer.js": 455, + "./node_modules/lodash/assign.js": 456, + "./node_modules/lodash/_assignValue.js": 457, + "./node_modules/lodash/_baseAssignValue.js": 458, + "./node_modules/lodash/_defineProperty.js": 459, + "./node_modules/lodash/_getNative.js": 460, + "./node_modules/lodash/_baseIsNative.js": 461, + "./node_modules/lodash/isFunction.js": 462, + "./node_modules/lodash/_baseGetTag.js": 463, + "./node_modules/lodash/_Symbol.js": 464, + "./node_modules/lodash/_root.js": 465, + "./node_modules/lodash/_freeGlobal.js": 466, + "./node_modules/lodash/_getRawTag.js": 467, + "./node_modules/lodash/_objectToString.js": 468, + "./node_modules/lodash/isObject.js": 469, + "./node_modules/lodash/_isMasked.js": 470, + "./node_modules/lodash/_coreJsData.js": 471, + "./node_modules/lodash/_toSource.js": 472, + "./node_modules/lodash/_getValue.js": 473, + "./node_modules/lodash/eq.js": 474, + "./node_modules/lodash/_copyObject.js": 475, + "./node_modules/lodash/_createAssigner.js": 476, + "./node_modules/lodash/_baseRest.js": 477, + "./node_modules/lodash/identity.js": 478, + "./node_modules/lodash/_overRest.js": 479, + "./node_modules/lodash/_apply.js": 480, + "./node_modules/lodash/_setToString.js": 481, + "./node_modules/lodash/_baseSetToString.js": 482, + "./node_modules/lodash/constant.js": 483, + "./node_modules/lodash/_shortOut.js": 484, + "./node_modules/lodash/_isIterateeCall.js": 485, + "./node_modules/lodash/isArrayLike.js": 486, + "./node_modules/lodash/isLength.js": 487, + "./node_modules/lodash/_isIndex.js": 488, + "./node_modules/lodash/_isPrototype.js": 489, + "./node_modules/lodash/keys.js": 490, + "./node_modules/lodash/_arrayLikeKeys.js": 491, + "./node_modules/lodash/_baseTimes.js": 492, + "./node_modules/lodash/isArguments.js": 493, + "./node_modules/lodash/_baseIsArguments.js": 494, + "./node_modules/lodash/isObjectLike.js": 495, + "./node_modules/lodash/isArray.js": 496, + "./node_modules/lodash/isBuffer.js": 497, + "./node_modules/webpack/buildin/module.js": 498, + "./node_modules/lodash/stubFalse.js": 499, + "./node_modules/lodash/isTypedArray.js": 500, + "./node_modules/lodash/_baseIsTypedArray.js": 501, + "./node_modules/lodash/_baseUnary.js": 502, + "./node_modules/lodash/_nodeUtil.js": 503, + "./node_modules/lodash/_baseKeys.js": 504, + "./node_modules/lodash/_nativeKeys.js": 505, + "./node_modules/lodash/_overArg.js": 506, + "./node_modules/react-leaflet/lib/MapComponent.js": 507, + "./node_modules/lodash/clone.js": 508, + "./node_modules/lodash/_baseClone.js": 509, + "./node_modules/lodash/_Stack.js": 510, + "./node_modules/lodash/_ListCache.js": 511, + "./node_modules/lodash/_listCacheClear.js": 512, + "./node_modules/lodash/_listCacheDelete.js": 513, + "./node_modules/lodash/_assocIndexOf.js": 514, + "./node_modules/lodash/_listCacheGet.js": 515, + "./node_modules/lodash/_listCacheHas.js": 516, + "./node_modules/lodash/_listCacheSet.js": 517, + "./node_modules/lodash/_stackClear.js": 518, + "./node_modules/lodash/_stackDelete.js": 519, + "./node_modules/lodash/_stackGet.js": 520, + "./node_modules/lodash/_stackHas.js": 521, + "./node_modules/lodash/_stackSet.js": 522, + "./node_modules/lodash/_Map.js": 523, + "./node_modules/lodash/_MapCache.js": 524, + "./node_modules/lodash/_mapCacheClear.js": 525, + "./node_modules/lodash/_Hash.js": 526, + "./node_modules/lodash/_hashClear.js": 527, + "./node_modules/lodash/_nativeCreate.js": 528, + "./node_modules/lodash/_hashDelete.js": 529, + "./node_modules/lodash/_hashGet.js": 530, + "./node_modules/lodash/_hashHas.js": 531, + "./node_modules/lodash/_hashSet.js": 532, + "./node_modules/lodash/_mapCacheDelete.js": 533, + "./node_modules/lodash/_getMapData.js": 534, + "./node_modules/lodash/_isKeyable.js": 535, + "./node_modules/lodash/_mapCacheGet.js": 536, + "./node_modules/lodash/_mapCacheHas.js": 537, + "./node_modules/lodash/_mapCacheSet.js": 538, + "./node_modules/lodash/_arrayEach.js": 539, + "./node_modules/lodash/_baseAssign.js": 540, + "./node_modules/lodash/_baseAssignIn.js": 541, + "./node_modules/lodash/keysIn.js": 542, + "./node_modules/lodash/_baseKeysIn.js": 543, + "./node_modules/lodash/_nativeKeysIn.js": 544, + "./node_modules/lodash/_cloneBuffer.js": 545, + "./node_modules/lodash/_copyArray.js": 546, + "./node_modules/lodash/_copySymbols.js": 547, + "./node_modules/lodash/_getSymbols.js": 548, + "./node_modules/lodash/stubArray.js": 549, + "./node_modules/lodash/_copySymbolsIn.js": 550, + "./node_modules/lodash/_getSymbolsIn.js": 551, + "./node_modules/lodash/_arrayPush.js": 552, + "./node_modules/lodash/_getPrototype.js": 553, + "./node_modules/lodash/_getAllKeys.js": 554, + "./node_modules/lodash/_baseGetAllKeys.js": 555, + "./node_modules/lodash/_getAllKeysIn.js": 556, + "./node_modules/lodash/_getTag.js": 557, + "./node_modules/lodash/_DataView.js": 558, + "./node_modules/lodash/_Promise.js": 559, + "./node_modules/lodash/_Set.js": 560, + "./node_modules/lodash/_WeakMap.js": 561, + "./node_modules/lodash/_initCloneArray.js": 562, + "./node_modules/lodash/_initCloneByTag.js": 563, + "./node_modules/lodash/_cloneArrayBuffer.js": 564, + "./node_modules/lodash/_Uint8Array.js": 565, + "./node_modules/lodash/_cloneDataView.js": 566, + "./node_modules/lodash/_cloneMap.js": 567, + "./node_modules/lodash/_addMapEntry.js": 568, + "./node_modules/lodash/_arrayReduce.js": 569, + "./node_modules/lodash/_mapToArray.js": 570, + "./node_modules/lodash/_cloneRegExp.js": 571, + "./node_modules/lodash/_cloneSet.js": 572, + "./node_modules/lodash/_addSetEntry.js": 573, + "./node_modules/lodash/_setToArray.js": 574, + "./node_modules/lodash/_cloneSymbol.js": 575, + "./node_modules/lodash/_cloneTypedArray.js": 576, + "./node_modules/lodash/_initCloneObject.js": 577, + "./node_modules/lodash/_baseCreate.js": 578, + "./node_modules/lodash/forEach.js": 579, + "./node_modules/lodash/_baseEach.js": 580, + "./node_modules/lodash/_baseForOwn.js": 581, + "./node_modules/lodash/_baseFor.js": 582, + "./node_modules/lodash/_createBaseFor.js": 583, + "./node_modules/lodash/_createBaseEach.js": 584, + "./node_modules/lodash/_castFunction.js": 585, + "./node_modules/lodash/reduce.js": 586, + "./node_modules/lodash/_baseIteratee.js": 587, + "./node_modules/lodash/_baseMatches.js": 588, + "./node_modules/lodash/_baseIsMatch.js": 589, + "./node_modules/lodash/_baseIsEqual.js": 590, + "./node_modules/lodash/_baseIsEqualDeep.js": 591, + "./node_modules/lodash/_equalArrays.js": 592, + "./node_modules/lodash/_SetCache.js": 593, + "./node_modules/lodash/_setCacheAdd.js": 594, + "./node_modules/lodash/_setCacheHas.js": 595, + "./node_modules/lodash/_arraySome.js": 596, + "./node_modules/lodash/_cacheHas.js": 597, + "./node_modules/lodash/_equalByTag.js": 598, + "./node_modules/lodash/_equalObjects.js": 599, + "./node_modules/lodash/_getMatchData.js": 600, + "./node_modules/lodash/_isStrictComparable.js": 601, + "./node_modules/lodash/_matchesStrictComparable.js": 602, + "./node_modules/lodash/_baseMatchesProperty.js": 603, + "./node_modules/lodash/get.js": 604, + "./node_modules/lodash/_baseGet.js": 605, + "./node_modules/lodash/_castPath.js": 606, + "./node_modules/lodash/_isKey.js": 607, + "./node_modules/lodash/isSymbol.js": 608, + "./node_modules/lodash/_stringToPath.js": 609, + "./node_modules/lodash/_memoizeCapped.js": 610, + "./node_modules/lodash/memoize.js": 611, + "./node_modules/lodash/toString.js": 612, + "./node_modules/lodash/_baseToString.js": 613, + "./node_modules/lodash/_arrayMap.js": 614, + "./node_modules/lodash/_toKey.js": 615, + "./node_modules/lodash/hasIn.js": 616, + "./node_modules/lodash/_baseHasIn.js": 617, + "./node_modules/lodash/_hasPath.js": 618, + "./node_modules/lodash/property.js": 619, + "./node_modules/lodash/_baseProperty.js": 620, + "./node_modules/lodash/_basePropertyDeep.js": 621, + "./node_modules/lodash/_baseReduce.js": 622, + "./node_modules/react-leaflet/lib/CanvasTileLayer.js": 623, + "./node_modules/react-leaflet/lib/Circle.js": 624, + "./node_modules/react-leaflet/lib/Path.js": 625, + "./node_modules/lodash/isEqual.js": 626, + "./node_modules/lodash/pick.js": 627, + "./node_modules/lodash/_basePick.js": 628, + "./node_modules/lodash/_basePickBy.js": 629, + "./node_modules/lodash/_baseSet.js": 630, + "./node_modules/lodash/_flatRest.js": 631, + "./node_modules/lodash/flatten.js": 632, + "./node_modules/lodash/_baseFlatten.js": 633, + "./node_modules/lodash/_isFlattenable.js": 634, + "./node_modules/react-leaflet/lib/CircleMarker.js": 635, + "./node_modules/react-leaflet/lib/FeatureGroup.js": 636, + "./node_modules/react-leaflet/lib/GeoJson.js": 637, + "./node_modules/react-leaflet/lib/ImageOverlay.js": 638, + "./node_modules/react-leaflet/lib/LayerGroup.js": 639, + "./node_modules/react-leaflet/lib/LayersControl.js": 640, + "./node_modules/react-leaflet/lib/Map.js": 641, + "./node_modules/lodash/isUndefined.js": 642, + "./node_modules/lodash/uniqueId.js": 643, + "./node_modules/react-leaflet/lib/Marker.js": 644, + "./node_modules/react-leaflet/lib/MultiPolygon.js": 645, + "./node_modules/react-leaflet/lib/MultiPolyline.js": 646, + "./node_modules/react-leaflet/lib/Polygon.js": 647, + "./node_modules/react-leaflet/lib/Polyline.js": 648, + "./node_modules/react-leaflet/lib/Popup.js": 649, + "./node_modules/react-leaflet/lib/Rectangle.js": 650, + "./node_modules/react-leaflet/lib/ScaleControl.js": 651, + "./node_modules/react-leaflet/lib/TileLayer.js": 652, + "./node_modules/react-leaflet/lib/WMSTileLayer.js": 653, + "./node_modules/react-leaflet/lib/ZoomControl.js": 654, + "./node_modules/plotly.js/lib/core.js": 655, + "./node_modules/plotly.js/src/core.js": 656, + "./node_modules/plotly.js/src/plotly.js": 657, + "./node_modules/plotly.js/src/plot_api/plot_config.js": 658, + "./node_modules/plotly.js/src/plots/plots.js": 659, + "./node_modules/d3/d3.js": 660, + "./node_modules/fast-isnumeric/index.js": 661, + "./node_modules/plotly.js/src/plot_api/plot_schema.js": 662, + "./node_modules/plotly.js/src/registry.js": 663, + "./node_modules/plotly.js/src/lib/loggers.js": 664, + "./node_modules/plotly.js/src/lib/noop.js": 665, + "./node_modules/plotly.js/src/lib/push_unique.js": 666, + "./node_modules/plotly.js/src/plots/attributes.js": 667, + "./node_modules/plotly.js/src/components/fx/attributes.js": 668, + "./node_modules/plotly.js/src/lib/extend.js": 669, + "./node_modules/plotly.js/src/lib/is_plain_object.js": 670, + "./node_modules/plotly.js/src/plots/font_attributes.js": 671, + "./node_modules/plotly.js/src/lib/index.js": 672, + "./node_modules/plotly.js/src/constants/numerical.js": 673, + "./node_modules/plotly.js/src/lib/nested_property.js": 674, + "./node_modules/plotly.js/src/lib/is_array.js": 675, + "./node_modules/plotly.js/src/plot_api/container_array_match.js": 676, + "./node_modules/plotly.js/src/lib/mod.js": 677, + "./node_modules/plotly.js/src/lib/to_log_range.js": 678, + "./node_modules/plotly.js/src/lib/relink_private.js": 679, + "./node_modules/plotly.js/src/lib/ensure_array.js": 680, + "./node_modules/plotly.js/src/lib/coerce.js": 681, + "./node_modules/tinycolor2/tinycolor.js": 682, + "./node_modules/plotly.js/src/components/colorscale/get_scale.js": 683, + "./node_modules/plotly.js/src/components/colorscale/scales.js": 684, + "./node_modules/plotly.js/src/components/colorscale/default_scale.js": 685, + "./node_modules/plotly.js/src/components/colorscale/is_valid_scale_array.js": 686, + "./node_modules/plotly.js/src/lib/dates.js": 687, + "./node_modules/plotly.js/src/lib/search.js": 688, + "./node_modules/plotly.js/src/lib/stats.js": 689, + "./node_modules/plotly.js/src/lib/matrix.js": 690, + "./node_modules/plotly.js/src/lib/notifier.js": 691, + "./node_modules/plotly.js/src/lib/filter_unique.js": 692, + "./node_modules/plotly.js/src/lib/filter_visible.js": 693, + "./node_modules/plotly.js/src/lib/clean_number.js": 694, + "./node_modules/plotly.js/src/lib/identity.js": 695, + "./node_modules/plotly.js/src/plots/layout_attributes.js": 696, + "./node_modules/plotly.js/src/components/color/attributes.js": 697, + "./node_modules/plotly.js/src/plots/frame_attributes.js": 698, + "./node_modules/plotly.js/src/plots/animation_attributes.js": 699, + "./node_modules/plotly.js/src/plots/polar/area_attributes.js": 700, + "./node_modules/plotly.js/src/traces/scatter/attributes.js": 701, + "./node_modules/plotly.js/src/components/colorscale/color_attributes.js": 702, + "./node_modules/plotly.js/src/components/colorscale/attributes.js": 703, + "./node_modules/plotly.js/src/components/errorbars/attributes.js": 704, + "./node_modules/plotly.js/src/components/colorbar/attributes.js": 705, + "./node_modules/plotly.js/src/plots/cartesian/layout_attributes.js": 706, + "./node_modules/plotly.js/src/components/drawing/attributes.js": 707, + "./node_modules/plotly.js/src/plots/cartesian/constants.js": 708, + "./node_modules/plotly.js/src/components/drawing/index.js": 709, + "./node_modules/plotly.js/src/components/color/index.js": 710, + "./node_modules/plotly.js/src/components/colorscale/index.js": 711, + "./node_modules/plotly.js/src/components/colorscale/defaults.js": 712, + "./node_modules/plotly.js/src/components/colorbar/has_colorbar.js": 713, + "./node_modules/plotly.js/src/components/colorbar/defaults.js": 714, + "./node_modules/plotly.js/src/plots/cartesian/tick_value_defaults.js": 715, + "./node_modules/plotly.js/src/plots/cartesian/tick_mark_defaults.js": 716, + "./node_modules/plotly.js/src/plots/cartesian/tick_label_defaults.js": 717, + "./node_modules/plotly.js/src/components/colorscale/is_valid_scale.js": 718, + "./node_modules/plotly.js/src/components/colorscale/flip_scale.js": 719, + "./node_modules/plotly.js/src/components/colorscale/calc.js": 720, + "./node_modules/plotly.js/src/components/colorscale/has_colorscale.js": 721, + "./node_modules/plotly.js/src/components/colorscale/extract_scale.js": 722, + "./node_modules/plotly.js/src/components/colorscale/make_color_scale_func.js": 723, + "./node_modules/plotly.js/src/lib/svg_text_utils.js": 724, + "./node_modules/plotly.js/src/constants/xmlns_namespaces.js": 725, + "./node_modules/plotly.js/src/constants/string_mappings.js": 726, + "./node_modules/plotly.js/src/constants/alignment.js": 727, + "./node_modules/plotly.js/src/traces/scatter/subtypes.js": 728, + "./node_modules/plotly.js/src/traces/scatter/make_bubble_size_func.js": 729, + "./node_modules/plotly.js/src/components/drawing/symbol_defs.js": 730, + "./node_modules/plotly.js/src/traces/scatter/constants.js": 731, + "./node_modules/plotly.js/src/plots/polar/axis_attributes.js": 732, + "./node_modules/plotly.js/src/components/errorbars/index.js": 733, + "./node_modules/plotly.js/src/components/errorbars/defaults.js": 734, + "./node_modules/plotly.js/src/components/errorbars/calc.js": 735, + "./node_modules/plotly.js/src/plots/cartesian/axes.js": 736, + "./node_modules/plotly.js/src/components/titles/index.js": 737, + "./node_modules/plotly.js/src/constants/interactions.js": 738, + "./node_modules/plotly.js/src/plots/cartesian/layout_defaults.js": 739, + "./node_modules/plotly.js/src/plots/cartesian/type_defaults.js": 740, + "./node_modules/plotly.js/src/plots/cartesian/axis_autotype.js": 741, + "./node_modules/plotly.js/src/plots/cartesian/axis_ids.js": 742, + "./node_modules/plotly.js/src/plots/cartesian/axis_defaults.js": 743, + "./node_modules/plotly.js/src/plots/cartesian/category_order_defaults.js": 744, + "./node_modules/plotly.js/src/plots/cartesian/set_convert.js": 745, + "./node_modules/plotly.js/src/plots/cartesian/ordered_categories.js": 746, + "./node_modules/plotly.js/src/plots/cartesian/constraint_defaults.js": 747, + "./node_modules/plotly.js/src/plots/cartesian/position_defaults.js": 748, + "./node_modules/plotly.js/src/components/errorbars/compute_error.js": 749, + "./node_modules/plotly.js/src/components/errorbars/plot.js": 750, + "./node_modules/plotly.js/src/components/errorbars/style.js": 751, + "./node_modules/plotly.js/src/plots/command.js": 752, + "./node_modules/plotly.js/src/components/modebar/index.js": 753, + "./node_modules/plotly.js/src/components/modebar/manage.js": 754, + "./node_modules/plotly.js/src/components/modebar/modebar.js": 755, + "./node_modules/plotly.js/build/ploticon.js": 756, + "./node_modules/plotly.js/src/components/modebar/buttons.js": 757, + "./node_modules/plotly.js/src/snapshot/download.js": 758, + "./node_modules/plotly.js/src/plot_api/to_image.js": 759, + "./node_modules/plotly.js/src/snapshot/helpers.js": 760, + "./node_modules/plotly.js/src/snapshot/cloneplot.js": 761, + "./node_modules/plotly.js/src/snapshot/tosvg.js": 762, + "./node_modules/plotly.js/src/snapshot/svgtoimg.js": 763, + "./node_modules/events/events.js": 764, + "./node_modules/plotly.js/src/snapshot/filesaver.js": 765, + "./node_modules/plotly.js/src/plot_api/plot_api.js": 766, + "./node_modules/plotly.js/src/lib/events.js": 767, + "./node_modules/plotly.js/src/lib/queue.js": 768, + "./node_modules/plotly.js/src/plots/polar/index.js": 769, + "./node_modules/plotly.js/src/plots/polar/micropolar.js": 770, + "./node_modules/plotly.js/src/plots/polar/micropolar_manager.js": 771, + "./node_modules/plotly.js/src/plots/polar/undo_manager.js": 772, + "./node_modules/plotly.js/src/plots/cartesian/graph_interact.js": 773, + "./node_modules/plotly.js/src/components/fx/index.js": 774, + "./node_modules/plotly.js/src/components/dragelement/index.js": 775, + "./node_modules/plotly.js/src/components/dragelement/align.js": 776, + "./node_modules/plotly.js/src/components/dragelement/cursor.js": 777, + "./node_modules/plotly.js/src/components/dragelement/unhover.js": 778, + "./node_modules/plotly.js/src/components/fx/helpers.js": 779, + "./node_modules/plotly.js/src/components/fx/constants.js": 780, + "./node_modules/plotly.js/src/components/fx/layout_attributes.js": 781, + "./node_modules/plotly.js/src/components/fx/layout_global_defaults.js": 782, + "./node_modules/plotly.js/src/components/fx/hoverlabel_defaults.js": 783, + "./node_modules/plotly.js/src/components/fx/defaults.js": 784, + "./node_modules/plotly.js/src/components/fx/layout_defaults.js": 785, + "./node_modules/plotly.js/src/components/fx/calc.js": 786, + "./node_modules/plotly.js/src/components/fx/hover.js": 787, + "./node_modules/plotly.js/src/lib/override_cursor.js": 788, + "./node_modules/plotly.js/src/lib/setcursor.js": 789, + "./node_modules/plotly.js/src/components/fx/click.js": 790, + "./node_modules/plotly.js/src/plots/cartesian/dragbox.js": 791, + "./node_modules/plotly.js/src/plots/cartesian/select.js": 792, + "./node_modules/plotly.js/src/lib/polygon.js": 793, + "./node_modules/plotly.js/src/plots/cartesian/scale_zoom.js": 794, + "./node_modules/plotly.js/src/plot_api/manage_arrays.js": 795, + "./node_modules/plotly.js/src/plot_api/helpers.js": 796, + "./node_modules/gl-mat4/fromQuat.js": 797, + "./node_modules/plotly.js/src/plot_api/subroutines.js": 798, + "./node_modules/plotly.js/src/plots/cartesian/constraints.js": 799, + "./node_modules/plotly.js/node_modules/es6-promise/dist/es6-promise.js": 800, + "./node_modules/plotly.js/build/plotcss.js": 802, + "./node_modules/plotly.js/src/fonts/mathjax_config.js": 803, + "./node_modules/plotly.js/src/plot_api/set_plot_config.js": 804, + "./node_modules/plotly.js/src/plot_api/register.js": 805, + "./node_modules/plotly.js/src/plot_api/validate.js": 806, + "./node_modules/plotly.js/src/traces/scatter/index.js": 807, + "./node_modules/plotly.js/src/traces/scatter/defaults.js": 808, + "./node_modules/plotly.js/src/traces/scatter/xy_defaults.js": 809, + "./node_modules/plotly.js/src/traces/scatter/marker_defaults.js": 810, + "./node_modules/plotly.js/src/traces/scatter/line_defaults.js": 811, + "./node_modules/plotly.js/src/traces/scatter/line_shape_defaults.js": 812, + "./node_modules/plotly.js/src/traces/scatter/text_defaults.js": 813, + "./node_modules/plotly.js/src/traces/scatter/fillcolor_defaults.js": 814, + "./node_modules/plotly.js/src/traces/scatter/clean_data.js": 815, + "./node_modules/plotly.js/src/traces/scatter/calc.js": 816, + "./node_modules/plotly.js/src/traces/scatter/colorscale_calc.js": 817, + "./node_modules/plotly.js/src/traces/scatter/arrays_to_calcdata.js": 818, + "./node_modules/plotly.js/src/traces/scatter/plot.js": 819, + "./node_modules/plotly.js/src/traces/scatter/line_points.js": 820, + "./node_modules/plotly.js/src/traces/scatter/link_traces.js": 821, + "./node_modules/plotly.js/src/traces/scatter/colorbar.js": 822, + "./node_modules/plotly.js/src/components/colorbar/draw.js": 823, + "./node_modules/plotly.js/src/traces/scatter/style.js": 824, + "./node_modules/plotly.js/src/traces/scatter/hover.js": 825, + "./node_modules/plotly.js/src/traces/scatter/get_trace_color.js": 826, + "./node_modules/plotly.js/src/traces/scatter/select.js": 827, + "./node_modules/plotly.js/src/plots/cartesian/index.js": 828, + "./node_modules/plotly.js/src/plots/cartesian/attributes.js": 829, + "./node_modules/plotly.js/src/plots/cartesian/transition_axes.js": 830, + "./node_modules/plotly.js/src/components/legend/index.js": 831, + "./node_modules/plotly.js/src/components/legend/attributes.js": 832, + "./node_modules/plotly.js/src/components/legend/defaults.js": 833, + "./node_modules/plotly.js/src/components/legend/helpers.js": 834, + "./node_modules/plotly.js/src/components/legend/draw.js": 835, + "./node_modules/plotly.js/src/components/legend/constants.js": 836, + "./node_modules/plotly.js/src/components/legend/get_legend_data.js": 837, + "./node_modules/plotly.js/src/components/legend/style.js": 838, + "./node_modules/plotly.js/src/traces/pie/style_one.js": 839, + "./node_modules/plotly.js/src/components/legend/anchor_utils.js": 840, + "./node_modules/plotly.js/src/components/annotations/index.js": 841, + "./node_modules/plotly.js/src/components/annotations/draw.js": 842, + "./node_modules/plotly.js/src/components/annotations/draw_arrow_head.js": 843, + "./node_modules/plotly.js/src/components/annotations/arrow_paths.js": 844, + "./node_modules/plotly.js/src/components/annotations/click.js": 845, + "./node_modules/plotly.js/src/components/annotations/attributes.js": 846, + "./node_modules/plotly.js/src/components/annotations/defaults.js": 847, + "./node_modules/plotly.js/src/plots/array_container_defaults.js": 848, + "./node_modules/plotly.js/src/components/annotations/annotation_defaults.js": 849, + "./node_modules/plotly.js/src/components/annotations/common_defaults.js": 850, + "./node_modules/plotly.js/src/components/annotations/calc_autorange.js": 851, + "./node_modules/plotly.js/src/components/annotations/convert_coords.js": 852, + "./node_modules/plotly.js/src/components/annotations3d/index.js": 853, + "./node_modules/plotly.js/src/components/annotations3d/attributes.js": 854, + "./node_modules/plotly.js/src/components/annotations3d/defaults.js": 855, + "./node_modules/plotly.js/src/components/annotations3d/convert.js": 856, + "./node_modules/plotly.js/src/components/annotations3d/draw.js": 857, + "./node_modules/plotly.js/src/plots/gl3d/project.js": 858, + "./node_modules/plotly.js/src/components/shapes/index.js": 859, + "./node_modules/plotly.js/src/components/shapes/draw.js": 860, + "./node_modules/plotly.js/src/components/shapes/constants.js": 861, + "./node_modules/plotly.js/src/components/shapes/helpers.js": 862, + "./node_modules/plotly.js/src/components/shapes/attributes.js": 863, + "./node_modules/plotly.js/src/components/shapes/defaults.js": 864, + "./node_modules/plotly.js/src/components/shapes/shape_defaults.js": 865, + "./node_modules/plotly.js/src/components/shapes/calc_autorange.js": 866, + "./node_modules/plotly.js/src/components/images/index.js": 867, + "./node_modules/plotly.js/src/components/images/attributes.js": 868, + "./node_modules/plotly.js/src/components/images/defaults.js": 869, + "./node_modules/plotly.js/src/components/images/draw.js": 870, + "./node_modules/plotly.js/src/components/images/convert_coords.js": 871, + "./node_modules/plotly.js/src/components/updatemenus/index.js": 872, + "./node_modules/plotly.js/src/components/updatemenus/constants.js": 873, + "./node_modules/plotly.js/src/components/updatemenus/attributes.js": 874, + "./node_modules/plotly.js/src/plots/pad_attributes.js": 875, + "./node_modules/plotly.js/src/components/updatemenus/defaults.js": 876, + "./node_modules/plotly.js/src/components/updatemenus/draw.js": 877, + "./node_modules/plotly.js/src/components/updatemenus/scrollbox.js": 878, + "./node_modules/plotly.js/src/components/sliders/index.js": 879, + "./node_modules/plotly.js/src/components/sliders/constants.js": 880, + "./node_modules/plotly.js/src/components/sliders/attributes.js": 881, + "./node_modules/plotly.js/src/components/sliders/defaults.js": 882, + "./node_modules/plotly.js/src/components/sliders/draw.js": 883, + "./node_modules/plotly.js/src/components/rangeslider/index.js": 884, + "./node_modules/plotly.js/src/components/rangeslider/attributes.js": 885, + "./node_modules/plotly.js/src/components/rangeslider/defaults.js": 886, + "./node_modules/plotly.js/src/components/rangeslider/calc_autorange.js": 887, + "./node_modules/plotly.js/src/components/rangeslider/constants.js": 888, + "./node_modules/plotly.js/src/components/rangeslider/draw.js": 889, + "./node_modules/plotly.js/src/components/rangeselector/index.js": 890, + "./node_modules/plotly.js/src/components/rangeselector/attributes.js": 891, + "./node_modules/plotly.js/src/components/rangeselector/button_attributes.js": 892, + "./node_modules/plotly.js/src/components/rangeselector/defaults.js": 893, + "./node_modules/plotly.js/src/components/rangeselector/constants.js": 894, + "./node_modules/plotly.js/src/components/rangeselector/draw.js": 895, + "./node_modules/plotly.js/src/components/rangeselector/get_update_object.js": 896, + "./node_modules/plotly.js/src/snapshot/index.js": 897, + "./node_modules/plotly.js/src/snapshot/toimage.js": 898, + "./node_modules/plotly.js/lib/bar.js": 899, + "./node_modules/plotly.js/src/traces/bar/index.js": 900, + "./node_modules/plotly.js/src/traces/bar/attributes.js": 901, + "./node_modules/plotly.js/src/traces/bar/layout_attributes.js": 902, + "./node_modules/plotly.js/src/traces/bar/defaults.js": 903, + "./node_modules/plotly.js/src/traces/bar/style_defaults.js": 904, + "./node_modules/plotly.js/src/traces/bar/layout_defaults.js": 905, + "./node_modules/plotly.js/src/traces/bar/calc.js": 906, + "./node_modules/plotly.js/src/traces/bar/arrays_to_calcdata.js": 907, + "./node_modules/plotly.js/src/traces/bar/set_positions.js": 908, + "./node_modules/plotly.js/src/traces/bar/sieve.js": 909, + "./node_modules/plotly.js/src/traces/bar/plot.js": 910, + "./node_modules/plotly.js/src/traces/bar/style.js": 911, + "./node_modules/plotly.js/src/traces/bar/hover.js": 912, + "./node_modules/plotly.js/lib/pie.js": 913, + "./node_modules/plotly.js/src/traces/pie/index.js": 914, + "./node_modules/plotly.js/src/traces/pie/attributes.js": 915, + "./node_modules/plotly.js/src/traces/pie/defaults.js": 916, + "./node_modules/plotly.js/src/traces/pie/layout_defaults.js": 917, + "./node_modules/plotly.js/src/traces/pie/layout_attributes.js": 918, + "./node_modules/plotly.js/src/traces/pie/calc.js": 919, + "./node_modules/plotly.js/src/traces/pie/helpers.js": 920, + "./node_modules/plotly.js/src/traces/pie/plot.js": 921, + "./node_modules/plotly.js/src/traces/pie/style.js": 922, + "./node_modules/plotly.js/src/traces/pie/base_plot.js": 923, + "./node_modules/immutable/dist/immutable.js": 924 + } +} \ No newline at end of file diff --git a/ui/index.html b/ui/index.html index 53e7d533c..22db9c949 100644 --- a/ui/index.html +++ b/ui/index.html @@ -8,6 +8,7 @@
- + + diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index e4efd0281..cb81110e1 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -346,6 +346,24 @@ class CorruptionRiskDashboard extends React.Component { ); })} + navigate('suppliers')} + className={cn({ active: page === 'suppliers' })} + key="suppliers" + > + Suppliers icon + {this.t('crd:contracts:baseInfo:suppliers')} + + navigate('procuring-entities')} + className={cn({ active: page === 'procuring-entities' })} + key="procuring-entities" + > + Procuring entities icon + {this.t('crd:contracts:menu:procuringEntities')} + navigate('contracts')} diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index ac29b1057..4e6dc31c7 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -290,5 +290,6 @@ "crd:contracts:baseInfo:tenderDates": "Tender Open/Close Dates", "crd:contracts:baseInfo:awardDate": "Award Date", "crd:contracts:baseInfo:contractAmount": "Contract Amount", - "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates" + "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates", + "crd:contracts:menu:procuringEntities": "Procuring entities" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 69166539f..04d0e69aa 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -290,5 +290,6 @@ "crd:contracts:baseInfo:tenderDates": "Tender Open/Close Dates", "crd:contracts:baseInfo:awardDate": "Award Date", "crd:contracts:baseInfo:contractAmount": "Contract Amount", - "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates" + "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates", + "crd:contracts:menu:procuringEntities": "Procuring entities" } From 99ee17f6791ce846b5c9105acc4e511a22ff8947 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 14:58:47 +0200 Subject: [PATCH 214/702] OCE-375 Changed menu hover and active colors --- ui/oce/corruption-risk/index.jsx | 25 +++++++++++++++---------- ui/oce/corruption-risk/style.less | 21 +++++++++++++++++---- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index cb81110e1..73b485c68 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -252,9 +252,9 @@ class CorruptionRiskDashboard extends React.Component { } render() { - const { dashboardSwitcherOpen, corruptionType, filterBoxIndex, currentFiltersState, + const { dashboardSwitcherOpen, filterBoxIndex, currentFiltersState, appliedFilters, data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, - disabledApiSecurity, locale } = this.state; + disabledApiSecurity } = this.state; const { onSwitch, route, navigate } = this.props; const translations = this.getTranslations(); const [page] = route; @@ -306,9 +306,9 @@ class CorruptionRiskDashboard extends React.Component { this.setState({ currentFiltersState })} onApply={filtersToApply => this.setState({ - filterBoxIndex: null, - appliedFilters: filtersToApply, - currentFiltersState: filtersToApply, + filterBoxIndex: null, + appliedFilters: filtersToApply, + currentFiltersState: filtersToApply, })} translations={translations} currentBoxIndex={filterBoxIndex} @@ -334,13 +334,18 @@ class CorruptionRiskDashboard extends React.Component { .filter(key => indicatorTypesMapping[key].types.indexOf(slug) > -1) .length; + let corruptionType; + if (page === 'type') { + [, corruptionType] = route; + } + return ( navigate('type', slug)} - className={cn({ active: page === 'type' && slug === corruptionType })} + className={cn({ active: slug === corruptionType })} key={slug} - > + > Tab icon {this.t(`crd:corruptionType:${slug}:name`)} ({count}) @@ -349,7 +354,7 @@ class CorruptionRiskDashboard extends React.Component { navigate('suppliers')} - className={cn({ active: page === 'suppliers' })} + className={cn('archive-link', { active: page === 'suppliers' })} key="suppliers" > Suppliers icon @@ -358,7 +363,7 @@ class CorruptionRiskDashboard extends React.Component { navigate('procuring-entities')} - className={cn({ active: page === 'procuring-entities' })} + className={cn('archive-link', { active: page === 'procuring-entities' })} key="procuring-entities" > Procuring entities icon @@ -367,7 +372,7 @@ class CorruptionRiskDashboard extends React.Component { navigate('contracts')} - className={cn({ active: page === 'contracts' })} + className={cn('archive-link', 'contracts-link', { active: page === 'contracts' })} key="contracts" > Contracts icon diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index d0560f491..f1667586c 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -288,15 +288,28 @@ &:first-child{ border-top: 1px solid #e6eaee; } - &.active, &:hover{ - background: #ffffff; - color: #2b9ff6; - } + .count{ color: #a9b2c0; display: inline-block; margin-left: 10px; } + + &.archive-link { + background: #ecf7ff; + } + + &.contracts-link{ + background: #d9e3eb; + } + + &.active, &:hover{ + background: #35ae46; + color: white; + .count { + color: white; + } + } img{ display: inline-block; margin-right: 10px; From b8eceb6926c0edeca09380ab8b35475591cd1fa3 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 30 Oct 2017 17:25:10 +0200 Subject: [PATCH 215/702] OCE-386 renamed redirect on backend side --- .../main/java/org/devgateway/toolkit/web/spring/MvcConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java index fb38780a7..c50c67499 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java @@ -37,7 +37,7 @@ public void addViewControllers(final ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/dashboard").setViewName("redirect:/ui/index.html"); registry.addViewController("/corruption-risk") - .setViewName("redirect:/ui/index.html?corruption-risk-dashboard"); + .setViewName("redirect:/ui/index.html#!/crd"); } @Bean From 91ad15176c395ddd06dd6c30a87bc129809d365c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 18:21:01 +0200 Subject: [PATCH 216/702] OCE-375 New icons --- ui/assets/icons/COLLUSION.png | Bin 15636 -> 0 bytes ui/assets/icons/FRAUD.png | Bin 15598 -> 0 bytes ui/assets/icons/RIGGING.png | Bin 15414 -> 0 bytes ui/assets/icons/blue/COLLUSION.svg | 15 ++++++++++++++ ui/assets/icons/blue/FRAUD.svg | 18 +++++++++++++++++ ui/assets/icons/blue/RIGGING.svg | 21 ++++++++++++++++++++ ui/assets/icons/blue/contracts.svg | 12 +++++++++++ ui/assets/icons/blue/overview.svg | 8 ++++++++ ui/assets/icons/blue/procuring-entities.svg | 14 +++++++++++++ ui/assets/icons/blue/suppliers.svg | 15 ++++++++++++++ ui/assets/icons/white/overview.svg | 8 ++++++++ ui/oce/corruption-risk/index.jsx | 16 +++++++++------ 12 files changed, 121 insertions(+), 6 deletions(-) delete mode 100644 ui/assets/icons/COLLUSION.png delete mode 100644 ui/assets/icons/FRAUD.png delete mode 100644 ui/assets/icons/RIGGING.png create mode 100644 ui/assets/icons/blue/COLLUSION.svg create mode 100644 ui/assets/icons/blue/FRAUD.svg create mode 100644 ui/assets/icons/blue/RIGGING.svg create mode 100644 ui/assets/icons/blue/contracts.svg create mode 100644 ui/assets/icons/blue/overview.svg create mode 100644 ui/assets/icons/blue/procuring-entities.svg create mode 100644 ui/assets/icons/blue/suppliers.svg create mode 100644 ui/assets/icons/white/overview.svg diff --git a/ui/assets/icons/COLLUSION.png b/ui/assets/icons/COLLUSION.png deleted file mode 100644 index 1b959ed1c1395e4216607b49b5515d5c42176681..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15636 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 wcfF~uX{w;(Fc(T1pF diff --git a/ui/assets/icons/FRAUD.png b/ui/assets/icons/FRAUD.png deleted file mode 100644 index 1239f11334ee11fdb5494a90d962f9f7a7d2c734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15598 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 OBnSJ4iq5?sm-RnRo4Tq1 diff --git a/ui/assets/icons/RIGGING.png b/ui/assets/icons/RIGGING.png deleted file mode 100644 index c3f7f6fafbc4d255287b5dfb06502fc65959f053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15414 zcmeI3Yg7|w8pp?at6^!Y^@b=LgQe?gGPw`}vmqQID!2xbi-d&=Lo&c3nB$|K~bw>RhEmMT2Jc*p{rE7C|=ODvdGRPF}xr+?Vf&|oRiGM z`~LsW^Um+RFkfavMEF8?-ZUNrLGEFp>PY(P!+c*IL;wCgu%n#5j5CMEkr3qeCi5Ku zZ7ZG%LH@JwXf37HEJk#MQH1J=WK3i;nrSrzDT8cgRJRhN_{mr*ZVD819X%o7jJ ziS%|Pd7ej&kva=*rf|ZtM`)88H(@(mu(MK64sD5o8)MOi5Swu!ZS4(P&(?ha9ibIAm#(n!k!h z;6ys4lZ?(ZV{jx!5*Zd9Hh&d;E8auhJ%y+APtJf$OwX`m$87j8Hm1j6vo8h53@Pb* zMM5kXN)eW5f-tBYt1RLa9Tuim(G4OT z*CUvpzn@s4R|x%+QB)}L^OXn#eD#=6j-kn@R4?~e$OG&UH5$5~pM02*fWzqG^>v zDBy5`G+1K}7b_xAz~KUEu*MuNRz#qH!v)e{jX7Mbh(G~{3#7psbGTR$fdUQ}NP{)z zaIqo+1spDr25ZdWVnqZBI9wnN)|kV^iU<^NxIh}LF^7v45h&nrfizfS4i_sTP{82= zX|To|E>=XKfWrmSV2wFktcXAXhYO^^8gsZ<5rF~@7f6FO=5Vnh0tFl{kOpha;bKJu z3OHOK4c3^$#fk_NaJWDktTBg+6%i=laDg;fV-6Q9B2d8L0%@?uFU7^{fAtSD(eL|N z=@Y1A;Qd5cFdb1R+HbG=%AlK&KM#C)Q{e$%D*}F>`-2P z<)`Q7fAC#lX-V&%;%Twd1i|r(Umcyh`A&OaZegL%gOQ|i>eQtw)&7Kd@7l`7;OMPE zJ_&1{glx>Yu~*Teol|QoJyTiV*~UB6QZi%B+`IQ%S8sVGOKMu#yyRJN&|O?Q{o1r} zxAy9CufB^v)NQgfHN9RpA+0QL>}8MgedES`8PfGOm1bC^I~J1t6rTDZpjzI$r};lU z(>=TKLSDw!pK>yJ_t9IqTf7cjzk54x(#*I-@4EcniQnp_y{E=0dV5Y*6?%DgcbMeL zBl{}qybiVPux_dC{;aulMuZo1tZ3_vPPjR2;f)=d-j=J8wnH28(4OC)Hq0IkwLEEQ zDhkhvZ(q@{z5ZsXIbEGN9!mWB(ieM!9-aTqV^~x%W%E5lN&dgE6YYDDwx&`|`pLOD zY1-;d4adJ+mjBrkFSq!6>g}_SMeL|TyZ&&#IJoBMMROAQVMyGJyph|52Dm5Uuqb|A z1n_LYjIo{wwq+&Y-@!-}>GlS`|UE{eNXjW(}oL>njA8e$7x@9}89 zMy*;SEIS(fzMEhBsf%3`-yVB^o>?!UgQF3Z|+`7IW;U3SRcP%n!G2yc@0fRBtFsWTWhjQQm?qjE_gte{rmL7 zy-^>xucb-C-s}yVAFeJBPFnXr`B8Vm diff --git a/ui/assets/icons/blue/COLLUSION.svg b/ui/assets/icons/blue/COLLUSION.svg new file mode 100644 index 000000000..59ec94d97 --- /dev/null +++ b/ui/assets/icons/blue/COLLUSION.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/blue/FRAUD.svg b/ui/assets/icons/blue/FRAUD.svg new file mode 100644 index 000000000..8ebd9002e --- /dev/null +++ b/ui/assets/icons/blue/FRAUD.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/ui/assets/icons/blue/RIGGING.svg b/ui/assets/icons/blue/RIGGING.svg new file mode 100644 index 000000000..dccc4cd40 --- /dev/null +++ b/ui/assets/icons/blue/RIGGING.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/ui/assets/icons/blue/contracts.svg b/ui/assets/icons/blue/contracts.svg new file mode 100644 index 000000000..6e323714c --- /dev/null +++ b/ui/assets/icons/blue/contracts.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/assets/icons/blue/overview.svg b/ui/assets/icons/blue/overview.svg new file mode 100644 index 000000000..7b18b4996 --- /dev/null +++ b/ui/assets/icons/blue/overview.svg @@ -0,0 +1,8 @@ + + + + diff --git a/ui/assets/icons/blue/procuring-entities.svg b/ui/assets/icons/blue/procuring-entities.svg new file mode 100644 index 000000000..ef5096987 --- /dev/null +++ b/ui/assets/icons/blue/procuring-entities.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/ui/assets/icons/blue/suppliers.svg b/ui/assets/icons/blue/suppliers.svg new file mode 100644 index 000000000..49438aae6 --- /dev/null +++ b/ui/assets/icons/blue/suppliers.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/white/overview.svg b/ui/assets/icons/white/overview.svg new file mode 100644 index 000000000..a6ee1b391 --- /dev/null +++ b/ui/assets/icons/white/overview.svg @@ -0,0 +1,8 @@ + + + + diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 73b485c68..7d9e97504 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -339,14 +339,18 @@ class CorruptionRiskDashboard extends React.Component { [, corruptionType] = route; } + const active = slug === corruptionType; + let icon = `${active ? 'white' : 'blue'}/${slug}.svg`; + + return ( navigate('type', slug)} - className={cn({ active: slug === corruptionType })} + className={cn({ active })} key={slug} - > - Tab icon + > + Tab icon {this.t(`crd:corruptionType:${slug}:name`)} ({count}) ); @@ -357,7 +361,7 @@ class CorruptionRiskDashboard extends React.Component { className={cn('archive-link', { active: page === 'suppliers' })} key="suppliers" > - Suppliers icon + Suppliers icon {this.t('crd:contracts:baseInfo:suppliers')} - Procuring entities icon + Procuring entities icon {this.t('crd:contracts:menu:procuringEntities')} - Contracts icon + Contracts icon {this.t('crd:general:contracts')} From c10ab2928543026e8e2d86f1043bc5deae03049b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 19:32:50 +0200 Subject: [PATCH 217/702] OCE-375 Active & hover icons --- ui/assets/icons/white/COLLUSION.svg | 15 ++++++++++ ui/assets/icons/white/FRAUD.svg | 18 ++++++++++++ ui/assets/icons/white/RIGGING.svg | 21 +++++++++++++ ui/assets/icons/white/contracts.svg | 12 ++++++++ ui/assets/icons/white/procuring-entities.svg | 14 +++++++++ ui/assets/icons/white/suppliers.svg | 15 ++++++++++ ui/oce/corruption-risk/index.jsx | 31 +++++++++++++------- ui/oce/corruption-risk/style.less | 22 ++++++++++---- 8 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 ui/assets/icons/white/COLLUSION.svg create mode 100644 ui/assets/icons/white/FRAUD.svg create mode 100644 ui/assets/icons/white/RIGGING.svg create mode 100644 ui/assets/icons/white/contracts.svg create mode 100644 ui/assets/icons/white/procuring-entities.svg create mode 100644 ui/assets/icons/white/suppliers.svg diff --git a/ui/assets/icons/white/COLLUSION.svg b/ui/assets/icons/white/COLLUSION.svg new file mode 100644 index 000000000..3ec71ac95 --- /dev/null +++ b/ui/assets/icons/white/COLLUSION.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/assets/icons/white/FRAUD.svg b/ui/assets/icons/white/FRAUD.svg new file mode 100644 index 000000000..a770cefe8 --- /dev/null +++ b/ui/assets/icons/white/FRAUD.svg @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/ui/assets/icons/white/RIGGING.svg b/ui/assets/icons/white/RIGGING.svg new file mode 100644 index 000000000..9b869512a --- /dev/null +++ b/ui/assets/icons/white/RIGGING.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/ui/assets/icons/white/contracts.svg b/ui/assets/icons/white/contracts.svg new file mode 100644 index 000000000..cd9277bc9 --- /dev/null +++ b/ui/assets/icons/white/contracts.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/assets/icons/white/procuring-entities.svg b/ui/assets/icons/white/procuring-entities.svg new file mode 100644 index 000000000..875b0510d --- /dev/null +++ b/ui/assets/icons/white/procuring-entities.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/ui/assets/icons/white/suppliers.svg b/ui/assets/icons/white/suppliers.svg new file mode 100644 index 000000000..a60eea556 --- /dev/null +++ b/ui/assets/icons/white/suppliers.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 7d9e97504..4d5308df7 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -329,6 +329,17 @@ class CorruptionRiskDashboard extends React.Component {

+ navigate()} + className={cn({ active: !page })} + > + Overview icon + Overview icon + {this.t('tabs:overview:title')} + + + {CORRUPTION_TYPES.map((slug) => { const count = Object.keys(indicatorTypesMapping) .filter(key => indicatorTypesMapping[key].types.indexOf(slug) > -1) @@ -339,18 +350,15 @@ class CorruptionRiskDashboard extends React.Component { [, corruptionType] = route; } - const active = slug === corruptionType; - let icon = `${active ? 'white' : 'blue'}/${slug}.svg`; - - return ( navigate('type', slug)} - className={cn({ active })} + className={cn({ active: slug === corruptionType })} key={slug} - > - Tab icon + > + Tab icon + Tab icon {this.t(`crd:corruptionType:${slug}:name`)} ({count}) ); @@ -361,7 +369,8 @@ class CorruptionRiskDashboard extends React.Component { className={cn('archive-link', { active: page === 'suppliers' })} key="suppliers" > - Suppliers icon + Suppliers icon + Suppliers icon {this.t('crd:contracts:baseInfo:suppliers')} - Procuring entities icon + Procuring entities icon + Procuring entities icon {this.t('crd:contracts:menu:procuringEntities')} - Contracts icon + Contracts icon + Contracts icon {this.t('crd:general:contracts')}
diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index f1667586c..6a2aa6119 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -303,17 +303,29 @@ background: #d9e3eb; } + img{ + display: inline-block; + margin-right: 10px; + width: 32px; + &.white { + display: none; + } + } + &.active, &:hover{ background: #35ae46; color: white; .count { color: white; } - } - img{ - display: inline-block; - margin-right: 10px; - width: 32px; + + img.white { + display: inline-block; + } + + img.blue { + display: none; + } } } } From a1af42b04e455d91723448b7cf0bfb1e9dee9922 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 30 Oct 2017 20:35:06 +0200 Subject: [PATCH 218/702] OCE-375 Overview menu item --- ui/oce/corruption-risk/index.jsx | 35 +++++++++----------- ui/oce/corruption-risk/style.less | 53 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 4d5308df7..dfa78f226 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -83,16 +83,16 @@ class CorruptionRiskDashboard extends React.Component { {locale.split('_')[0]} )); - /* return Object.keys(TRANSLATIONS).map(locale => - * ( - * {`${locale}), - * );*/ + /* return Object.keys(TRANSLATIONS).map(locale => + * ( + * {`${locale}), + * );*/ } setLocale(locale) { @@ -319,20 +319,11 @@ class CorruptionRiskDashboard extends React.Component { allMonths={allMonths} />
); } From f035cfa975410afb89da6320cb78fd154291736b Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 6 Nov 2017 17:20:57 +0200 Subject: [PATCH 230/702] OCE-379 added averageNumberOfTenderers endpoint --- ui/oce/visualizations/charts/avg-nr-bids.jsx | 2 +- .../AverageNumberOfTenderersController.java | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/ui/oce/visualizations/charts/avg-nr-bids.jsx b/ui/oce/visualizations/charts/avg-nr-bids.jsx index 89fe1be14..7f4cea481 100644 --- a/ui/oce/visualizations/charts/avg-nr-bids.jsx +++ b/ui/oce/visualizations/charts/avg-nr-bids.jsx @@ -38,7 +38,7 @@ class AvgNrBids extends FrontendDateFilterableChart{ } } -AvgNrBids.endpoint = 'averageNumberOfTenderers'; +AvgNrBids.endpoint = 'averageNumberOfTenderersYearly'; AvgNrBids.excelEP = 'averageNumberBidsExcelChart'; AvgNrBids.getName = t => t('charts:avgNrBids:title'); AvgNrBids.getFillerDatum = seed => Map(seed).set('averageNoTenderers', 0); diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/AverageNumberOfTenderersController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/AverageNumberOfTenderersController.java index aa3321c31..3bc2e557b 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/AverageNumberOfTenderersController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/AverageNumberOfTenderersController.java @@ -55,9 +55,9 @@ public static final class Keys { + "by year read from tender.tenderPeriod.startDate. " + "The number of tenderers are read from tender.numberOfTenderers") - @RequestMapping(value = "/api/averageNumberOfTenderers", + @RequestMapping(value = "/api/averageNumberOfTenderersYearly", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") - public List averageNumberOfTenderers(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + public List averageNumberOfTenderersYearly(@ModelAttribute @Valid final YearFilterPagingRequest filter) { DBObject project = new BasicDBObject(); addYearlyMonthlyProjection(filter, project, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE_REF); @@ -80,4 +80,32 @@ public List averageNumberOfTenderers(@ModelAttribute @Valid final Year return list; } + @ApiOperation(value = "Calculate average number of tenderers. The endpoint can be filtered" + + "by year read from tender.tenderPeriod.startDate. " + + "The number of tenderers are read from tender.numberOfTenderers") + + @RequestMapping(value = "/api/averageNumberOfTenderers", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List averageNumberOfTenderers(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + DBObject project = new BasicDBObject(); + addYearlyMonthlyProjection(filter, project, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE_REF); + project.put("tender.numberOfTenderers", 1); + + Aggregation agg = newAggregation( + match(where("tender.numberOfTenderers").gt(0) + .and(MongoConstants.FieldNames.TENDER_PERIOD_START_DATE).exists(true) + .andOperator(getYearDefaultFilterCriteria(filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), + new CustomProjectionOperation(project), + group().avg("tender.numberOfTenderers") + .as(Keys.AVERAGE_NO_OF_TENDERERS)); + + + AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); + List list = results.getMappedResults(); + return list; + } + + } \ No newline at end of file From 99bc4c6565d34519cfda1368ba311d2c222efc88 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 8 Nov 2017 13:27:51 +0200 Subject: [PATCH 231/702] OCE-380 /api/flaggedRelease/ocid/[id] --- .../ocds/web/rest/controller/OcdsController.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index fa0ce7feb..cd99b4500 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -21,6 +21,7 @@ import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; +import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json.Views; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; @@ -51,6 +52,9 @@ public class OcdsController extends GenericOCDSController { @Autowired private ReleaseRepository releaseRepository; + @Autowired + private FlaggedReleaseRepository flaggedReleaseRepository; + @ApiOperation(value = "Returns a release entity for the given project id. " + "The project id is read from planning.budget.projectID") @RequestMapping(value = "/api/ocds/release/budgetProjectId/{projectId:^[a-zA-Z0-9]*$}", @@ -168,6 +172,17 @@ public List flaggedOcdsReleases(@ModelAttribute @Valid return find; } + @ApiOperation(value = "Returns a release entity for the given open contracting id (OCID).") + @RequestMapping(value = "/api/flaggedRelease/ocid/{ocid}", + method = { RequestMethod.POST, RequestMethod.GET }, + produces = "application/json") + @JsonView(Views.Internal.class) + public FlaggedRelease flaggedReleaseByOcid(@PathVariable final String ocid) { + + FlaggedRelease release = flaggedReleaseRepository.findByOcid(ocid); + return release; + } + @ApiOperation(value = "Returns all available packages, filtered by the given criteria." + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") @RequestMapping(value = "/api/ocds/package/all", method = { RequestMethod.POST, RequestMethod.GET }, From 6e288ff90993953755930beb22a4b00db1520b66 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 16:04:53 +0200 Subject: [PATCH 232/702] OCE-380 Added clickable crosstab --- ui/oce/corruption-risk/clickable-crosstab.jsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 ui/oce/corruption-risk/clickable-crosstab.jsx diff --git a/ui/oce/corruption-risk/clickable-crosstab.jsx b/ui/oce/corruption-risk/clickable-crosstab.jsx new file mode 100644 index 000000000..a04539c08 --- /dev/null +++ b/ui/oce/corruption-risk/clickable-crosstab.jsx @@ -0,0 +1,98 @@ +import Crosstab from './crosstab'; +import cn from 'classnames'; + +class ClickableCrosstab extends Crosstab { + constructor(...args){ + super(...args); + this.state.currentlySelected = false; + } + + row(rowData, rowIndicatorID) { + const { currentlySelected } = this.state; + const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); + return ( + + {rowIndicatorName} + {rowData.getIn([rowIndicatorID, 'count'])} + {rowData.map((datum, indicatorID) => { + if(indicatorID == rowIndicatorID){ + return — + } else { + const percent = datum.get('percent'); + const color = Math.round(255 - 255 * (percent/100)) + const style = {backgroundColor: `rgb(${color}, 255, ${color})`} + const selected = rowIndicatorID == currentlySelected.rowIndicatorID && + indicatorID == currentlySelected.indicatorID; + return ( + this.setState({ currentlySelected: { rowIndicatorID, indicatorID }})} + > + {percent && percent.toFixed(2)} % + + ) + } + }).toArray()} + + ) + } + + maybeGetBox() { + const { currentlySelected } = this.state; + if (!currentlySelected) return null; + const { rowIndicatorID, indicatorID } = currentlySelected; + + const row = this.props.data.get(rowIndicatorID); + const datum = row.get(indicatorID); + + const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); + const rowIndicatorDescription = this.t(`crd:indicators:${rowIndicatorID}:indicator`); + const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); + const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); + const count = datum.get('count'); + const percent = datum.get('percent'); + + return ( +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
+
+
+

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+

{rowIndicatorName}: {rowIndicatorDescription}

+

{this.t('crd:corruptionType:crosstab:popup:and')}

+

{indicatorName}: {indicatorDescription}

+
+
+
+ ); + } + + componentDidMount() { + super.componentDidMount(); + document.body.addEventListener('click', () => this.setState({ currentlySelected: false })) + } + + render(){ + const {indicators, data} = this.props; + if(!data) return null; + if(!data.count()) return null; + return ( +
{ e.stopPropagation() }}> + {super.render()} + {this.maybeGetBox()} +
+ ) + } +} + +export default ClickableCrosstab; From a1eb58f061ce639151936d33bd04081fb415213f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 16:05:09 +0200 Subject: [PATCH 233/702] OCE-380 Passing filters to Contract page --- ui/oce/corruption-risk/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 20039f9f7..d3e37e9ba 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -165,6 +165,8 @@ class CorruptionRiskDashboard extends React.Component { translations={translations} doSearch={query => navigate('contracts', query)} indicatorTypesMapping={indicatorTypesMapping} + filters={filters} + years={years} /> ) } else { From 37ab09891084b22bd8be0bb9377c60f73aae06cb Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 16:05:47 +0200 Subject: [PATCH 234/702] OCE-380 Flag analysis --- .../contracts/single/index.jsx | 88 ++++++++++++------- ui/oce/corruption-risk/contracts/style.less | 20 ++++- ui/oce/corruption-risk/style.less | 30 +++++-- 3 files changed, 101 insertions(+), 37 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index ef4fe3038..fca9281cc 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -7,15 +7,9 @@ import TopSearch from '../top-search'; import NrOfBidders from './donuts/nr-of-bidders'; import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; -import Crosstab from '../../crosstab'; +import Crosstab from '../../clickable-crosstab'; import { CORRUPTION_TYPES } from '../../constants'; -const indicators = { - FRAUD: ["i002", "i085", "i171"], - RIGGING: ["i038", "i007", "i077", "i019", "i180", "i002", "i171"], - COLLUSION: ["i085"] -}; - class Info extends translatable(Visualization) { constructor(...args){ super(...args); @@ -24,7 +18,7 @@ class Info extends translatable(Visualization) { getCustomEP(){ const { id } = this.props; - return `ocds/release/ocid/${id}`; + return `flaggedRelease/ocid/${id}`; } getSuppliers(){ @@ -54,7 +48,7 @@ class Info extends translatable(Visualization) { {title &&
{this.t('crd:general:contract:title')}
} {title &&
{title}
} - +
- + - {data.map((contract) => { - const id = contract.get('ocid'); - const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); - const flagType = contract.getIn(['flags', 'flaggedStats', 0, 'type']); - return ( - - - - - - - - - - - ); - })} - + navigate('contract', id)} + > + {content} + ); } + + render() { + const { data } = this.props; + if (!data || !data.count()) return null; + const jsData = data.map((contract) => { + const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + + ' ' + + contract.getIn(['tender', 'value', 'currency'], ''); + + const winningAward = contract.get('awards', List()).find(award => award.get('status') === 'active'); + let awardAmount = 'N/A'; + if (winningAward) { + awardAmount = winningAward.getIn(['value', 'amount'], 'N/A') + + ' ' + + winningAward.getIn(['value', 'currency'], '') + } + + const startDate = contract.getIn(['tender', 'tenderPeriod', 'startDate']); + + const flagTypes = contract.getIn(['flags', 'flaggedStats'], List()) + .map(flagType => this.t(`crd:corruptionType:${flagType.get('type')}:name`)) + .join(', ') || 'N/A'; + + return { + status: contract.getIn(['tender', 'status'], 'N/A'), + id: contract.get('ocid'), + title: contract.getIn(['tender', 'title'], 'N/A'), + PEName: contract.getIn(['tender', 'procuringEntity', 'name'], 'N/A'), + tenderAmount, + awardAmount, + startDate: startDate ? new Date(startDate).toLocaleDateString() : 'N/A', + flagTypes, + }; + }).toJS(); + + return ( + + + {this.t('crd:contracts:baseInfo:status')} + + + + {this.t('crd:procurementsTable:contractID')} + + + + {this.t('crd:general:contract:title')} + + + + {this.t('crd:contracts:list:procuringEntity')} + + + + {this.t('crd:procurementsTable:tenderAmount')} + + + + {this.t('crd:contracts:list:awardAmount')} + + + + {this.t('crd:procurementsTable:tenderDate')} + + + + {this.t('crd:procurementsTable:flagType')} + + + ) + } } CList.endpoint = 'flaggedRelease/all'; @@ -107,28 +144,14 @@ export default class Contracts extends CRDPage { {this.t('crd:contracts:top-search:resultsFor').replace('$#$', searchQuery)} } -
@@ -147,7 +141,8 @@ export default class Contract extends CRDPage { super(...args); this.state = { contract: Map(), - crosstab: Map() + crosstab: Map(), + indicators: {} } } @@ -160,14 +155,38 @@ export default class Contract extends CRDPage { suppliers.slice(0, 2); } - componentWillReceiveProps(nextProps) { - console.log(nextProps); + groupIndicators({ indicatorTypesMapping }, { contract }) { + if (!indicatorTypesMapping || !Object.keys(indicatorTypesMapping).length || !contract) return; + const newIndicators = {}; + contract.get('flags', List()) .forEach((flag, name) => { + if(flag.get && flag.get('value')) { + indicatorTypesMapping[name].types.forEach(type => { + newIndicators[type] = newIndicators[type] || []; + newIndicators[type].push(name); + }) + } + }); + this.setState({ indicators: newIndicators }); + } + + componentDidMount() { + super.componentDidMount(); + this.groupIndicators(this.props, this.state); + } + + componentWillUpdate(nextProps, nextState) { + const indicatorsChanged = this.props.indicatorTypesMapping != nextProps.indicatorTypesMapping; + const contractChanged = this.state.contract != nextState.contract; + if (indicatorsChanged || contractChanged) { + this.groupIndicators(nextProps, nextState); + } } render() { - const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab } = this.state; - const { id, translations, doSearch, indicatorTypesMapping } = this.props; - console.log(indicatorTypesMapping); + const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, + indicators } = this.state; + + const { id, translations, doSearch, indicatorTypesMapping, filters, years } = this.props; return (
-
- {CORRUPTION_TYPES.map(corruptionType => { - return ( - { - const { crosstab } = this.state; - this.setState({ crosstab: crosstab.set(corruptionType, data)}) - }} - /> - )})} +
+

+ Flag analysis +   + (Click a cell on the charts below to release detailed information) +

+ {Object.keys(indicators).map(corruptionType => ( +
+

+ {this.t(`crd:corruptionType:${corruptionType}:pageTitle`)} +

+ { + const { crosstab } = this.state; + this.setState({ crosstab: crosstab.set(corruptionType, data)}) + }} + /> +
+ ))}
); diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index b5a4ffb80..4cd4e3dec 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -17,8 +17,10 @@ } } - th, td{ - width: 33%; + &.info-table { + th, td{ + width: 33%; + } } } @@ -59,3 +61,17 @@ } } +.contract-page { + .flag-analysis { + clear: both; + + h2 { + padding-top: 50px; + } + + h3 { + margin-top: 70px; + } + } +} + diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 206429e73..dd1e09f2f 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -340,14 +340,22 @@ margin-bottom: auto; padding: 20px 40px 0 30px; - .crd-popup{ - @popupColor: #222c3c; + + .crd-popup { @popupWidth: 400px;//if you're changing this you need to also change POPUP_WIDTH in constants.es6 position: absolute; top: 0; left: 0; width: @popupWidth; height: 150px; + + hr { + width: 50%; + } + } + + .crosstab-box, .crd-popup{ + @popupColor: #222c3c; z-index: 1; background: @popupColor; padding: 15px 5px; @@ -361,11 +369,12 @@ .info{ color: white; } - hr{ - width: 50%; + + hr { opacity: .1; margin: 5px auto; } + .arrow{ background: @popupColor; position: absolute; @@ -378,6 +387,14 @@ } } + .crosstab-box { + padding: 15px; + hr { + width: 100%; + } + } + + .chart-corruption-types{ position: relative; .hovertext{ @@ -458,6 +475,10 @@ } } + td.hoverable:hover, td.selectable.selected { + box-shadow: inset 0px 0px 0px 5px #ffd307; + } + td.hoverable{ position: relative; .crd-popup{ @@ -477,7 +498,6 @@ } &:hover { - box-shadow: inset 0px 0px 0px 5px #ffd307; .crd-popup{ display: block; top: auto; From d25b14d41f3f9efe0901dc153529f6612852f9eb Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 18:13:35 +0200 Subject: [PATCH 235/702] OCE-380 Msg when there's no flags --- .../contracts/single/index.jsx | 69 ++++++++++++------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index fca9281cc..e6fda3f99 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -182,6 +182,49 @@ export default class Contract extends CRDPage { } } + maybeGetFlagAnalysis() { + const { filters, translations, years } = this.props; + const { indicators, crosstab } = this.state; + const noIndicators = Object.keys(indicators) + .every(corruptionType => + !indicators[corruptionType] || !indicators[corruptionType].length); + + if (noIndicators) return ( +
+

{this.t('crd:contracts:flagAnalysis')}

+

{this.t('crd:contracts:noFlags')}

+
+ ); + + return ( +
+

+ {this.t('crd:contracts:flagAnalysis')} +   + ({this.t('crd:contracts:clickCrosstabHint')}) +

+ {Object.keys(indicators).map(corruptionType => ( +
+

+ {this.t(`crd:corruptionType:${corruptionType}:pageTitle`)} +

+ { + const { crosstab } = this.state; + this.setState({ crosstab: crosstab.set(corruptionType, data)}) + }} + /> +
+ ))} +
+ ); + } + render() { const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, indicators } = this.state; @@ -232,31 +275,7 @@ export default class Contract extends CRDPage { />
-
-

- Flag analysis -   - (Click a cell on the charts below to release detailed information) -

- {Object.keys(indicators).map(corruptionType => ( -
-

- {this.t(`crd:corruptionType:${corruptionType}:pageTitle`)} -

- { - const { crosstab } = this.state; - this.setState({ crosstab: crosstab.set(corruptionType, data)}) - }} - /> -
- ))} -
+ {this.maybeGetFlagAnalysis()} ); } From 4d8cd82a4e8723ecc126bf699840e19bb6a4fb56 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 18:13:50 +0200 Subject: [PATCH 236/702] OCE-380 Translations for flag analysis --- web/public/languages/en_US.json | 5 ++++- web/public/languages/es_ES.json | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 4e6dc31c7..d6fefbac5 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -291,5 +291,8 @@ "crd:contracts:baseInfo:awardDate": "Award Date", "crd:contracts:baseInfo:contractAmount": "Contract Amount", "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates", - "crd:contracts:menu:procuringEntities": "Procuring entities" + "crd:contracts:menu:procuringEntities": "Procuring entities", + "crd:contracts:flagAnalysis": "Flag analysis", + "crd:contracts:noFlags": "This procurement has no flags", + "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 04d0e69aa..ad7dce8ad 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -291,5 +291,9 @@ "crd:contracts:baseInfo:awardDate": "Award Date", "crd:contracts:baseInfo:contractAmount": "Contract Amount", "crd:contracts:baseInfo:contractDates": "Contract Open/Close Dates", - "crd:contracts:menu:procuringEntities": "Procuring entities" + "crd:contracts:menu:procuringEntities": "Procuring entities", + "crd:contracts:flagAnalysis": "Flag analysis", + "crd:contracts:noFlags": "This procurement has no flags", + "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information" + } From 03fe1e78918ba60bc6aeee5d55fd5b0fa33f1f6b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 18:22:16 +0200 Subject: [PATCH 237/702] OCE-380 Added procurements table links --- ui/oce/corruption-risk/index.jsx | 2 ++ .../corruption-risk/individual-indicator.jsx | 5 +++-- ui/oce/corruption-risk/overview-page.jsx | 3 ++- ui/oce/corruption-risk/procurements-table.jsx | 21 +++++++++++++++---- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index d3e37e9ba..ad4b7bd0c 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -144,6 +144,7 @@ class CorruptionRiskDashboard extends React.Component { months={months} width={width} styling={styling} + navigate={navigate} /> ); } else if (page === 'contracts') { @@ -180,6 +181,7 @@ class CorruptionRiskDashboard extends React.Component { indicatorTypesMapping={indicatorTypesMapping} styling={styling} width={width} + navigate={navigate} /> ); } diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx index 640d26631..1569c0605 100644 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ b/ui/oce/corruption-risk/individual-indicator.jsx @@ -140,8 +140,8 @@ class IndividualIndicatorPage extends translatable(CRDPage){ render(){ const {chart, table} = this.state; - const {corruptionType, indicator, translations, filters, years, monthly, months, width - , styling} = this.props; + const { corruptionType, indicator, translations, filters, years, monthly, months, width + , styling, navigate } = this.props; return (

{this.t(`crd:indicators:${indicator}:name`)}

@@ -197,6 +197,7 @@ class IndividualIndicatorPage extends translatable(CRDPage){ years={years} monthly={monthly} months={months} + navigate={navigate} />
diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 26c4e9c4e..454515126 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -152,7 +152,7 @@ class OverviewPage extends CRDPage{ render(){ const {corruptionType, topFlaggedContracts} = this.state; - const {filters, translations, years, monthly, months, indicatorTypesMapping, styling, width} = this.props; + const { filters, translations, years, monthly, months, indicatorTypesMapping, styling, width, navigate } = this.props; return (
@@ -181,6 +181,7 @@ class OverviewPage extends CRDPage{ monthly={monthly} months={months} requestNewData={(_, topFlaggedContracts) => this.setState({topFlaggedContracts})} + navigate={navigate} />
diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 82e6d07a4..86f6e35c2 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -59,7 +59,7 @@ class Popup extends translatable(React.Component){ class ProcurementsTable extends Table{ row(entry, index){ - const {translations} = this.props; + const { translations, navigate } = this.props; const tenderValue = entry.getIn(['tender', 'value']); const awardValue = entry.getIn(['awards', 0, 'value']); const tenderPeriod = entry.get('tenderPeriod'); @@ -77,14 +77,27 @@ class ProcurementsTable extends Table{ const procuringEntityName = entry.getIn(['procuringEntity', 'name']); const title = entry.get('title'); + const id = entry.get('ocid'); return (
{entry.get('tag', []).join(', ')}{entry.get('ocid')} + navigate('contract', id)} + > + {id} + + @@ -107,7 +120,7 @@ class ProcurementsTable extends Table{ } render(){ - const {data} = this.props; + const { data } = this.props; return ( From 2814537b7f1267f1c33f7ad5197f830077b9ad18 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 8 Nov 2017 18:24:46 +0200 Subject: [PATCH 238/702] OCE-380 Flag counter --- ui/oce/corruption-risk/contracts/single/index.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index e6fda3f99..4048ae635 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -244,7 +244,11 @@ export default class Contract extends CRDPage { translations={translations} />
-

6 Flags

+

+ {contract.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()} +   + Flags +

Date: Thu, 9 Nov 2017 06:59:12 +0200 Subject: [PATCH 239/702] OCE-379 Using the new EP for avg nr of bidders donut --- .../contracts/single/donuts/nr-of-bidders.jsx | 28 +++++++++++-------- .../contracts/single/index.jsx | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index 7d5b2c91b..612da6428 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -1,21 +1,18 @@ import { List } from 'immutable'; import CenterTextDonut from './index.jsx'; -const AVG_MOCK = 10; - class NrOfBidders extends CenterTextDonut { getClassnames() { return super.getClassnames().concat('nr-of-bidders'); } getCenterText() { - const { contract } = this.props; - if (!contract) return ''; - const count = contract.getIn(['tender', 'tenderers'], List()).count(); + const { count, data: avg } = this.props; + if (isNaN(avg) || isNaN(count)) return ''; return (
{count} - /{AVG_MOCK} + /{avg.toFixed(2)}
); } @@ -26,13 +23,19 @@ class NrOfBidders extends CenterTextDonut { } NrOfBidders.Donut = class extends CenterTextDonut.Donut { + transform(data) { + return data[0].averageNoTenderers; + } + getData() { - const data = super.getData(); - const { contract } = this.props; - if (!data || !data.count() || !contract) return []; - const count = contract.getIn(['tender', 'tenderers'], List()).count(); + const avg = super.getData(); + const { count } = this.props; + if (isNaN(avg) || isNaN(count)) return []; return [{ - values: [count, AVG_MOCK - count], + values: [ + count, + avg - count + ], textinfo: 'value', textposition: 'none', hole: 0.8, @@ -51,6 +54,7 @@ NrOfBidders.Donut = class extends CenterTextDonut.Donut { } } -NrOfBidders.Donut.endpoint = 'totalFlags'; +NrOfBidders.Donut.endpoint = 'averageNumberOfTenderers'; +NrOfBidders.Donut.UPDATABLE_FIELDS = CenterTextDonut.Donut.UPDATABLE_FIELDS.concat('count'); export default NrOfBidders; diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 4048ae635..f26cbdcf5 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -251,6 +251,7 @@ export default class Contract extends CRDPage {
Date: Thu, 9 Nov 2017 07:15:56 +0200 Subject: [PATCH 240/702] OCE-379 Added the flag icon --- ui/assets/icons/flag.svg | 12 ++++++++++++ ui/oce/corruption-risk/contracts/single/index.jsx | 5 +++-- ui/oce/corruption-risk/contracts/style.less | 5 +++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 ui/assets/icons/flag.svg diff --git a/ui/assets/icons/flag.svg b/ui/assets/icons/flag.svg new file mode 100644 index 000000000..539fa292f --- /dev/null +++ b/ui/assets/icons/flag.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index f26cbdcf5..07377109b 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -245,9 +245,10 @@ export default class Contract extends CRDPage { />

- {contract.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()} + Flag icon   - Flags + {contract.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()} +  Flags

Date: Thu, 9 Nov 2017 07:23:22 +0200 Subject: [PATCH 241/702] OCE-379 Fixed top search z-index --- ui/oce/corruption-risk/contracts/style.less | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 3f5d0368b..d534f110b 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -46,6 +46,7 @@ .contract-page, .contracts-page { .top-search { + z-index: 0; margin: 20px; input.form-control { border-radius: 4px; From b5354f7802957fed7ffb06cb19b6455c0ac3b186 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 10 Nov 2017 13:49:08 +0200 Subject: [PATCH 242/702] OCE-379 Create flag information section endpoint for count releases --- .../web/rest/controller/OcdsController.java | 45 ++++++++++++++----- .../resources/allowedApiEndpoints.properties | 3 +- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index cd99b4500..f993b3a45 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -40,9 +40,7 @@ import static org.springframework.data.mongodb.core.query.Query.query; /** - * * @author mpostelnicu - * */ @RestController public class OcdsController extends GenericOCDSController { @@ -58,7 +56,7 @@ public class OcdsController extends GenericOCDSController { @ApiOperation(value = "Returns a release entity for the given project id. " + "The project id is read from planning.budget.projectID") @RequestMapping(value = "/api/ocds/release/budgetProjectId/{projectId:^[a-zA-Z0-9]*$}", - method = { RequestMethod.POST, RequestMethod.GET }, + method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public Release ocdsByProjectId(@PathVariable final String projectId) { @@ -69,7 +67,7 @@ public Release ocdsByProjectId(@PathVariable final String projectId) { @ApiOperation(value = "Returns a release entity for the given open contracting id (OCID).") @RequestMapping(value = "/api/ocds/release/ocid/{ocid}", - method = { RequestMethod.POST, RequestMethod.GET }, + method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public Release ocdsByOcid(@PathVariable final String ocid) { @@ -80,7 +78,7 @@ public Release ocdsByOcid(@PathVariable final String ocid) { @ApiOperation(value = "Returns a release package for the given open contracting id (OCID)." + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") - @RequestMapping(value = "/api/ocds/package/ocid/{ocid}", method = { RequestMethod.POST, RequestMethod.GET }, + @RequestMapping(value = "/api/ocds/package/ocid/{ocid}", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public ReleasePackage ocdsPackageByOcid(@PathVariable final String ocid) { @@ -110,7 +108,7 @@ public ReleasePackage createReleasePackage(final Release release) { + "The project id is read from planning.budget.projectID." + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") @RequestMapping(value = "/api/ocds/package/budgetProjectId/{projectId:^[a-zA-Z0-9]*$}", - method = { RequestMethod.POST, RequestMethod.GET }, + method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public ReleasePackage packagedReleaseByProjectId(@PathVariable final String projectId) { @@ -146,6 +144,33 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR } + /** + * Returns a list of OCDS Releases, order by Id, using pagination + * + * @return the release data + */ + @ApiOperation(value = "Counts releases, filter by given criteria") + @RequestMapping(value = "/api/ocds/release/count", method = {RequestMethod.POST, RequestMethod.GET}, + produces = "application/json") + @JsonView(Views.Public.class) + public Long ocdsReleasesCount(@ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { + + Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), + Direction.ASC, "id"); + + Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); + + if (StringUtils.isNotEmpty(releaseRequest.getText())) { + query.addCriteria(getTextCriteria(releaseRequest)); + } + + Long count = mongoTemplate.count(query, Release.class); + + return count; + + } + + /** * Returns a list of OCDS FlaggedReleases, order by Id, using pagination * @@ -155,8 +180,8 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR @RequestMapping(value = "/api/flaggedRelease/all", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Internal.class) - public List flaggedOcdsReleases(@ModelAttribute @Valid - final YearFilterPagingRequest releaseRequest) { + public List flaggedOcdsReleases( + @ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); @@ -174,7 +199,7 @@ public List flaggedOcdsReleases(@ModelAttribute @Valid @ApiOperation(value = "Returns a release entity for the given open contracting id (OCID).") @RequestMapping(value = "/api/flaggedRelease/ocid/{ocid}", - method = { RequestMethod.POST, RequestMethod.GET }, + method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Internal.class) public FlaggedRelease flaggedReleaseByOcid(@PathVariable final String ocid) { @@ -185,7 +210,7 @@ public FlaggedRelease flaggedReleaseByOcid(@PathVariable final String ocid) { @ApiOperation(value = "Returns all available packages, filtered by the given criteria." + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") - @RequestMapping(value = "/api/ocds/package/all", method = { RequestMethod.POST, RequestMethod.GET }, + @RequestMapping(value = "/api/ocds/package/all", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") @JsonView(Views.Public.class) public List ocdsPackages(@ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties index 846827894..2858a2eb7 100644 --- a/web/src/main/resources/allowedApiEndpoints.properties +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -68,4 +68,5 @@ allowedApiEndpoints=/api/tenderPriceByProcurementMethod**,\ /api/planningByLocation**,\ /api/awardsByLocation**,\ /api/activeAwardsCount**,\ -/api/translations/** \ No newline at end of file +/api/translations/**,\ +/api/ocds/release/count** \ No newline at end of file From 3454d39b3b37165f3071c4ecbd6a310c2a3571e5 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 17:03:20 +0200 Subject: [PATCH 243/702] OCE-379 Changed nr of bidders ratio --- .../contracts/single/donuts/nr-of-bidders.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index 612da6428..792452423 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -31,11 +31,9 @@ NrOfBidders.Donut = class extends CenterTextDonut.Donut { const avg = super.getData(); const { count } = this.props; if (isNaN(avg) || isNaN(count)) return []; + return [{ - values: [ - count, - avg - count - ], + values: [count * avg, avg], textinfo: 'value', textposition: 'none', hole: 0.8, From d7b2a1eafebdb78e94b7115a01696afec5282dda Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 17:03:56 +0200 Subject: [PATCH 244/702] OCE-379 Wired the release/count ep --- .../single/donuts/nr-contract-with-pe.jsx | 48 ++++++++++++------- .../contracts/single/index.jsx | 2 +- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx index 170d272b1..de0a98799 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -6,18 +6,13 @@ class NrOfContractsWithPE extends CenterTextDonut { } getCenterText() { - const { contract, data } = this.props; - if (!contract || !data) return ''; - const peID = contract.getIn(['tender', 'procuringEntity', 'id']); - const withThisPE = data.filter(c => - c.getIn(['tender', 'procuringEntity', 'id']) === peID) - .count(); - const total = data.count(); + const { data } = this.props; + if (!data) return null; return (
- {withThisPE} + {data.get('thisPE')}
- of {total} + of {data.get('total')}
); @@ -29,17 +24,36 @@ class NrOfContractsWithPE extends CenterTextDonut { } NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { + getCustomEP() { + const eps = ['ocds/release/count']; + const { procuringEntityId } = this.props; + if (procuringEntityId) { + eps.push(`ocds/release/count/?procuringEntityId=${procuringEntityId}`) + } + return eps; + } + + transform([total, thisPE]){ + return { + thisPE, + total + } + } + + componentDidUpdate(prevProps, ...rest) { + if (this.props.procuringEntityId != prevProps.procuringEntityId) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, ...rest); + } + } + getData() { const { contract } = this.props; const data = super.getData(); - if (!data || !data.count() || !contract) return []; - const peID = contract.getIn(['tender', 'procuringEntity', 'id']); - const withThisPE = data.filter(c => - c.getIn(['tender', 'procuringEntity', 'id']) === peID) - .count(); - const total = data.count(); + if (!data) return []; return [{ - values: [withThisPE, total - withThisPE], + values: [data.get('thisPE'), data.get('total')], textinfo: 'value', textposition: 'none', hole: 0.8, @@ -58,6 +72,4 @@ NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { } }; -NrOfContractsWithPE.Donut.endpoint = 'ocds/release/all'; - export default NrOfContractsWithPE; diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 07377109b..96f6d6350 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -263,7 +263,7 @@ export default class Contract extends CRDPage {
Date: Fri, 10 Nov 2017 19:15:04 +0200 Subject: [PATCH 245/702] OCE-379 Wrapping center donuts content. Updated margins --- .../contracts/single/donuts/index.jsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx index ee5ab1a5b..bb1894a18 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx @@ -16,17 +16,19 @@ class CenterTextDonut extends React.PureComponent { const { Donut } = this.constructor; return (
- +
+ +
+ {this.getCenterText()} +
+

{this.getTitle()}

-
- {this.getCenterText()} -
); } From acb8121f665bc02b68f9f8165bb8fd8ab3c0f3fa Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 19:15:25 +0200 Subject: [PATCH 246/702] OCE-379 Try-catch for avgnr of tenderers --- .../contracts/single/donuts/nr-of-bidders.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index 792452423..778b8dd71 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -24,7 +24,11 @@ class NrOfBidders extends CenterTextDonut { NrOfBidders.Donut = class extends CenterTextDonut.Donut { transform(data) { - return data[0].averageNoTenderers; + try { + return data[0].averageNoTenderers; + } catch(_) { + return 0; + } } getData() { From 03a2cb30facf3d41d28951214da3f68ea3811815 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 19:15:54 +0200 Subject: [PATCH 247/702] OCE-379 Percent PE spending chart --- .../single/donuts/percent-pe-spending.jsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx index fea1c257f..9fab6c832 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx @@ -2,7 +2,15 @@ import CenterTextDonut from './index.jsx'; class PercentPESpending extends CenterTextDonut { getCenterText() { - return '12%'; + const { data } = this.props; + if (!data) return null; + return ( + +   + {data.get('percentage').toFixed(2)} + % + + ); } getTitle() { @@ -11,11 +19,25 @@ class PercentPESpending extends CenterTextDonut { } PercentPESpending.Donut = class extends CenterTextDonut.Donut { + getCustomEP() { + const { procuringEntityId, supplierId } = this.props; + return `percentageAmountAwarded?procuringEntityId=${procuringEntityId}&supplierId=${supplierId}`; + } + + transform(data){ + const { percentage, totalAwarded, totalAwardedToSuppliers } = data[0]; + return { + percentage, + total: totalAwarded.sum, + toSuppliers: totalAwardedToSuppliers.sum + } + } + getData() { const data = super.getData(); if (!data || !data.count()) return []; return [{ - values: [5, 10], + values: [data.get('toSuppliers'), data.get('total')], labels: ['this', 'total'], textinfo: 'value', textposition: 'none', @@ -35,6 +57,4 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { } } -PercentPESpending.Donut.endpoint = 'totalFlags'; - export default PercentPESpending; From 9d4f01c261ab2a61662adcdc7846f03ff9f22b38 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 19:16:06 +0200 Subject: [PATCH 248/702] OCE-379 Styles update. Relative donut center text font size --- .../contracts/single/donuts/style.less | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/style.less b/ui/oce/corruption-risk/contracts/single/donuts/style.less index 38500e5be..656a33261 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/style.less +++ b/ui/oce/corruption-risk/contracts/single/donuts/style.less @@ -1,14 +1,20 @@ .center-text-donut { text-align: center; + &>div{ + position: relative; + } .center-text { - position: absolute; - top: 35%; - left: 50%; - font-size: 3em; + font-size: 3vw; font-weight: bold; - width: 200px; - margin-left: -100px; color: #144361; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; } &.nr-of-bidders{ From c5bed7ff02634b4164580724874420b479d13614 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 10 Nov 2017 19:16:36 +0200 Subject: [PATCH 249/702] OCE-379 Added status keyinfo. Filters for donuts. Resizing for donuts. --- .../contracts/single/index.jsx | 96 ++++++++++--------- ui/oce/corruption-risk/index.jsx | 1 + 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 96f6d6350..e9ece96d8 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -11,27 +11,13 @@ import Crosstab from '../../clickable-crosstab'; import { CORRUPTION_TYPES } from '../../constants'; class Info extends translatable(Visualization) { - constructor(...args){ - super(...args); - this.state.showAllSuppliers = false; - } - getCustomEP(){ const { id } = this.props; return `flaggedRelease/ocid/${id}`; } - getSuppliers(){ - const { data } = this.props; - const { showAllSuppliers } = this.state; - const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); - return showAllSuppliers ? - suppliers : - suppliers.slice(0, 2); - } - render() { - const { data } = this.props; + const { data, supplier } = this.props; const title = data.getIn(['tender', 'title']); const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); @@ -42,9 +28,17 @@ class Info extends translatable(Visualization) { return (
+
+
+
{this.t('crd:procurementsTable:contractID')}
+
{data.get('ocid')}
+
+
+
Status
+
{data.get('tag', []).join(', ')}
+
+
-
{this.t('crd:procurementsTable:contractID')}
-
{data.get('ocid')}
{title &&
{this.t('crd:general:contract:title')}
} {title &&
{title}
}
@@ -67,8 +61,8 @@ class Info extends translatable(Visualization) {
{this.t('crd:contracts:baseInfo:suppliers')}
- {suppliers.count() ? - this.getSuppliers().map(supplier =>

{supplier.get('name')}

) : + {supplier ? + supplier.get('name') : this.t('general:undefined') }
@@ -146,15 +140,6 @@ export default class Contract extends CRDPage { } } - getSuppliers(){ - const { data } = this.props; - const { showAllSuppliers } = this.state; - const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); - return showAllSuppliers ? - suppliers : - suppliers.slice(0, 2); - } - groupIndicators({ indicatorTypesMapping }, { contract }) { if (!indicatorTypesMapping || !Object.keys(indicatorTypesMapping).length || !contract) return; const newIndicators = {}; @@ -229,7 +214,20 @@ export default class Contract extends CRDPage { const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, indicators } = this.state; - const { id, translations, doSearch, indicatorTypesMapping, filters, years } = this.props; + const { id, translations, doSearch, indicatorTypesMapping, filters, years, + width } = this.props; + + if (!contract) return null; + + const supplier = contract.get('awards', List()) + .find(award => award.get('status') === 'active', undefined, Map()) + .getIn(['suppliers', 0]); + + const procuringEntityId = contract.getIn(['tender', 'procuringEntity', 'id']) || + contract.getIn(['tender', 'procuringEntity', 'identifier', 'id']); + + const donutSize = width / 3 - 30; + return (
this.setState({contract})} translations={translations} /> @@ -250,35 +249,42 @@ export default class Contract extends CRDPage { {contract.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()}  Flags -
+
this.setState({ nrOfBidders })} translations={translations} + width={donutSize} />
-
+
this.setState({ nrContracts })} translations={translations} + width={donutSize} />
-
- this.setState({ percentPESpending })} - translations={translations} - /> +
+ {procuringEntityId && supplier && + this.setState({ percentPESpending })} + translations={translations} + width={donutSize} + /> + }
{this.maybeGetFlagAnalysis()} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index ad4b7bd0c..32f2ff5ce 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -168,6 +168,7 @@ class CorruptionRiskDashboard extends React.Component { indicatorTypesMapping={indicatorTypesMapping} filters={filters} years={years} + width={width} /> ) } else { From 4d0b0061c002a4038b0a3f28abb60e35e9e53325 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 15:25:59 +0200 Subject: [PATCH 250/702] OCE-376 Renamed Couter to FlagsCounter that now subclasses Counter --- ui/oce/corruption-risk/total-flags.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/total-flags.jsx b/ui/oce/corruption-risk/total-flags.jsx index 6efb7e0f7..3580cc84c 100644 --- a/ui/oce/corruption-risk/total-flags.jsx +++ b/ui/oce/corruption-risk/total-flags.jsx @@ -46,7 +46,9 @@ class TotalFlagsChart extends backendYearFilterable(Chart){ TotalFlagsChart.endpoint = 'totalFlaggedIndicatorsByIndicatorType'; -class Counter extends backendYearFilterable(Visualization){ +class Counter extends backendYearFilterable(Visualization) {}; + +class FlagsCounter extends Counter { render(){ const {data} = this.props; if(!data) return null; @@ -61,7 +63,7 @@ class Counter extends backendYearFilterable(Visualization){ } } -Counter.endpoint = 'totalFlags'; +FlagsCounter.endpoint = 'totalFlags'; class TotalFlags extends translatable(React.Component){ constructor(...args){ @@ -91,7 +93,7 @@ class TotalFlags extends translatable(React.Component){ if(!width) return null; return (
- requestNewData(['counter'], data)} translations={translations} From 32a18b50d23dbdc46f771782a6574f16e6e820a8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 15:47:52 +0200 Subject: [PATCH 251/702] OCE-376 Added contracts counter, styles and translations --- ui/oce/corruption-risk/style.less | 11 +++--- ui/oce/corruption-risk/total-flags.jsx | 52 +++++++++++++++++++++----- web/public/languages/en_US.json | 3 +- web/public/languages/es_ES.json | 4 +- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index dd1e09f2f..c7d284886 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -190,22 +190,21 @@ float: right; } .total-flags{ - background: #eff9ff; + background: #fafafa; margin-left: -30px; position: absolute; width: 100%; height: 100%; @infoWidth: 250px; - .total-flags-counter{ - width: @infoWidth; - margin: 20px auto 0 auto; + .counter{ + margin: 20px; .text{ float: left; - font-size: 1.5em; + font-size: 1.3em; padding-top: (2em - 1.5em) / 2; } .count{ - font-size: 2em; + font-size: 1.5em; } } .crd-legend{ diff --git a/ui/oce/corruption-risk/total-flags.jsx b/ui/oce/corruption-risk/total-flags.jsx index 3580cc84c..0edcaf2cb 100644 --- a/ui/oce/corruption-risk/total-flags.jsx +++ b/ui/oce/corruption-risk/total-flags.jsx @@ -46,25 +46,48 @@ class TotalFlagsChart extends backendYearFilterable(Chart){ TotalFlagsChart.endpoint = 'totalFlaggedIndicatorsByIndicatorType'; -class Counter extends backendYearFilterable(Visualization) {}; - -class FlagsCounter extends Counter { - render(){ +class Counter extends backendYearFilterable(Visualization) { + render() { const {data} = this.props; if(!data) return null; return ( -
-
{this.t('crd:charts:totalFlags:title')}
+
+
+ {this.getTitle()} +
- {data.getIn([0, 'flaggedCount'], 0)} + {this.getCount()}
- ) + ); + } +}; + +class FlagsCounter extends Counter { + getTitle() { + return this.t('crd:charts:totalFlags:title'); + } + + getCount() { + const { data } = this.props; + return data.getIn([0, 'flaggedCount'], 0); } } FlagsCounter.endpoint = 'totalFlags'; +class ContractCounter extends Counter { + getTitle() { + return this.t('crd:sidebar:totalContracts:title'); + } + + getCount() { + return this.props.data; + } +} + +ContractCounter.endpoint = 'ocds/release/count'; + class TotalFlags extends translatable(React.Component){ constructor(...args){ super(...args); @@ -93,9 +116,18 @@ class TotalFlags extends translatable(React.Component){ if(!width) return null; return (
+ requestNewData(['contractCounter'], data)} + translations={translations} + filters={filters} + years={years} + months={months} + monthly={monthly} + /> requestNewData(['counter'], data)} + data={data.get('flagsCounter')} + requestNewData={(_, data) => requestNewData(['flagsCounter'], data)} translations={translations} filters={filters} years={years} diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index d6fefbac5..80fff008a 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -294,5 +294,6 @@ "crd:contracts:menu:procuringEntities": "Procuring entities", "crd:contracts:flagAnalysis": "Flag analysis", "crd:contracts:noFlags": "This procurement has no flags", - "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information" + "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", + "crd:sidebar:totalContracts:title": "Total Procurements" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index ad7dce8ad..56d0d0281 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -294,6 +294,6 @@ "crd:contracts:menu:procuringEntities": "Procuring entities", "crd:contracts:flagAnalysis": "Flag analysis", "crd:contracts:noFlags": "This procurement has no flags", - "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information" - + "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", + "crd:sidebar:totalContracts:title": "Total Procurements" } From c1c204b81edfcac172f672b9d625f7b63a29c0ca Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 13 Nov 2017 17:17:59 +0200 Subject: [PATCH 252/702] OCE-400 Create filter for award status --- .../web/rest/controller/GenericOCDSController.java | 11 +++++++++-- .../request/DefaultFilterPagingRequest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index 061faa34a..3791de525 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -165,6 +165,11 @@ protected Criteria getFlagTypeFilterCriteria(final DefaultFilterPagingRequest fi return createFilterCriteria("flags.flaggedStats.type", filter.getFlagType(), filter); } + protected Criteria getAwardStatusFilterCriteria(final DefaultFilterPagingRequest filter) { + return createFilterCriteria("awards.status", filter.getAwardStatus(), filter); + } + + /** * Adds monthly projection operation, when needed, if the * {@link YearFilterPagingRequest#getMonthly()} @@ -478,7 +483,8 @@ protected Criteria getDefaultFilterCriteria(final DefaultFilterPagingRequest fil getByAwardAmountIntervalCriteria(filter), getFlaggedCriteria(filter), getFlagTypeFilterCriteria(filter), - getElectronicSubmissionCriteria(filter)); + getElectronicSubmissionCriteria(filter), + getAwardStatusFilterCriteria(filter)); } protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest filter, final String dateProperty) { @@ -495,7 +501,8 @@ protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest fi getElectronicSubmissionCriteria(filter), getFlaggedCriteria(filter), getFlagTypeFilterCriteria(filter), - getYearFilterCriteria(filter, dateProperty)); + getYearFilterCriteria(filter, dateProperty), + getAwardStatusFilterCriteria(filter)); } protected MatchOperation getMatchDefaultFilterOperation(final DefaultFilterPagingRequest filter) { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java index a8b0cb2f0..2e7b82608 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java @@ -17,6 +17,10 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { @ApiModelProperty(value = "Full text search of of release entities") private String text; + @ApiModelProperty(value = "Filter by award.status, possible values are available from the OCDS standard page" + + "http://standard.open-contracting.org/latest/en/schema/codelists/#award-status") + private TreeSet awardStatus; + @EachPattern(regexp = "^[a-zA-Z0-9]*$") @ApiModelProperty(value = "This corresponds to the tender.items.classification._id") private TreeSet bidTypeId; @@ -199,4 +203,13 @@ public Boolean getFlagged() { public void setFlagged(Boolean flagged) { this.flagged = flagged; } + + + public TreeSet getAwardStatus() { + return awardStatus; + } + + public void setAwardStatus(TreeSet awardStatus) { + this.awardStatus = awardStatus; + } } From e062ead921df90fdd5a6d19d09a04fa0a6b1c283 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 21:16:50 +0200 Subject: [PATCH 253/702] OCE-376 Total flags styling --- ui/oce/corruption-risk/style.less | 27 +++++++++++++++++++------- ui/oce/corruption-risk/total-flags.jsx | 6 +++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index c7d284886..d5a1a6c19 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -174,8 +174,8 @@ aside{ @leftPadding: 30px; bottom:0; - background: #fafafa; - border-right: 1px solid #e6eaee; + background: transparent; + border-right: none; height: 100%; padding-left: @leftPadding; padding-top: 10px; @@ -194,17 +194,27 @@ margin-left: -30px; position: absolute; width: 100%; - height: 100%; + margin-top: 15px; + padding-bottom: 15px; @infoWidth: 250px; + border-radius: 5px; + border: 1px solid #e7ebef; + border-left-style: none; .counter{ - margin: 20px; + padding: 10px 20px 15px 20px; + &:first-child { + border-bottom: 1px solid #e7ebef; + } .text{ - float: left; font-size: 1.3em; - padding-top: (2em - 1.5em) / 2; + padding-top: .5em; } .count{ - font-size: 1.5em; + float: right; + font-size: 2em; + font-weight: bold; + color: #2b9ff6; + border-bottom: 1px dotted #2b9ff6; } } .crd-legend{ @@ -246,9 +256,12 @@ [role=navigation]{ margin-left: -@leftPadding; margin-top: -1px; + border-right: 1px solid #e7ebef; + border-bottom-right-radius: 5px; a{ border-bottom: 1px solid #e6eaee; color: #000000; + background: #fafafa; cursor: pointer; display: block; font-size: 0.9em; diff --git a/ui/oce/corruption-risk/total-flags.jsx b/ui/oce/corruption-risk/total-flags.jsx index 0edcaf2cb..371953566 100644 --- a/ui/oce/corruption-risk/total-flags.jsx +++ b/ui/oce/corruption-risk/total-flags.jsx @@ -52,12 +52,12 @@ class Counter extends backendYearFilterable(Visualization) { if(!data) return null; return (
-
- {this.getTitle()} -
{this.getCount()}
+
+ {this.getTitle()} +
); } From 08fd5b9836e43de5efe872f28e2445c517900e02 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 22:33:37 +0200 Subject: [PATCH 254/702] OCE-379 Smaller donuts & center text alignment --- .../corruption-risk/contracts/single/donuts/index.jsx | 4 ---- .../corruption-risk/contracts/single/donuts/style.less | 10 +++++++--- ui/oce/corruption-risk/contracts/single/index.jsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx index bb1894a18..6d3cddce1 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/index.jsx @@ -8,10 +8,6 @@ class CenterTextDonut extends React.PureComponent { return ['center-text-donut']; } - getCenterText() { - return 'Sample text'; - } - render() { const { Donut } = this.constructor; return ( diff --git a/ui/oce/corruption-risk/contracts/single/donuts/style.less b/ui/oce/corruption-risk/contracts/single/donuts/style.less index 656a33261..71917d8c6 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/style.less +++ b/ui/oce/corruption-risk/contracts/single/donuts/style.less @@ -3,6 +3,13 @@ &>div{ position: relative; } + + &>div, .center-text { + display: flex; + align-items: center; + justify-content: center; + } + .center-text { font-size: 3vw; font-weight: bold; @@ -12,9 +19,6 @@ left: 0; right: 0; bottom: 0; - display: flex; - align-items: center; - justify-content: center; } &.nr-of-bidders{ diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index e9ece96d8..ccd5d6221 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -226,7 +226,7 @@ export default class Contract extends CRDPage { const procuringEntityId = contract.getIn(['tender', 'procuringEntity', 'id']) || contract.getIn(['tender', 'procuringEntity', 'identifier', 'id']); - const donutSize = width / 3 - 30; + const donutSize = width / 3 - 100; return (
From 5e7d1b3a779a06de9157132075764bff3f8e3d71 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 22:35:23 +0200 Subject: [PATCH 255/702] OCE-379 Rendering contract title element conditionally to prevent empty space --- ui/oce/corruption-risk/contracts/single/index.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index ccd5d6221..fd339c20c 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -38,10 +38,12 @@ class Info extends translatable(Visualization) {
{data.get('tag', []).join(', ')}
-
- {title &&
{this.t('crd:general:contract:title')}
} - {title &&
{title}
} -
+ {title && +
+ {title &&
{this.t('crd:general:contract:title')}
} + {title &&
{title}
} +
+ }
From cdc4bfa1932fa36715c9686677430f4181fa425e Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 23:08:31 +0200 Subject: [PATCH 256/702] OCE-379 Fixed nr contracts with pe chart to show real data --- .../single/donuts/nr-contract-with-pe.jsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx index de0a98799..601d4a0dd 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -25,12 +25,13 @@ class NrOfContractsWithPE extends CenterTextDonut { NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { getCustomEP() { - const eps = ['ocds/release/count']; - const { procuringEntityId } = this.props; - if (procuringEntityId) { - eps.push(`ocds/release/count/?procuringEntityId=${procuringEntityId}`) - } - return eps; + const { procuringEntityId, supplierId } = this.props; + if (!procuringEntityId || !supplierId) return []; + return [ + `ocds/release/count/?procuringEntityId=${procuringEntityId}`, + `ocds/release/count/?procuringEntityId=${procuringEntityId}` + + `&supplierId=${supplierId}&awardStatus=active` + ]; } transform([total, thisPE]){ @@ -41,7 +42,9 @@ NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { } componentDidUpdate(prevProps, ...rest) { - if (this.props.procuringEntityId != prevProps.procuringEntityId) { + const peChanged = this.props.procuringEntityId != prevProps.procuringEntityId; + const supplierChanged = this.props.supplierId != prevProps.supplierId; + if (peChanged || supplierChanged) { this.fetch(); } else { super.componentDidUpdate(prevProps, ...rest); From 94194471ca3702f706ae3cb394ff7c5d7a893be1 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 23:09:05 +0200 Subject: [PATCH 257/702] OCE-379 Updating and monthly filtering --- .../single/donuts/percent-pe-spending.jsx | 10 +++++++ .../contracts/single/index.jsx | 29 ++++++++++++------- ui/oce/corruption-risk/index.jsx | 2 ++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx index 9fab6c832..ad7a1dc49 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx @@ -33,6 +33,16 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { } } + componentDidUpdate(prevProps, ...rest) { + const peChanged = this.props.procuringEntityId != prevProps.procuringEntityId; + const supplierChanged = this.props.supplierId != prevProps.supplierId; + if (peChanged || supplierChanged) { + this.fetch(); + } else { + super.componentDidUpdate(prevProps, ...rest); + } + } + getData() { const data = super.getData(); if (!data || !data.count()) return []; diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index fd339c20c..07224c9d1 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -214,7 +214,7 @@ export default class Contract extends CRDPage { render() { const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, - indicators } = this.state; + indicators, months, monthly } = this.state; const { id, translations, doSearch, indicatorTypesMapping, filters, years, width } = this.props; @@ -258,21 +258,28 @@ export default class Contract extends CRDPage { data={nrOfBidders} filters={filters} years={years} + monthly={monthly} + months={months} requestNewData={(_, nrOfBidders) => this.setState({ nrOfBidders })} translations={translations} width={donutSize} />
- this.setState({ nrContracts })} - translations={translations} - width={donutSize} - /> + {procuringEntityId && supplier && + this.setState({ nrContracts })} + translations={translations} + width={donutSize} + /> + }
{procuringEntityId && supplier && @@ -282,6 +289,8 @@ export default class Contract extends CRDPage { procuringEntityId={procuringEntityId} supplierId={supplier.get('id')} years={years} + monthly={monthly} + months={months} requestNewData={(_, percentPESpending) => this.setState({ percentPESpending })} translations={translations} width={donutSize} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 32f2ff5ce..8bac917c1 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -168,6 +168,8 @@ class CorruptionRiskDashboard extends React.Component { indicatorTypesMapping={indicatorTypesMapping} filters={filters} years={years} + monthly={monthly} + months={months} width={width} /> ) From bfe44107c4eda5ea3ff19935d436ac9bf380e44d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 13 Nov 2017 23:11:31 +0200 Subject: [PATCH 258/702] OCE-379 Preventing center text from covering the charts --- ui/oce/corruption-risk/contracts/single/donuts/style.less | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/style.less b/ui/oce/corruption-risk/contracts/single/donuts/style.less index 71917d8c6..26273cb8f 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/style.less +++ b/ui/oce/corruption-risk/contracts/single/donuts/style.less @@ -19,6 +19,7 @@ left: 0; right: 0; bottom: 0; + z-index: -1; } &.nr-of-bidders{ From 76cd5c4cbdc377f96eaf2ee2f77e806f01b916c9 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Tue, 14 Nov 2017 12:48:06 +0200 Subject: [PATCH 259/702] OCE-403 New endpoints don't react to filters --- .../controller/GenericOCDSController.java | 13 ++++++---- .../web/rest/controller/OcdsController.java | 10 +++++--- .../PercentageAmountAwardedController.java | 24 ++++++++++++++++++- .../resources/allowedApiEndpoints.properties | 3 ++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index 3791de525..5b47d8bf8 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -83,7 +83,7 @@ protected Date getStartDate(final int year) { * @return */ protected DBObject getPercentageMongoOp(String expression1, String expression2) { - return new BasicDBObject("$cond", + return new BasicDBObject("$cond", Arrays.asList(new BasicDBObject("$eq", Arrays.asList(ref(expression2), 0)), new BasicDBObject("$literal", 0), new BasicDBObject("$multiply", @@ -159,7 +159,6 @@ protected Criteria getBidTypeIdFilterCriteria(final DefaultFilterPagingRequest f /** * Appends flags.flaggedStats.type filter - * */ protected Criteria getFlagTypeFilterCriteria(final DefaultFilterPagingRequest filter) { return createFilterCriteria("flags.flaggedStats.type", filter.getFlagType(), filter); @@ -296,7 +295,7 @@ protected Criteria getByTenderDeliveryLocationIdentifier(final DefaultFilterPagi * @param filter * @return */ - private Criteria getByTenderAmountIntervalCriteria(final DefaultFilterPagingRequest filter) { + protected Criteria getByTenderAmountIntervalCriteria(final DefaultFilterPagingRequest filter) { if (filter.getMaxTenderValue() == null && filter.getMinTenderValue() == null) { return new Criteria(); } @@ -319,7 +318,7 @@ private Criteria getByTenderAmountIntervalCriteria(final DefaultFilterPagingRequ * @param filter * @return */ - private Criteria getByAwardAmountIntervalCriteria(final DefaultFilterPagingRequest filter) { + protected Criteria getByAwardAmountIntervalCriteria(final DefaultFilterPagingRequest filter) { if (filter.getMaxAwardValue() == null && filter.getMinAwardValue() == null) { return new Criteria(); } @@ -509,6 +508,12 @@ protected MatchOperation getMatchDefaultFilterOperation(final DefaultFilterPagin return match(getDefaultFilterCriteria(filter)); } + + protected MatchOperation getMatchYearDefaultFilterOperation(final YearFilterPagingRequest filter, + final String dateProperty) { + return match(getYearDefaultFilterCriteria(filter, dateProperty)); + } + /** * Creates a groupby expression that takes into account the filter. It will * only use one of the filter options as groupby and ignores the rest. diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index f993b3a45..33f101dd2 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -21,6 +21,7 @@ import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.persistence.mongo.repository.main.FlaggedReleaseRepository; import org.devgateway.ocds.persistence.mongo.repository.main.ReleaseRepository; import org.devgateway.ocds.persistence.mongo.spring.json.Views; @@ -131,7 +132,8 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); - Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); + Query query = query(getYearFilterCriteria(releaseRequest, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE)) + .with(pageRequest); if (StringUtils.isNotEmpty(releaseRequest.getText())) { query.addCriteria(getTextCriteria(releaseRequest)); @@ -158,7 +160,8 @@ public Long ocdsReleasesCount(@ModelAttribute @Valid final YearFilterPagingReque Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); - Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); + Query query = query(getYearDefaultFilterCriteria(releaseRequest, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE)).with(pageRequest); if (StringUtils.isNotEmpty(releaseRequest.getText())) { query.addCriteria(getTextCriteria(releaseRequest)); @@ -186,7 +189,8 @@ public List flaggedOcdsReleases( Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), Direction.ASC, "id"); - Query query = query(getDefaultFilterCriteria(releaseRequest)).with(pageRequest); + Query query = query(getYearDefaultFilterCriteria(releaseRequest, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE)).with(pageRequest); if (StringUtils.isNotEmpty(releaseRequest.getText())) { query.addCriteria(getTextCriteria(releaseRequest)); diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java index 17af30a62..a7bbd4c94 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/PercentageAmountAwardedController.java @@ -16,12 +16,14 @@ import io.swagger.annotations.ApiOperation; import java.util.List; import javax.validation.Valid; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.devgateway.toolkit.persistence.mongo.aggregate.CustomProjectionOperation; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @@ -48,13 +50,33 @@ public static final class Keys { } - @ApiOperation("") + @Override + protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest filter, final String dateProperty) { + return new Criteria().andOperator( + getBidTypeIdFilterCriteria(filter), + getNotBidTypeIdFilterCriteria(filter), + getNotProcuringEntityIdCriteria(filter), + getProcurementMethodCriteria(filter), + getByTenderDeliveryLocationIdentifier(filter), + getByTenderAmountIntervalCriteria(filter), + getByAwardAmountIntervalCriteria(filter), + getElectronicSubmissionCriteria(filter), + getFlaggedCriteria(filter), + getFlagTypeFilterCriteria(filter), + getYearFilterCriteria(filter, dateProperty), + getAwardStatusFilterCriteria(filter)); + } + + @ApiOperation("Calculate percentage of awards awarded to a list of suppliers vs total awards. Filters by all" + + " filters. Careful using supplierId filter here!" + + " It has a different signification than for other endpoints.") @RequestMapping(value = "/api/percentageAmountAwarded", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") public List percentTendersCancelled(@ModelAttribute @Valid final YearFilterPagingRequest filter) { Assert.notEmpty(filter.getProcuringEntityId(), "Must provide at least one procuringEntity!"); Assert.notEmpty(filter.getSupplierId(), "Must provide at least one supplierId!"); Aggregation agg = newAggregation( + getMatchYearDefaultFilterOperation(filter, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE), match(where("tender.procuringEntity").exists(true).and("awards.suppliers.0").exists(true) .andOperator(getProcuringEntityIdCriteria(filter))), unwind("awards"), diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties index 2858a2eb7..f049bc606 100644 --- a/web/src/main/resources/allowedApiEndpoints.properties +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -69,4 +69,5 @@ allowedApiEndpoints=/api/tenderPriceByProcurementMethod**,\ /api/awardsByLocation**,\ /api/activeAwardsCount**,\ /api/translations/**,\ -/api/ocds/release/count** \ No newline at end of file +/api/ocds/release/count**,\ +/api/percentageAmountAwarded** \ No newline at end of file From e135c076a68f0428d905dbe714605d7188bd3f5b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 14 Nov 2017 13:12:47 +0200 Subject: [PATCH 260/702] OCE-379 Donut popups --- .../contracts/single/donuts/nr-contract-with-pe.jsx | 8 ++++++-- .../contracts/single/donuts/nr-of-bidders.jsx | 10 +++++++--- .../contracts/single/donuts/percent-pe-spending.jsx | 9 ++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx index 601d4a0dd..ce06bf14a 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -56,9 +56,13 @@ NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { const data = super.getData(); if (!data) return []; return [{ + labels: ['Won by this supplier', 'Contracts with this PE'], values: [data.get('thisPE'), data.get('total')], - textinfo: 'value', - textposition: 'none', + hoverlabel: { + bgcolor: '#144361' + }, + hoverinfo: 'label', + textinfo: 'none', hole: 0.8, type: 'pie', marker: { diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index 778b8dd71..056ac8d4d 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -37,9 +37,13 @@ NrOfBidders.Donut = class extends CenterTextDonut.Donut { if (isNaN(avg) || isNaN(count)) return []; return [{ - values: [count * avg, avg], - textinfo: 'value', - textposition: 'none', + labels: ['This contract', 'Average'], + values: [count == 1 ? count : count * avg, avg], + hoverlabel: { + bgcolor: '#144361' + }, + hoverinfo: 'label', + textinfo: 'none', hole: 0.8, type: 'pie', marker: { diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx index ad7a1dc49..2813c66a2 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx @@ -47,10 +47,13 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { const data = super.getData(); if (!data || !data.count()) return []; return [{ + labels: ['Awarded to this supplier', 'Awarded by this PE'], values: [data.get('toSuppliers'), data.get('total')], - labels: ['this', 'total'], - textinfo: 'value', - textposition: 'none', + hoverlabel: { + bgcolor: '#144361' + }, + hoverinfo: 'label', + textinfo: 'none', hole: 0.8, type: 'pie', marker: { From 8c3429b3edde65a3383f99a7757a86066395b0f9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 14 Nov 2017 13:27:43 +0200 Subject: [PATCH 261/702] OCE-379 Try-catch for percent PE spending --- .../single/donuts/percent-pe-spending.jsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx index 2813c66a2..08beef6dc 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx @@ -25,11 +25,19 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { } transform(data){ - const { percentage, totalAwarded, totalAwardedToSuppliers } = data[0]; - return { - percentage, - total: totalAwarded.sum, - toSuppliers: totalAwardedToSuppliers.sum + try { + const { percentage, totalAwarded, totalAwardedToSuppliers } = data[0]; + return { + percentage, + total: totalAwarded.sum, + toSuppliers: totalAwardedToSuppliers.sum + } + } catch(e) { + return { + percentage: 0, + total: 0, + toSuppliers: 0 + } } } From 866aee8fead60540037d2c889961f7c27c7c1cdb Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 13:01:46 +0200 Subject: [PATCH 262/702] OCE-379 Moved flag counter to the top --- ui/oce/corruption-risk/contracts/single/index.jsx | 14 ++++++++------ ui/oce/corruption-risk/contracts/style.less | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 07224c9d1..0e03a8d5b 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -27,16 +27,22 @@ class Info extends translatable(Visualization) { award.get('status') != 'unsuccessful') || Map(); return ( -
+
{this.t('crd:procurementsTable:contractID')}
{data.get('ocid')}
-
+
Status
{data.get('tag', []).join(', ')}
+
+ Flag icon +   + {data.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()} +  Flags +
{title &&
@@ -246,10 +252,6 @@ export default class Contract extends CRDPage { />

- Flag icon -   - {contract.get('flags', List()).filter(flag => flag.get && flag.get('value')).count()} -  Flags

Date: Wed, 15 Nov 2017 17:35:13 +0200 Subject: [PATCH 263/702] OCE-379 Fixed donuts not being passed proper months and monthly values --- ui/oce/corruption-risk/contracts/single/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 0e03a8d5b..99dcc6b08 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -220,10 +220,10 @@ export default class Contract extends CRDPage { render() { const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, - indicators, months, monthly } = this.state; + indicators } = this.state; const { id, translations, doSearch, indicatorTypesMapping, filters, years, - width } = this.props; + width, months, monthly } = this.props; if (!contract) return null; From e96f8b406c597b6ac39b46b1e0a3c7f3a9ff439e Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 18:10:36 +0200 Subject: [PATCH 264/702] OCE-379 Changed contracts link bgcolor --- ui/oce/corruption-risk/style.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index d5a1a6c19..e2ac2c39b 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -286,7 +286,7 @@ } &.contracts-link{ - background: #d9e3eb; + background: #c2dcef; } img{ From 888abaf26ce196135e7317f27c8e9c809083b115 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 18:17:55 +0200 Subject: [PATCH 265/702] OCE-379 Only showing CRD description when "Overview" is the current tab --- ui/oce/corruption-risk/style.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index e2ac2c39b..31d7b4623 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -321,7 +321,7 @@ } .crd-description-link { - &:hover { + &.active { &+.crd-description { display: block; } From c088ad89fa76de3b3dae158a72908bc46d93d517 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 18:26:32 +0200 Subject: [PATCH 266/702] OCE-379 Fixed overview infosign color --- ui/oce/corruption-risk/style.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 31d7b4623..d12b5a0e0 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -326,13 +326,16 @@ display: block; } + } + + &:hover, &.active{ .glyphicon { color: white; } } .glyphicon { - color: #ecf7ff; + color: #34ac45; padding: 5px 15px; font-size: 1.5em; } From 54ab033d3a0c423b7cee22010f6027c3b0f3444c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 18:31:06 +0200 Subject: [PATCH 267/702] OCE-379 Changed counters color and removed underline --- ui/oce/corruption-risk/style.less | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index d12b5a0e0..6d21769e2 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -213,8 +213,7 @@ float: right; font-size: 2em; font-weight: bold; - color: #2b9ff6; - border-bottom: 1px dotted #2b9ff6; + color: #144361; } } .crd-legend{ From 41897a6f9c3413f5703626c34dfc1d5aed0ff3ef Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 18:41:05 +0200 Subject: [PATCH 268/702] OCE-376 Styling left hand donut popups --- ui/oce/corruption-risk/total-flags.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/total-flags.jsx b/ui/oce/corruption-risk/total-flags.jsx index 371953566..054b48c0f 100644 --- a/ui/oce/corruption-risk/total-flags.jsx +++ b/ui/oce/corruption-risk/total-flags.jsx @@ -19,8 +19,11 @@ class TotalFlagsChart extends backendYearFilterable(Chart){ values: data.map(pluckImm('indicatorCount')).toJS(), labels: labels, textinfo: 'value', - hole: .85, + hole: .8, type: 'pie', + hoverlabel: { + bgcolor: '#144361' + }, marker: { colors: ['#fac329', '#289df5', '#3372b1']//if you change this colors you'll have to also change it for the custom legend in ./style.less }, From 2b6b6282d460249e38de8bbaedb32dbd74df0c3c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 19:09:03 +0200 Subject: [PATCH 269/702] OCE-376 Extracted sidebar to a separate component --- ui/oce/corruption-risk/index.jsx | 101 +++++------------------------ ui/oce/corruption-risk/sidebar.jsx | 99 ++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 85 deletions(-) create mode 100644 ui/oce/corruption-risk/sidebar.jsx diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 8bac917c1..2aaa5aed4 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -9,12 +9,11 @@ import IndividualIndicatorPage from './individual-indicator'; import ContractsPage from './contracts'; import ContractPage from './contracts/single'; import Filters from './filters'; -import TotalFlags from './total-flags'; import LandingPopup from './landing-popup'; import { LOGIN_URL } from './constants'; // eslint-disable-next-line no-unused-vars import style from './style.less'; -import { CORRUPTION_TYPES } from './constants'; +import Sidebar from './sidebar'; // eslint-disable-next-line no-undef class CorruptionRiskDashboard extends React.Component { @@ -172,7 +171,7 @@ class CorruptionRiskDashboard extends React.Component { months={months} width={width} /> - ) + ); } else { return ( - + + this.setState({ data: this.state.data.setIn(path, newData) })} + filters={filters} + years={years} + monthly={monthly} + months={months} + />
{this.getPage()}
diff --git a/ui/oce/corruption-risk/sidebar.jsx b/ui/oce/corruption-risk/sidebar.jsx new file mode 100644 index 000000000..ecf9eefad --- /dev/null +++ b/ui/oce/corruption-risk/sidebar.jsx @@ -0,0 +1,99 @@ +import cn from 'classnames'; +import { Map } from 'immutable'; +import translatable from '../translatable'; +import TotalFlags from './total-flags'; +import { CORRUPTION_TYPES } from './constants'; + +// eslint-disable-next-line no-undef +class Sidebar extends translatable(React.PureComponent) { + render() { + const { page, indicatorTypesMapping, filters, years, monthly, months, navigate, translations, + data, requestNewData, route } = this.props; + + return ( + + ); + } +} + +export default Sidebar; From eca2fcd84b25eb9fe9cd3427c24899fdd4da977a Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 20:29:16 +0200 Subject: [PATCH 270/702] OCE-376 Removed position:absolute for total flags --- ui/oce/corruption-risk/style.less | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 6d21769e2..01b16fab1 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -191,11 +191,8 @@ } .total-flags{ background: #fafafa; - margin-left: -30px; - position: absolute; - width: 100%; - margin-top: 15px; - padding-bottom: 15px; + margin: 15px -15px 0 -30px; + padding-bottom: 50px; @infoWidth: 250px; border-radius: 5px; border: 1px solid #e7ebef; @@ -354,7 +351,6 @@ margin-bottom: auto; padding: 20px 40px 0 30px; - .crd-popup { @popupWidth: 400px;//if you're changing this you need to also change POPUP_WIDTH in constants.es6 position: absolute; From 38f55f9199ade16e0c6ab9f351e6b3997a7f46b9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 20:30:21 +0200 Subject: [PATCH 271/702] OCE-376 Sidebar scrolling --- ui/oce/corruption-risk/sidebar.jsx | 180 +++++++++++++++++------------ 1 file changed, 105 insertions(+), 75 deletions(-) diff --git a/ui/oce/corruption-risk/sidebar.jsx b/ui/oce/corruption-risk/sidebar.jsx index ecf9eefad..ec5f1f6ba 100644 --- a/ui/oce/corruption-risk/sidebar.jsx +++ b/ui/oce/corruption-risk/sidebar.jsx @@ -1,3 +1,4 @@ +import ReactDOM from 'react-dom'; import cn from 'classnames'; import { Map } from 'immutable'; import translatable from '../translatable'; @@ -6,91 +7,120 @@ import { CORRUPTION_TYPES } from './constants'; // eslint-disable-next-line no-undef class Sidebar extends translatable(React.PureComponent) { + constructor(...args){ + super(...args); + this.prevScrollY = 0; + } + + componentDidMount() { + const el = ReactDOM.findDOMNode(this); + const scrollTarget = el.querySelector('div'); + const offsetTop = el.getBoundingClientRect().top; + + window.addEventListener('scroll', e => { + const diff = window.scrollY - this.prevScrollY; + this.prevScrollY = window.scrollY; + let margin = parseInt(scrollTarget.style.marginTop); + if (isNaN(margin)) margin = 0; + margin -= diff; + const newMargin = Math.min( + 0, + Math.max( + margin, + window.innerHeight - scrollTarget.offsetHeight - offsetTop + ) + ); + scrollTarget.style.marginTop = `${newMargin}px`; + }); + } + render() { const { page, indicatorTypesMapping, filters, years, monthly, months, navigate, translations, data, requestNewData, route } = this.props; return (
); } From 7e2c8b064665312344f57b63a8586b33e77ff1c3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 21:29:44 +0200 Subject: [PATCH 272/702] Filtering winnings awards for procurement table --- ui/oce/corruption-risk/procurements-table.jsx | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 86f6e35c2..95c2bba70 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -59,9 +59,15 @@ class Popup extends translatable(React.Component){ class ProcurementsTable extends Table{ row(entry, index){ - const { translations, navigate } = this.props; + const {translations} = this.props; const tenderValue = entry.getIn(['tender', 'value']); - const awardValue = entry.getIn(['awards', 0, 'value']); + let awardValue; + + const winningAward = entry.get('awards').find(award => award.get('status') == 'active'); + if (winningAward) { + awardValue = winningAward.get('value'); + } + const tenderPeriod = entry.get('tenderPeriod'); const startDate = new Date(tenderPeriod.get('startDate')); const endDate = new Date(tenderPeriod.get('endDate')); @@ -77,27 +83,14 @@ class ProcurementsTable extends Table{ const procuringEntityName = entry.getIn(['procuringEntity', 'name']); const title = entry.get('title'); - const id = entry.get('ocid'); return (
- + - + From 90acb9113f13f06d67f28bc2785039c1583070b2 Mon Sep 17 00:00:00 2001 From: Alexei Date: Wed, 15 Nov 2017 21:39:02 +0200 Subject: [PATCH 273/702] Revert copypaste from OCUA --- ui/oce/corruption-risk/procurements-table.jsx | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 95c2bba70..ef65d75fe 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -1,15 +1,15 @@ -import cn from "classnames"; -import Table from "../visualizations/tables"; -import translatable from "../translatable"; -import ReactDOM from "react-dom"; -import {POPUP_HEIGHT} from './constants'; +import ReactDOM from 'react-dom'; +import Table from '../visualizations/tables'; +import translatable from '../translatable'; +import { POPUP_HEIGHT } from './constants'; +// eslint-disable-next-line no-undef class Popup extends translatable(React.Component){ - constructor(...args){ + constructor(...args) { super(...args); this.state = { - showPopup: false - } + showPopup: false, + }; } getPopup(){ @@ -22,18 +22,18 @@ class Popup extends translatable(React.Component){
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}
-
+
{flagIds.map(flagId =>

{this.t(`crd:indicators:${flagId}:name`)}

)}
-
+
- ) + ); } - showPopup(){ + showPopup() { const el = ReactDOM.findDOMNode(this); this.setState({ showPopup: true, @@ -48,7 +48,7 @@ class Popup extends translatable(React.Component){
- + - ) + ); } - render(){ - const {data} = this.props; + render() { + const { data } = this.props; return (
{entry.get('tag', []).join(', ')} - navigate('contract', id)} - > - {id} - - {entry.get('ocid')} @@ -106,7 +99,12 @@ class ProcurementsTable extends Table{ {tenderValue && tenderValue.get('amount')} {tenderValue && tenderValue.get('currency')}{awardValue.get('amount')} {awardValue.get('currency')} + {awardValue ? + awardValue.get('amount') + ' ' + awardValue.get('currency') : + 'N/A' + } + {startDate.toLocaleDateString()}—{endDate.toLocaleDateString()} {this.t(`crd:corruptionType:${type}:name`)}
this.setState({showPopup: false})} + onMouseLeave={() => this.setState({ showPopup: false })} > {flaggedStats.get('count')} {showPopup && this.getPopup()} @@ -59,7 +59,7 @@ class Popup extends translatable(React.Component){ class ProcurementsTable extends Table{ row(entry, index){ - const {translations} = this.props; + const { translations, navigate } = this.props; const tenderValue = entry.getIn(['tender', 'value']); let awardValue; @@ -83,14 +83,27 @@ class ProcurementsTable extends Table{ const procuringEntityName = entry.getIn(['procuringEntity', 'name']); const title = entry.get('title'); + const id = entry.get('ocid'); return (
{entry.get('tag', []).join(', ')}{entry.get('ocid')} + navigate('contract', id)} + > + {id} + + @@ -114,11 +127,11 @@ class ProcurementsTable extends Table{ translations={translations} />
@@ -138,7 +151,7 @@ class ProcurementsTable extends Table{ {data && data.map(this.row.bind(this))}
- ) + ); } } From efd4f6ba420c5056407374ab3a2a7c75d551ac41 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Wed, 15 Nov 2017 22:45:59 +0200 Subject: [PATCH 274/702] OCE-404 Filtering winnings awards for procurement table --- .../rest/controller/CorruptionRiskDashboardTablesController.java | 1 + .../controller/flags/AbstractFlagReleaseSearchController.java | 1 + 2 files changed, 2 insertions(+) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java index 251a2fdb7..197b4eb52 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java @@ -64,6 +64,7 @@ public List corruptionRiskOverviewTable( project("ocid", "tender.procuringEntity.name", "tender.tenderPeriod", "flags", "tender.title", "tag") .and("tender.value").as("tender.value").and("awards.value").as("awards.value") + .and("awards.status").as("awards.status") .andExclude(Fields.UNDERSCORE_ID), sort(Sort.Direction.DESC, "flags.flaggedStats.count"), skip(filter.getSkip()), diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java index cf5dbd411..6a5be31f3 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java @@ -41,6 +41,7 @@ public List releaseFlagSearch(@ModelAttribute @Valid final YearFilterP project("ocid", "tender.procuringEntity.name", "tender.tenderPeriod", "flags", "tender.title", "tag") .and("tender.value").as("tender.value").and("awards.value").as("awards.value") + .and("awards.status").as("awards.status") .andExclude(Fields.UNDERSCORE_ID), sort(Sort.Direction.DESC, "flags.flaggedStats.count"), skip(filter.getSkip()), From 3d55ffd4db5e42fd5f1cec4c87cb20977cbac8df Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 15 Nov 2017 23:39:31 +0200 Subject: [PATCH 275/702] OCE-379 Aligning info content with the table --- ui/oce/corruption-risk/contracts/style.less | 11 ++++++++++- ui/oce/corruption-risk/style.less | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index ac1a8078e..304d93e63 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -82,7 +82,16 @@ section.info { margin-bottom: 60px; - .flags{ + + &>.row{ + margin-left: 0; + margin-right: 0; + &>*{ + padding-left: 0; + } + } + + .flags { font-size: 2em; } } diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 01b16fab1..29c1a0ab8 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -185,10 +185,12 @@ font-size: 80%; line-height:128%; } + i{ color: #34ac45; float: right; } + .total-flags{ background: #fafafa; margin: 15px -15px 0 -30px; From ac0be1021617c58c7005d49de480d6a52118a7d3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 17 Nov 2017 15:46:18 +0200 Subject: [PATCH 276/702] OCE-395 Added react-bootstrap-table --- ui/oce/corruption-risk/contracts/index.jsx | 185 +++++++++++--------- ui/oce/corruption-risk/contracts/style.less | 1 + ui/package.json | 1 + 3 files changed, 106 insertions(+), 81 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 0ff89a300..837503610 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -1,6 +1,8 @@ +import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; import { List } from 'immutable'; -import cn from 'classnames'; import URI from 'urijs'; +// eslint-disable-next-line no-unused-vars +import rbtStyles from 'react-bootstrap-table/dist/react-bootstrap-table-all.min.css'; import CRDPage from '../page'; import Visualization from '../../visualization'; import TopSearch from './top-search'; @@ -9,77 +11,112 @@ const API_ROOT = '/api'; class CList extends Visualization { buildUrl(ep) { - let { filters, searchQuery } = this.props; - let uri = new URI(API_ROOT + '/' + ep).addSearch(filters.toJS()); + const { filters, searchQuery } = this.props; + const uri = new URI(`${API_ROOT}/${ep}`) + .addSearch('pageSize', 20) + .addSearch(filters.toJS()); return searchQuery ? uri.addSearch('text', searchQuery) : uri; } - componentDidUpdate(prevProps){ + componentDidUpdate(prevProps) { const { filters, searchQuery } = this.props; - if (filters != prevProps.filters || searchQuery != prevProps.searchQuery) { + if (filters !== prevProps.filters || searchQuery !== prevProps.searchQuery) { this.fetch(); } } - render() { - const { data, navigate } = this.props; + mkLink(content, { id }) { + const { navigate } = this.props; return ( -
{contract.getIn(['tender', 'status'], this.t('general:undefined'))} - navigate('contract', id)} - > - {id} - - - navigate('contract', id)} - > - {contract.getIn(['tender', 'title'], id)} - - - {contract.getIn(['tender', 'procuringEntity', 'name'], this.t('general:undefined'))} - - {contract.getIn(['tender', 'value', 'amount'], this.t('general:undefined'))} -   - {contract.getIn(['tender', 'value', 'currency'])} - - {contract.getIn(['awards', 0, 'value', 'amount'], this.t('general:undefined'))} -   - {contract.getIn(['awards', 0, 'value', 'currency'])} - - {startDate ? - new Date(startDate).toLocaleDateString() : - this.t('general:undefined') - } - - {flagType ? - this.t(`crd:corruptionType:${flagType}:name`) : - this.t('general:undefined')} -
- - - - - - - - - - - - - this.setState({ list: newList })} - navigate={navigate} - translations={translations} - searchQuery={searchQuery} - /> -
{this.t('crd:contracts:baseInfo:status')}{this.t('crd:procurementsTable:contractID')}{this.t('crd:general:contract:title')}{this.t('crd:contracts:list:procuringEntity')}{this.t('crd:procurementsTable:tenderAmount')}{this.t('crd:contracts:list:awardAmount')}{this.t('crd:procurementsTable:tenderDate')}{this.t('crd:procurementsTable:flagType')}
+ this.setState({ list: newList })} + navigate={navigate} + translations={translations} + searchQuery={searchQuery} + /> {searchQuery && !list.count() ? {this.t('crd:contracts:top-search:nothingFound')} : null}
diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 304d93e63..f247e599f 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -30,6 +30,7 @@ } .contracts-page{ + margin-bottom: 40px; table { th, td{ width: 12.5%; diff --git a/ui/package.json b/ui/package.json index 2f24c412f..dda852407 100644 --- a/ui/package.json +++ b/ui/package.json @@ -71,6 +71,7 @@ "react": "^15.6.1", "react-addons-test-utils": "^15.1.0", "react-bootstrap": "^0.30.6", + "react-bootstrap-table": "^4.1.4", "react-dom": "^15.6.1", "react-hot-loader": "^1.3.0", "react-immutable-proptypes": "^1.2.0", From 6af15c41ce06144ae20acb50b7c0580df533a733 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 17 Nov 2017 17:42:20 +0200 Subject: [PATCH 277/702] OCE-395 Handling size per page --- ui/oce/corruption-risk/contracts/index.jsx | 49 +++++++++++++++++++--- ui/oce/corruption-risk/index.jsx | 3 +- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 837503610..160d5e414 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -1,4 +1,5 @@ import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; +import PaginationList from 'react-bootstrap-table/lib/pagination/PaginationList'; import { List } from 'immutable'; import URI from 'urijs'; // eslint-disable-next-line no-unused-vars @@ -10,19 +11,28 @@ import TopSearch from './top-search'; const API_ROOT = '/api'; class CList extends Visualization { + constructor(...args){ + super(...args); + this.state = { + pageSize: 20 + } + } + buildUrl(ep) { const { filters, searchQuery } = this.props; + const { pageSize } = this.state; const uri = new URI(`${API_ROOT}/${ep}`) - .addSearch('pageSize', 20) + .addSearch('pageSize', pageSize) .addSearch(filters.toJS()); return searchQuery ? uri.addSearch('text', searchQuery) : uri; } - componentDidUpdate(prevProps) { - const { filters, searchQuery } = this.props; - if (filters !== prevProps.filters || searchQuery !== prevProps.searchQuery) { + componentDidUpdate(prevProps, prevState) { + const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] != prevProps[key]); + const stateChanged = ['pageSize'].some(key => this.state[key] != prevState[key]); + if (propsChanged || stateChanged) { this.fetch(); } } @@ -39,9 +49,26 @@ class CList extends Visualization { ); } + renderPaginationPanel(props) { + console.log(props); + delete props.totalPages; + return ( +
+ +
+ ) + } + render() { - const { data } = this.props; + const { data, count } = this.props; + if (!data || !data.count()) return null; + + const { pageSize } = this.state; + const jsData = data.map((contract) => { const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + ' ' + @@ -79,8 +106,17 @@ class CList extends Visualization { striped bordered={false} pagination + remote + fetchInfo = {{ + dataTotalSize: count + }} options={{ page: 1, + dataSize: 100, + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({text: value, value})), + onSizePerPageList: pageSize => this.setState({ pageSize }), + paginationPosition: 'both', }} > @@ -131,7 +167,7 @@ export default class Contracts extends CRDPage { render() { const { list } = this.state; - const { filters, navigate, translations, searchQuery, doSearch } = this.props; + const { filters, navigate, translations, searchQuery, doSearch, count } = this.props; return (
{searchQuery && !list.count() ? {this.t('crd:contracts:top-search:nothingFound')} : null} diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 2aaa5aed4..96be3694b 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -104,7 +104,7 @@ class CorruptionRiskDashboard extends React.Component { const styling = this.constructor.STYLING || this.props.styling; const [page] = route; - const { appliedFilters, indicatorTypesMapping, width } = this.state; + const { appliedFilters, indicatorTypesMapping, width, data } = this.state; const { filters, years, months } = this.destructFilters(appliedFilters); const monthly = years.count() === 1; @@ -155,6 +155,7 @@ class CorruptionRiskDashboard extends React.Component { translations={translations} searchQuery={searchQuery} doSearch={query => navigate('contracts', query)} + count={data.getIn(['totalFlags', 'contractCounter'])} /> ); } else if (page === 'contract') { From da6db1b2e7f9124e83f7c7cfbe3286698cef824c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 17 Nov 2017 17:50:38 +0200 Subject: [PATCH 278/702] OCE-395 Pagination --- ui/oce/corruption-risk/contracts/index.jsx | 28 +++++++--------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 160d5e414..88f5185d9 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -14,15 +14,18 @@ class CList extends Visualization { constructor(...args){ super(...args); this.state = { - pageSize: 20 + pageSize: 20, + page: 1 } } buildUrl(ep) { const { filters, searchQuery } = this.props; - const { pageSize } = this.state; + const { pageSize, page } = this.state; + const skip = pageSize * (page - 1); const uri = new URI(`${API_ROOT}/${ep}`) .addSearch('pageSize', pageSize) + .addSearch('pageNumber', page - 1) .addSearch(filters.toJS()); return searchQuery ? uri.addSearch('text', searchQuery) : @@ -31,7 +34,7 @@ class CList extends Visualization { componentDidUpdate(prevProps, prevState) { const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] != prevProps[key]); - const stateChanged = ['pageSize'].some(key => this.state[key] != prevState[key]); + const stateChanged = ['pageSize', 'page'].some(key => this.state[key] != prevState[key]); if (propsChanged || stateChanged) { this.fetch(); } @@ -49,25 +52,12 @@ class CList extends Visualization { ); } - renderPaginationPanel(props) { - console.log(props); - delete props.totalPages; - return ( -
- -
- ) - } - render() { const { data, count } = this.props; if (!data || !data.count()) return null; - const { pageSize } = this.state; + const { pageSize, page } = this.state; const jsData = data.map((contract) => { const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + @@ -111,8 +101,8 @@ class CList extends Visualization { dataTotalSize: count }} options={{ - page: 1, - dataSize: 100, + page, + onPageChange: page => this.setState({ page }), sizePerPage: pageSize, sizePerPageList: [20, 50, 100, 200].map(value => ({text: value, value})), onSizePerPageList: pageSize => this.setState({ pageSize }), From 97b39cc07f6d84d60f844e61658b3a6148e45fa6 Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Sun, 19 Nov 2017 14:58:09 +0200 Subject: [PATCH 279/702] added excel export functionality. --- checkstyle.xml | 2 +- .../forms/util/MarkupCacheService.java | 7 + persistence/pom.xml | 35 +- .../persistence/excel/AbstractExcelSheet.java | 212 +++++++++++ .../persistence/excel/ExcelFieldService.java | 196 ++++++++++ .../toolkit/persistence/excel/ExcelFile.java | 18 + .../persistence/excel/ExcelFileDefault.java | 44 +++ .../toolkit/persistence/excel/ExcelSheet.java | 62 +++ .../persistence/excel/ExcelSheetDefault.java | 354 ++++++++++++++++++ .../toolkit/persistence/excel/FieldType.java | 51 +++ .../excel/annotation/ExcelExport.java | 27 ++ .../persistence/excel/info/ClassFields.java | 14 + .../excel/info/ClassFieldsDefault.java | 69 ++++ .../excel/info/ClassFieldsExcelExport.java | 33 ++ .../excel/service/ExcelGeneratorService.java | 61 +++ persistence/src/main/resources/ehcache.xml | 9 + pom.xml | 2 +- .../generators/GenericExcelKeyGenerator.java | 49 +++ .../toolkit/web/spring/MvcConfig.java | 11 + 19 files changed, 1253 insertions(+), 3 deletions(-) create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java create mode 100644 web/src/main/java/org/devgateway/toolkit/web/generators/GenericExcelKeyGenerator.java diff --git a/checkstyle.xml b/checkstyle.xml index 66e6b24fe..2e94cee8c 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -81,7 +81,7 @@ - + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java index e13d6ae07..ce11bbc65 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java @@ -153,6 +153,13 @@ public void clearReportsApiCache() { if (cache != null) { cache.removeAll(); } + + // get the reports cache "excelExportCache", declared in ehcache.xml + Cache excelExportCache = cm.getCache("excelExportCache"); + + if (excelExportCache != null) { + excelExportCache.removeAll(); + } } private String createCacheKey(final String outputType, final String reportName, final String parameters) { diff --git a/persistence/pom.xml b/persistence/pom.xml index 4c8e86ad4..f41e12e39 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -23,6 +23,8 @@ 5.0.0.GA 2.6.11 0.3.5-06032016 + 3.2.1 + 20.0 @@ -42,7 +44,7 @@ org.springframework.boot spring-boot-starter-tomcat - + org.springframework.boot spring-boot-starter-data-rest @@ -178,10 +180,12 @@ liquibase-core ${liquibase.version} + joda-time joda-time + org.jadira.usertype usertype.core @@ -193,6 +197,35 @@ + + + org.apache.commons + commons-lang3 + ${commons.lang3.version} + + + + com.google.guava + guava + ${guava.version} + + + + commons-beanutils + commons-beanutils + + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java new file mode 100644 index 000000000..9e947f3e4 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheet.java @@ -0,0 +1,212 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * Class that prepares the default Styles and Fonts for Excel cells. + * + * @author idobre + * @since 13/11/2017 + */ +public abstract class AbstractExcelSheet implements ExcelSheet { + protected final Workbook workbook; + + private Font dataFont; + + private Font headerFont; + + private Font linkFont; + + private final CellStyle dataStyleCell; + + private final CellStyle headerStyleCell; + + private final CellStyle linkStyleCell; + + private final CreationHelper createHelper; + + // declare only one cell object reference + private Cell cell; + + public AbstractExcelSheet(final Workbook workbook) { + this.workbook = workbook; + + // get the styles from workbook without creating them again (by default the workbook has already 1 style) + if (workbook.getNumCellStyles() > 1) { + this.dataStyleCell = workbook.getCellStyleAt((short) 1); + this.headerStyleCell = workbook.getCellStyleAt((short) 2); + this.linkStyleCell = workbook.getCellStyleAt((short) 3); + } else { + // init the fonts and styles + this.dataFont = this.workbook.createFont(); + this.dataFont.setFontHeightInPoints((short) 12); + this.dataFont.setFontName("Times New Roman"); + this.dataFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); + + this.headerFont = this.workbook.createFont(); + this.headerFont.setFontHeightInPoints((short) 14); + this.headerFont.setFontName("Times New Roman"); + this.headerFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); + this.headerFont.setBold(true); + + this.linkFont = this.workbook.createFont(); + this.linkFont.setFontHeightInPoints((short) 12); + this.linkFont.setFontName("Times New Roman"); + // by default hyperlinks are blue and underlined + this.linkFont.setColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex()); + this.linkFont.setUnderline(Font.U_SINGLE); + + this.dataStyleCell = this.workbook.createCellStyle(); + this.dataStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.dataStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.dataStyleCell.setWrapText(true); + this.dataStyleCell.setFont(this.dataFont); + + this.headerStyleCell = this.workbook.createCellStyle(); + this.headerStyleCell.setAlignment(HorizontalAlignment.CENTER); + this.headerStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.headerStyleCell.setWrapText(true); + this.headerStyleCell.setFont(this.headerFont); + + this.linkStyleCell = this.workbook.createCellStyle(); + this.linkStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.linkStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); + this.linkStyleCell.setWrapText(true); + this.linkStyleCell.setFont(this.linkFont); + } + + this.createHelper = workbook.getCreationHelper(); + } + + /** + * Creates a cell and tries to determine it's type based on the value type. + * + * There is only one Cell object otherwise the Heap Space will fill really quickly. + * + * @param value + * @param row + * @param column + */ + @Override + public void writeCell(final Object value, final Row row, final int column) { + // try to determine the cell type based on the object value + // if nothing matches then use 'CellType.STRING' as type and call the object toString() function. + // * don't create any cell if the value is null (Cell.CELL_TYPE_BLANK) + // * do nothing if we have an empty List/Set instead of display empty brackets like [ ] + if (value != null && !((value instanceof List || value instanceof Set) && ((Collection) value).isEmpty())) { + if (value instanceof String) { + cell = row.createCell(column, CellType.STRING); + cell.setCellValue((String) value); + } else { + if (value instanceof Integer) { + cell = row.createCell(column, CellType.NUMERIC); + cell.setCellValue((Integer) value); + } else { + if (value instanceof BigDecimal) { + cell = row.createCell(column, CellType.NUMERIC); + cell.setCellValue(((BigDecimal) value).doubleValue()); + } else { + if (value instanceof Boolean) { + cell = row.createCell(column, CellType.BOOLEAN); + cell.setCellValue(((Boolean) value) ? "Yes" : "No"); + } else { + if (value instanceof Date) { + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); + + cell = row.createCell(column, CellType.STRING); + cell.setCellValue(sdf.format((Date) value)); + } else { + cell = row.createCell(column, CellType.STRING); + cell.setCellValue(value.toString()); + } + } + } + } + } + + cell.setCellStyle(dataStyleCell); + } else { + // create a CellType.BLANK + row.createCell(column); + } + } + + /** + * Create a header cell with a particular style. + * + * @param value + * @param row + * @param column + */ + protected void writeHeaderCell(final Object value, final Row row, final int column) { + this.writeCell(value, row, column); + cell.setCellStyle(headerStyleCell); + } + + /** + * Creates a cell that is a link to another sheet in the document {@link HyperlinkType#DOCUMENT}. + * + * @param value + * @param row + * @param column + * @param sheetName + * @param rowNumber + */ + public void writeCellLink(final Object value, final Row row, final int column, + final String sheetName, final int rowNumber) { + this.writeCell(value, row, column); + final Hyperlink link = createHelper.createHyperlink(HyperlinkType.DOCUMENT); + + // always point to first column A in excel file + link.setAddress("'" + sheetName + "'!A" + rowNumber); + cell.setHyperlink(link); + cell.setCellStyle(linkStyleCell); + } + + /** + * Create a new row and set the default height (different heights for headers and data rows). + * + * @param sheet + * @param rowNumber + * @return Row + */ + protected Row createRow(final Sheet sheet, final int rowNumber) { + final Row row = sheet.createRow(rowNumber); + + if (rowNumber < 1) { + row.setHeight((short) 800); // 40px (800 / 10 / 2) + } else { + row.setHeight((short) 600); // 30px (600 / 10 / 2) + } + + return row; + } + + /** + * Get the last 'free' cell of a {@link Row}. + * + * @param row + */ + protected int getFreeColl(final Row row) { + return row.getLastCellNum() == -1 ? 0 : row.getLastCellNum(); + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java new file mode 100644 index 000000000..ad993f7be --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFieldService.java @@ -0,0 +1,196 @@ +package org.devgateway.toolkit.persistence.excel; + +import com.google.common.collect.Lists; +import org.apache.log4j.Logger; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.devgateway.toolkit.persistence.excel.info.ClassFields; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsDefault; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsExcelExport; +import org.springframework.data.domain.Persistable; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author idobre + * @since 10/11/2017 + */ +public final class ExcelFieldService { + private static final Logger logger = Logger.getLogger(ExcelFieldService.class); + + private static Map fieldsTypeCache; + + private static Map fieldsClassCache; + + private static Map> fieldsCache; + + + static { + fieldsTypeCache = new HashMap<>(); + fieldsClassCache = new HashMap<>(); + fieldsCache = new HashMap<>(); + } + + /** + * Utility classes should not have a public or default constructor. + */ + private ExcelFieldService() { + + } + + /** + * Return the {@link FieldType} of a Field + * This is used to determine the writing strategy for this particular field. + * + * @param field + * @return {@link FieldType} + */ + public static FieldType getFieldType(final Field field) { + FieldType fieldType; + + if (fieldsTypeCache.get(field) != null) { + fieldType = fieldsTypeCache.get(field); + } else { + fieldType = FieldType.basic; // default field type + final Class fieldClass = getFieldClass(field); + + // first we check if we have a basic type + if (FieldType.BASICTYPES.contains(fieldClass) || fieldClass.isEnum()) { + fieldType = FieldType.basic; + } else { + final ExcelExport excelExport = field.getAnnotation(ExcelExport.class); + if (excelExport != null) { + fieldType = FieldType.object; + + // if we just want to export a field (toString) we consider it a basic type. + if (excelExport.justExport()) { + fieldType = FieldType.basic; + } + + // check if we want to export the object in a separate sheet + if (excelExport.separateSheet()) { + fieldType = FieldType.objectSeparateSheet; + } + } + } + + fieldsTypeCache.put(field, fieldType); + } + + return fieldType; + } + + + /** + * Return the Class of a field. + * + * @param field + * @return {@link Class} + */ + public static Class getFieldClass(final Field field) { + Class fieldClass = null; + + if (fieldsClassCache.get(field) != null) { + fieldClass = fieldsClassCache.get(field); + } else { + if (isCollection(field)) { + final ParameterizedType genericListType = (ParameterizedType) field.getGenericType(); + try { + fieldClass = Class.forName(genericListType.getActualTypeArguments()[0].getTypeName()); + } catch (ClassNotFoundException e) { + logger.error(e); + } + } else { + fieldClass = field.getType(); + } + + fieldsClassCache.put(field, fieldClass); + } + + return fieldClass; + } + + /** + * Returns an Iterator with the Fields of a Class. + * The fields are filtered with the {@link ClassFieldsExcelExport} class. + * + * @param clazz + * @return {@link Iterator} + */ + public static Iterator getFields(final Class clazz) { + final Iterator fields; + + if (fieldsCache.get(clazz) != null) { + final List fieldsList = fieldsCache.get(clazz); + fields = fieldsList.iterator(); + } else { + final ClassFields classFields = new ClassFieldsExcelExport(new ClassFieldsDefault(clazz, true)); + fields = classFields.getFields(); + + fieldsCache.put(clazz, Lists.newArrayList(classFields.getFields())); + } + + return fields; + } + + /** + * Get the ID for an Entity - {@link Persistable}. + * + * Return -1 if everything wrong happened. + * + * @param object + * @return Entity ID + */ + public static Long getObjectID(final Object object) { + Long objectId = Long.valueOf(-1); + + if (object == null || !(Persistable.class.isAssignableFrom(object.getClass()))) { + return Long.valueOf(-1); + } + + try { + final Method idMethod = object.getClass().getMethod("getId"); + if (idMethod != null) { + objectId = (Long) idMethod.invoke(object); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + + return objectId; + } + + /** + * Returns the name that we should use as a header for this {@link Field}. + * + * @param field + */ + public static String getFieldName(final Field field) { + final ExcelExport excelExport = field.getAnnotation(ExcelExport.class); + if (excelExport != null) { + if (!excelExport.name().isEmpty()) { + return excelExport.name(); + } + } + + return field.getName(); + } + + /** + * Check if a {@link Field} Type is a Collection. + * + * @param field + * @return {@link Boolean} + */ + public static Boolean isCollection(final Field field) { + return field.getType().equals(java.util.Collection.class) + || field.getType().equals(java.util.Set.class) + || field.getType().equals(java.util.List.class); + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java new file mode 100644 index 000000000..5f6e2d11d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFile.java @@ -0,0 +1,18 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.Workbook; + +/** + * ExcelFile Type - it should transform a list of Objects into a Workbook. + * + * @author idobre + * @since 13/11/2017 + */ +public interface ExcelFile { + /** + * Create an Workbook that can be exported. + * + * @return {@link Workbook} + */ + Workbook createWorkbook(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java new file mode 100644 index 000000000..b2e7521ae --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefault.java @@ -0,0 +1,44 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; + +import java.util.List; + +/** + * Default implementation of the {@link ExcelFile} type. + * + * @author idobre + * @since 13/11/2017 + */ +public class ExcelFileDefault implements ExcelFile { + private final List objects; + + private final Workbook workbook; + + public ExcelFileDefault(final List objects) { + Validate.notNull(objects, "The list of objects can't be null!"); + Validate.noNullElements(objects, "The list of objects can't contain null elements!"); + + this.objects = objects; + + // create the excel file + this.workbook = new SXSSFWorkbook(100); + } + + @Override + public Workbook createWorkbook() { + // don't do anything if the list of objects is empty, just display the error message. + if (objects.isEmpty()) { + final ExcelSheet excelSheet = new ExcelSheetDefault(this.workbook, "no data"); + excelSheet.emptySheet(); + } else { + final Class clazz = this.objects.get(0).getClass(); + final ExcelSheet excelSheet = new ExcelSheetDefault(this.workbook, clazz.getSimpleName().toLowerCase()); + excelSheet.writeSheet(clazz, objects); + } + + return workbook; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java new file mode 100644 index 000000000..82cc941fa --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheet.java @@ -0,0 +1,62 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.Row; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; + +import java.util.List; + +/** + * Create an Excel Sheet representation of an Object of type . + * In this process it's possible to create a new Excel Sheet if + * some fields are annotated with {@link ExcelExport#separateSheet = true} + * + * @author idobre + * @since 13/11/2017 + */ +public interface ExcelSheet { + /** + * Write the value into specified {@link Row} and column number. + * + * @param value + * @param row + * @param column + */ + void writeCell(Object value, Row row, int column); + + /** + * Write the object into specified {@link Row}. + * We need to also know the {@link Class} of the Object + * since we will export null objects as well but with empty cells. + * + * @param clazz + * @param object + * @param row + */ + void writeRow(Class clazz, Object object, Row row); + + /** + * Write the received list of objects into an excel sheet. + * We need to also know the {@link Class} of the Object(s) + * since we will export null objects as well but with empty cells. + * + * @param clazz + * @param objects + */ + void writeSheet(Class clazz, List objects); + + /** + * Write the objects and return the first row index. + * Used to (possible) create a document link between sheets. + */ + int writeSheetGetLink(Class clazz, List objects); + + /** + * Returns the Sheet name. + */ + String getExcelSheetName(); + + /** + * Just create an empty excel sheet. + */ + void emptySheet(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java new file mode 100644 index 000000000..4f2b23c83 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/ExcelSheetDefault.java @@ -0,0 +1,354 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.log4j.Logger; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.StringJoiner; + +/** + * @author idobre + * @since 13/11/2017 + */ +public class ExcelSheetDefault extends AbstractExcelSheet { + private static final Logger logger = Logger.getLogger(ExcelSheetDefault.class); + + private static final String PARENTSHEET = "parentSheet"; + private static final String PARENTID = "parentID"; + private static final String PARENTROWNUMBER = "parentRowNumber"; + + private final Sheet excelSheet; + + private final String excelSheetName; + + // Use this field to identify the header prefix for children objects that are exported in the same sheet. + private final Stack headerPrefix; + + private Map parentInfo; + + // Boolean that identifies if the header was written already for this sheet. + private Boolean headerWasWritten = false; + + /** + * Constructor used to print an Object in a separate excel sheet. + * The sheet will be created based on object name that is exported. + * + * {@link #excelSheetName} would be the name of the excel + * and the recommended value is {@link Class#getSimpleName().toLowerCase();} + * + * @param workbook + * @param excelSheetName + */ + public ExcelSheetDefault(final Workbook workbook, final String excelSheetName) { + super(workbook); + + this.excelSheetName = excelSheetName; + this.headerPrefix = new Stack<>(); + + // it's possible that this sheet was created by other object + if (workbook.getSheet(excelSheetName) == null) { + // create the main excel sheet + excelSheet = workbook.createSheet(excelSheetName); + + // freeze the header row + excelSheet.createFreezePane(0, 1); + + // create the first row that is used as a header + createRow(excelSheet, 0); + + // set a default width - this will increase the performance + excelSheet.setDefaultColumnWidth(35); + } else { + excelSheet = workbook.getSheet(excelSheetName); + } + } + + /** + * Constructor used to print an Object in a separate sheet + * but with the additional possibility to create a link back to Object's Parent sheet. + * + * @param workbook + * @param excelSheetName + * @param parentInfo + */ + ExcelSheetDefault(final Workbook workbook, final String excelSheetName, final Map parentInfo) { + this(workbook, excelSheetName); + + this.parentInfo = parentInfo; + } + + @Override + public void writeRow(final Class clazz, final Object object, final Row row) { + final Iterator fields = ExcelFieldService.getFields(clazz); + + // if we have a parent then the first row should be the parent name with a link. + if (getFreeColl(row) == 0 && parentInfo != null) { + String parentCell = (String) parentInfo.get(PARENTSHEET); + if (parentInfo.get(PARENTID) != null) { + parentCell += " - " + parentInfo.get(PARENTID); + } + writeHeaderLabel("Parent", row, 0); + writeCellLink(parentCell, row, 0, + (String) parentInfo.get(PARENTSHEET), (int) parentInfo.get(PARENTROWNUMBER)); + } + + while (fields.hasNext()) { + final Field field = fields.next(); + final FieldType fieldType = ExcelFieldService.getFieldType(field); + + try { + switch (fieldType) { + case basic: + int coll = getFreeColl(row); + writeHeaderLabel(field, row, coll); + writeCell(getFieldValue(field, object), row, coll); + + break; + + case object: + headerPrefix.push(ExcelFieldService.getFieldName(field)); + Class fieldClass = ExcelFieldService.getFieldClass(field); + + if (ExcelFieldService.isCollection(field)) { + final List flattenObjects = new ArrayList(); + flattenObjects.addAll((Collection) getFieldValue(field, object)); + writeRowFlattenObject(fieldClass, flattenObjects, row); + } else { + writeRow(fieldClass, getFieldValue(field, object), row); + } + + headerPrefix.pop(); + break; + + case objectSeparateSheet: + fieldClass = ExcelFieldService.getFieldClass(field); + + // add some informations about the parent + final Map info = new HashMap(); + info.put(PARENTSHEET, excelSheetName); + info.put(PARENTID, ExcelFieldService.getObjectID(object)); + info.put(PARENTROWNUMBER, row.getRowNum() + 1); + + final ExcelSheet objectSepareteSheet = new ExcelSheetDefault(workbook, + fieldClass.getSimpleName().toLowerCase(), info); + final List newObjects = new ArrayList(); + final Object value = getFieldValue(field, object); + + if (value != null) { + if (ExcelFieldService.isCollection(field)) { + newObjects.addAll((Collection) value); + } else { + newObjects.add(value); + } + } + + coll = getFreeColl(row); + writeHeaderLabel(field, row, coll); + final int rowNumber = objectSepareteSheet.writeSheetGetLink(fieldClass, newObjects); + if (rowNumber != -1) { + writeCellLink(field.getName(), row, coll, + objectSepareteSheet.getExcelSheetName(), rowNumber); + } else { + writeCell(null, row, coll); + } + + break; + + default: + logger.error("Undefined field type"); + break; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + } + } + + /** + * Function to flat export an array of objects. Example: + * [{ + * x: aaa, + * y: bbb, + * },{ + * x: ccc, + * y: ddd, + * }] + * + * Will be printed as: + * | x | y | + * | aaa ; bbb | ccc ; ddd | + * + * @param clazz + * @param objects + * @param row + */ + private void writeRowFlattenObject(final Class clazz, final List objects, final Row row) { + final Iterator fields = ExcelFieldService.getFields(clazz); + + while (fields.hasNext()) { + final Field field = fields.next(); + final FieldType fieldType = ExcelFieldService.getFieldType(field); + + try { + switch (fieldType) { + case basic: + final int coll = getFreeColl(row); + final StringJoiner flattenValue = new StringJoiner(" | "); + + for (final Object obj : objects) { + final Object value = getFieldValue(field, obj); + if (value != null) { + flattenValue.add(value.toString()); + } + } + + writeHeaderLabel(field, row, coll); + writeCell(flattenValue.toString(), row, coll); + break; + + case object: + if (ExcelFieldService.isCollection(field)) { + logger.error("Unsupported operation for field: '" + field.getName() + "'! You can not " + + "flatten an array of objects that contains other array of objects"); + } else { + headerPrefix.push(ExcelFieldService.getFieldName(field)); + final Class fieldClass = ExcelFieldService.getFieldClass(field); + final List newObjects = new ArrayList(); + + for (Object obj : objects) { + final Object value = getFieldValue(field, obj); + if (value != null) { + newObjects.add(value); + } + } + + writeRowFlattenObject(fieldClass, newObjects, row); + headerPrefix.pop(); + } + + break; + + case objectSeparateSheet: + logger.error("Unsupported operation for field: '" + field.getName() + "'! You can not flatten " + + "an array of objects that contains objects that need to be printed in other sheet."); + break; + + default: + logger.error("Undefined field type"); + break; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.error(e); + } + } + } + + @Override + public void writeSheet(final Class clazz, final List objects) { + for (Object obj : objects) { + int lastRow = excelSheet.getLastRowNum(); + final Row row = createRow(excelSheet, ++lastRow); + + writeRow(clazz, obj, row); + } + } + + @Override + public int writeSheetGetLink(final Class clazz, final List objects) { + if (objects == null || objects.isEmpty()) { + return -1; + } + + // check if this is first time when we created this sheet and skip header row + // also add 2 since the getLastRowNum() function is 0-based and Excel is 1-based + int lastRow = excelSheet.getLastRowNum() == 0 ? 2 : (excelSheet.getLastRowNum() + 2); + this.writeSheet(clazz, objects); + + return lastRow; + } + + /** + * Print an error message in case we have an empty sheet. + */ + public void emptySheet() { + final Row row; + if (excelSheet.getRow(0) == null) { + row = createRow(excelSheet, 0); + } else { + row = excelSheet.getRow(0); + } + writeHeaderCell("No objects returned.", row, 0); + } + + /** + * Compute the header prefix for the current object. + */ + private String getHeaderPrefix() { + if (headerPrefix.isEmpty()) { + return ""; + } + + final StringJoiner header = new StringJoiner("/"); + final Enumeration elements = headerPrefix.elements(); + while (elements.hasMoreElements()) { + header.add(elements.nextElement()); + } + + return header.toString() + "/"; + } + + /** + * Functions that check if the header cell is empty, if yes then add the label. + */ + private void writeHeaderLabel(final String label, final Row row, final int coll) { + if (!headerWasWritten) { + if (row.getRowNum() == 1) { // write the header only once. + final Row headerRow = excelSheet.getRow(0); + final Cell headerCell = headerRow.getCell(coll); + + if (headerCell == null) { + writeHeaderCell(label, headerRow, coll); + } + } else { + headerWasWritten = true; + } + } + } + + /** + * Functions that check if the header cell is empty, if yes then add the field label. + */ + private void writeHeaderLabel(final Field field, final Row row, final int coll) { + if (!headerWasWritten) { + writeHeaderLabel(getHeaderPrefix() + ExcelFieldService.getFieldName(field), row, coll); + } + } + + /** + * Return the value of a {@link Field} from an {@link Object}. + */ + private Object getFieldValue(final Field field, final Object object) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + return object == null || !PropertyUtils.isReadable(object, field.getName()) + ? null + : PropertyUtils.getProperty(object, field.getName()); + } + + @Override + public String getExcelSheetName() { + return excelSheetName; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java new file mode 100644 index 000000000..e8d8c39b5 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/FieldType.java @@ -0,0 +1,51 @@ +package org.devgateway.toolkit.persistence.excel; + +import com.google.common.collect.ImmutableSet; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * Identify the type of a Field that will be used in the writing strategy. + * + * @author idobre + * @since 10/11/2017 + */ +public enum FieldType { + basic("basic"), + + object("object"), + + objectSeparateSheet("objectSeparateSheet"); + + private final String value; + + FieldType(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + /** + * This is not a complete list of primitive types in Java or their wrappers! + * Is used to quickly identify if a field is a 'simply' object that can be printed in a Cell + */ + public static final ImmutableSet> BASICTYPES = new ImmutableSet.Builder>() + .add(String.class) + .add(BigDecimal.class) + .add(Date.class) + .add(Integer.class) + .add(Long.class) + .add(Double.class) + .add(Float.class) + .add(Boolean.class) + .add(int.class) + .add(long.class) + .add(double.class) + .add(float.class) + .add(boolean.class) + .build(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java new file mode 100644 index 000000000..73e2e4da5 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/annotation/ExcelExport.java @@ -0,0 +1,27 @@ +package org.devgateway.toolkit.persistence.excel.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation that indicates if a field should be exported to Excel Export. + * + * {@link #name} - field name that will be used in the Excel header + * {@link #separateSheet} - parameter that indicates if an object should be exported in a separate Excel Sheet + * {@link #justExport} - just export the Object usually using {@link #toString} method (without exporting it's + * children) + * + * @author idobre + * @since 10/11/2017 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcelExport { + String value() default ""; + + String name() default ""; + + boolean separateSheet() default false; + + boolean justExport() default false; +} + diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java new file mode 100644 index 000000000..b1e63c9a2 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFields.java @@ -0,0 +1,14 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import java.lang.reflect.Field; +import java.util.Iterator; + +/** + * Returns all the fields of a Class. + * + * @author idobre + * @since 10/11/2017 + */ +public interface ClassFields { + Iterator getFields(); +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java new file mode 100644 index 000000000..269ca4d14 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefault.java @@ -0,0 +1,69 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Default implementation of {@link ClassFields} interface that will return all declared fields of a class. + * + * @author idobre + * @since 10/11/2017 + */ +public final class ClassFieldsDefault implements ClassFields { + private final Class clazz; + + private final Boolean getInheritedFields; + + private Field[] declaredFields; + + public ClassFieldsDefault(final Class clazz, final Boolean getInheritedFields) { + this.clazz = clazz; + this.getInheritedFields = getInheritedFields; + } + + public ClassFieldsDefault(final Class clazz) { + this(clazz, false); + } + + @Override + public Iterator getFields() { + // cache declared fields of a class + if (declaredFields == null) { + if (getInheritedFields) { + declaredFields = getAllFields(clazz).toArray(new Field[getAllFields(clazz).size()]); + } else { + declaredFields = clazz.getDeclaredFields(); + } + } + + // filter some of the fields including this$0 used in inner classes + final Iterator fields = Arrays.stream(declaredFields) + .filter(field -> !field.getName().equals("serialVersionUID")) + .filter(field -> !field.getName().equals("this$0")) + .iterator(); + + return fields; + } + + /** + * Function used to get also the inherited fields. + * + * @param clazz + * @return + */ + private List getAllFields(final Class clazz) { + final List fields = new ArrayList<>(); + final Class superClazz = clazz.getSuperclass(); + + if (superClazz != null) { + fields.addAll(getAllFields(superClazz)); + } + + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + + return fields; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java new file mode 100644 index 000000000..f5419314d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExport.java @@ -0,0 +1,33 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; + +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.stream.StreamSupport; + +/** + * Decorator class used to obtain only Excel Exportable fields (the ones annotated with {@link ExcelExport}. + * + * @author idobre + * @since 10/11/2017 + */ +public final class ClassFieldsExcelExport implements ClassFields { + private final ClassFields original; + + public ClassFieldsExcelExport(final ClassFields classFields) { + this.original = classFields; + } + + @Override + public Iterator getFields() { + final Iterable originalFields = () -> this.original.getFields(); + + // return only classes that are annotated with @ExcelExport + final Iterator fields = StreamSupport.stream(originalFields.spliterator(), false) + .filter(field -> field.getAnnotation(ExcelExport.class) != null) + .iterator(); + + return fields; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java new file mode 100644 index 000000000..835810ea7 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/excel/service/ExcelGeneratorService.java @@ -0,0 +1,61 @@ +package org.devgateway.toolkit.persistence.excel.service; + +import org.apache.poi.ss.usermodel.Workbook; +import org.devgateway.toolkit.persistence.excel.ExcelFile; +import org.devgateway.toolkit.persistence.excel.ExcelFileDefault; +import org.devgateway.toolkit.persistence.repository.BaseJpaRepository; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * @author idobre + * @since 15/11/2017 + */ +@Service +@CacheConfig(keyGenerator = "genericExcelKeyGenerator", cacheNames = "excelExportCache") +@Cacheable +public class ExcelGeneratorService { + /** + * Method that returns a byte array with an excel export. + * + * @param jpaRepository - {@link JpaRepository} from where we will get the data + * @param spec - {@link Specification} for filtering the data + * @param pageable - {@link Pageable} for paginating the data + * + * @return byte[] + * @throws IOException + */ + public byte[] getExcelDownload(final BaseJpaRepository jpaRepository, + final Specification spec, + final Pageable pageable) throws IOException { + final List objects = jpaRepository.findAll(spec, pageable).getContent(); + final ExcelFile excelFile = new ExcelFileDefault(objects); + final Workbook workbook = excelFile.createWorkbook(); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + workbook.write(baos); + final byte[] bytes = baos.toByteArray(); + + return bytes; + } + + /** + * Return the number of records for this {@link Specification}. + * + * @param jpaRepository + * @param spec + * @return count + */ + public long count(final BaseJpaRepository jpaRepository, + final Specification spec) { + return jpaRepository.count(spec); + } +} diff --git a/persistence/src/main/resources/ehcache.xml b/persistence/src/main/resources/ehcache.xml index c6a59470c..30aee7b9f 100644 --- a/persistence/src/main/resources/ehcache.xml +++ b/persistence/src/main/resources/ehcache.xml @@ -41,4 +41,13 @@ maxEntriesLocalHeap="100000" statistics="true"> + + + diff --git a/pom.xml b/pom.xml index ccacc2ab7..401880149 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 3.5.3 1.5.8.RELEASE 10.14.1.0 - 3.14 + 3.17 3.12 devgateway/toolkit 0.4.13 diff --git a/web/src/main/java/org/devgateway/toolkit/web/generators/GenericExcelKeyGenerator.java b/web/src/main/java/org/devgateway/toolkit/web/generators/GenericExcelKeyGenerator.java new file mode 100644 index 000000000..50c5fe386 --- /dev/null +++ b/web/src/main/java/org/devgateway/toolkit/web/generators/GenericExcelKeyGenerator.java @@ -0,0 +1,49 @@ +package org.devgateway.toolkit.web.generators; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.interceptor.KeyGenerator; + +import java.lang.reflect.Method; + +/** + * @author idobre + * @since 16/11/2017 + * + * {@link KeyGenerator} that uses some parameters to create a key. + * This KeyGenerator is used for Excel File Generator. + */ +public class GenericExcelKeyGenerator implements KeyGenerator { + private final Logger logger = LoggerFactory.getLogger(GenericExcelKeyGenerator.class); + + private final ObjectMapper objectMapper; + + public GenericExcelKeyGenerator(final ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Object generate(final Object target, final Method method, final Object... params) { + if (params.length < 2) { + throw new RuntimeException( + "Wrong parameters received for generating custom GenericExcelKeyGenerator key!"); + } + + try { + StringBuilder key = new StringBuilder(method.toString()); + key.append(params[0].getClass().getGenericInterfaces()[0].getTypeName()); // add the BaseJpaRepository class + key.append(params[1].getClass().getSimpleName()); // add Specification class + + for (int i = 1; i < params.length; i++) { + key.append(objectMapper.writeValueAsString(params[i])); + } + + return key.toString().hashCode(); + } catch (JsonProcessingException e) { + logger.error(e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java index 3476df3b6..611abf593 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java @@ -11,6 +11,12 @@ *******************************************************************************/ package org.devgateway.toolkit.web.spring; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.devgateway.toolkit.web.generators.GenericExcelKeyGenerator; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -26,4 +32,9 @@ public void addViewControllers(final ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); } + @Bean(name = "genericExcelKeyGenerator") + public KeyGenerator genericExcelKeyGenerator(final ObjectMapper objectMapper) { + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return new GenericExcelKeyGenerator(objectMapper); + } } From 2a6aa1296a4d77d696d0525559f50b8afc6418ea Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Sun, 19 Nov 2017 15:25:59 +0200 Subject: [PATCH 280/702] update excel charts feature to work with the new poi version. --- .../excelcharts/ExcelChartSheetDefault.java | 29 ++++++++++--------- .../excelcharts/data/XSSFAreaChartData.java | 4 +-- .../excelcharts/data/XSSFBarChartData.java | 4 +-- .../excelcharts/data/XSSFBubbleChartData.java | 4 +-- .../excelcharts/data/XSSFLineChartData.java | 4 +-- .../excelcharts/data/XSSFPieChartData.java | 4 +-- .../data/XSSFScatterChartData.java | 4 +-- .../data/XSSFStackedBarChartData.java | 2 +- .../excelcharts/ExcelChartDefaultTest.java | 2 +- .../ExcelChartSheetDefaultTest.java | 7 +++-- 10 files changed, 34 insertions(+), 30 deletions(-) diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefault.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefault.java index 693d5d9e4..2d93b2773 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefault.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefault.java @@ -3,12 +3,15 @@ import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.charts.ChartDataSource; import org.apache.poi.ss.usermodel.charts.ChartLegend; @@ -57,23 +60,23 @@ public ExcelChartSheetDefault(final Workbook workbook, final String excelSheetNa final Font dataFont = workbook.createFont(); dataFont.setFontHeightInPoints((short) DATAFONTHEIGHT); dataFont.setFontName("Times New Roman"); - dataFont.setColor(HSSFColor.BLACK.index); + dataFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); final Font headerFont = workbook.createFont(); headerFont.setFontHeightInPoints((short) HEADERFONTHEIGHT); headerFont.setFontName("Times New Roman"); - headerFont.setColor(HSSFColor.BLACK.index); + headerFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex()); headerFont.setBold(true); this.dataStyleCell = workbook.createCellStyle(); - this.dataStyleCell.setAlignment(CellStyle.ALIGN_LEFT); - this.dataStyleCell.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + this.dataStyleCell.setAlignment(HorizontalAlignment.LEFT); + this.dataStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); this.dataStyleCell.setWrapText(true); this.dataStyleCell.setFont(dataFont); this.headerStyleCell = workbook.createCellStyle(); - this.headerStyleCell.setAlignment(CellStyle.ALIGN_CENTER); - this.headerStyleCell.setVerticalAlignment(CellStyle.VERTICAL_CENTER); + this.headerStyleCell.setAlignment(HorizontalAlignment.CENTER); + this.headerStyleCell.setVerticalAlignment(VerticalAlignment.CENTER); this.headerStyleCell.setWrapText(true); this.headerStyleCell.setFont(headerFont); } @@ -93,27 +96,27 @@ public void writeCell(final Object value, final Row row, final int column) { if (value != null && !((value instanceof List || value instanceof Set) && ((Collection) value).isEmpty())) { final Cell cell; if (value instanceof String) { - cell = row.createCell(column, Cell.CELL_TYPE_STRING); + cell = row.createCell(column, CellType.STRING); cell.setCellValue((String) value); } else { if (value instanceof Integer) { - cell = row.createCell(column, Cell.CELL_TYPE_NUMERIC); + cell = row.createCell(column, CellType.NUMERIC); cell.setCellValue((Integer) value); } else { if (value instanceof Number) { - cell = row.createCell(column, Cell.CELL_TYPE_NUMERIC); + cell = row.createCell(column, CellType.NUMERIC); cell.setCellValue(((Number) value).doubleValue()); } else { if (value instanceof Boolean) { - cell = row.createCell(column, Cell.CELL_TYPE_BOOLEAN); + cell = row.createCell(column, CellType.BOOLEAN); cell.setCellValue(((Boolean) value) ? "Yes" : "No"); } else { if (value instanceof Date) { final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); - cell = row.createCell(column, Cell.CELL_TYPE_STRING); + cell = row.createCell(column, CellType.STRING); cell.setCellValue(sdf.format((Date) value)); } else { - cell = row.createCell(column, Cell.CELL_TYPE_STRING); + cell = row.createCell(column, CellType.STRING); cell.setCellValue(value.toString()); } } @@ -193,7 +196,7 @@ public ChartDataSource getCategoryChartDataSource() { throw new IllegalStateException("It seems that we don't have any category in the excel file"); } return getChartDataSource(0); // categories should always be on the - // first row + // first row } /** diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFAreaChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFAreaChartData.java index ddb94d240..d732e1707 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFAreaChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFAreaChartData.java @@ -28,7 +28,7 @@ public XSSFAreaChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -62,7 +62,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { final CTAreaChart areChart = plotArea.addNewAreaChart(); areChart.addNewVaryColors().setVal(false); - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); CTValAx[] ctValAx = plotArea.getValAxArray(); if (ctValAx.length != 0) { diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBarChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBarChartData.java index c8b49898d..331da5e03 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBarChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBarChartData.java @@ -31,7 +31,7 @@ public XSSFBarChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -69,7 +69,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { // set bars orientation barChart.addNewBarDir().setVal(barDir); - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); CTValAx[] ctValAx = plotArea.getValAxArray(); if (ctValAx.length != 0) { diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBubbleChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBubbleChartData.java index bdf96d67e..625b4dee1 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBubbleChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFBubbleChartData.java @@ -26,7 +26,7 @@ public XSSFBubbleChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -67,6 +67,6 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { bubbleChart.addNewAxId().setVal(ax.getId()); } - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); } } diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFLineChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFLineChartData.java index a3e6fda36..a76754e18 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFLineChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFLineChartData.java @@ -29,7 +29,7 @@ public XSSFLineChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -74,7 +74,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { lineChart.addNewAxId().setVal(ax.getId()); } - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); // add grid lines CTCatAx[] ctCatAx = plotArea.getCatAxArray(); diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFPieChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFPieChartData.java index f7871de95..795c55d6a 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFPieChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFPieChartData.java @@ -26,7 +26,7 @@ public XSSFPieChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -60,7 +60,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { final CTPieChart pieChart = plotArea.addNewPieChart(); pieChart.addNewVaryColors().setVal(true); - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); for (CustomChartSeries s : series) { s.addToChart(pieChart); diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFScatterChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFScatterChartData.java index 8db0bc009..0dc9d81ad 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFScatterChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFScatterChartData.java @@ -30,7 +30,7 @@ public XSSFScatterChartData(final String title) { @Override protected CustomChartSeries createNewSerie(final int id, final int order, final ChartDataSource categories, - final ChartDataSource values) { + final ChartDataSource values) { return new AbstractSeries(id, order, categories, values) { @Override public void addToChart(final XmlObject ctChart) { @@ -72,7 +72,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { scatterChart.addNewAxId().setVal(ax.getId()); } - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); // add grid lines CTCatAx[] ctCatAx = plotArea.getCatAxArray(); diff --git a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFStackedBarChartData.java b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFStackedBarChartData.java index 57943f552..3ee881a10 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFStackedBarChartData.java +++ b/web/src/main/java/org/devgateway/toolkit/web/excelcharts/data/XSSFStackedBarChartData.java @@ -44,7 +44,7 @@ public void fillChart(final Chart chart, final ChartAxis... axis) { // set bars orientation barChart.addNewBarDir().setVal(barDir); - xssfChart.setTitle(this.title); + xssfChart.setTitleText(this.title); CTValAx[] ctValAx = plotArea.getValAxArray(); if (ctValAx.length != 0) { diff --git a/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartDefaultTest.java b/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartDefaultTest.java index 599487f6f..e2d8f7a6a 100644 --- a/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartDefaultTest.java +++ b/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartDefaultTest.java @@ -44,7 +44,7 @@ public void createWorkbook() throws Exception { Assert.assertEquals("number of charts", 1, charts.size()); final XSSFChart chart = charts.get(0); - Assert.assertEquals("chart title", "line chart", chart.getTitle().getString()); + Assert.assertEquals("chart title", "line chart", chart.getTitleText().getString()); final CTChart ctChart = chart.getCTChart(); Assert.assertEquals("We should not have any area chart", 0, ctChart.getPlotArea().getAreaChartArray().length); diff --git a/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefaultTest.java b/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefaultTest.java index 1548c925c..5d54a5491 100644 --- a/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefaultTest.java +++ b/web/src/test/java/org/devgateway/toolkit/web/excelcharts/ExcelChartSheetDefaultTest.java @@ -1,6 +1,7 @@ package org.devgateway.toolkit.web.excelcharts; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Workbook; @@ -40,10 +41,10 @@ public void writeCell() throws Exception { excelChartSheet.writeCell("text", row, 2); excelChartSheet.writeCell(1, row, 3); - Assert.assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(0).getCellType()); + Assert.assertEquals(CellType.BLANK, row.getCell(0).getCellTypeEnum()); Assert.assertEquals("Yes", row.getCell(1).getStringCellValue()); - Assert.assertEquals(Cell.CELL_TYPE_STRING, row.getCell(2).getCellType()); - Assert.assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(3).getCellType()); + Assert.assertEquals(CellType.STRING, row.getCell(2).getCellTypeEnum()); + Assert.assertEquals(CellType.NUMERIC, row.getCell(3).getCellTypeEnum()); } @Test From 8b92803737df8db3f2611a9a79e343cb6d44bae6 Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Sun, 19 Nov 2017 15:26:24 +0200 Subject: [PATCH 281/702] add tests for excel file export. --- .../excel/AbstractExcelSheetTest.java | 66 ++++ .../excel/ExcelFieldServiceTest.java | 121 ++++++ .../excel/ExcelFileDefaultTest.java | 345 ++++++++++++++++++ .../excel/info/ClassFieldsDefaultTest.java | 61 ++++ .../info/ClassFieldsExcelExportTest.java | 75 ++++ 5 files changed, 668 insertions(+) create mode 100644 persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java create mode 100644 persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java create mode 100644 persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java create mode 100644 persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java create mode 100644 persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java new file mode 100644 index 000000000..5dcd87fd0 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/AbstractExcelSheetTest.java @@ -0,0 +1,66 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * @author idobre + * @since 15/11/2017 + */ +public class AbstractExcelSheetTest { + private class MockExcelSheet extends AbstractExcelSheet { + public MockExcelSheet(Workbook workbook) { + super(workbook); + } + + @Override + public void writeRow(Class clazz, Object object, Row row) { + + } + + @Override + public void writeSheet(Class clazz, List objects) { + + } + + @Override + public int writeSheetGetLink(Class clazz, List objects) { + return 0; + } + + @Override + public String getExcelSheetName() { + return null; + } + + @Override + public void emptySheet() { + + } + } + + @Test + public void writeCell() throws Exception { + final Workbook workbook = new XSSFWorkbook(); + final ExcelSheet excelSheet = new MockExcelSheet(workbook); + final Sheet sheet = workbook.createSheet("sheet"); + final Row row = sheet.createRow(0); + + excelSheet.writeCell(null, row, 0); + excelSheet.writeCell(Boolean.TRUE, row, 1); + excelSheet.writeCell("text", row, 2); + excelSheet.writeCell(1, row, 3); + + Assert.assertEquals(CellType.BLANK, row.getCell(0).getCellTypeEnum()); + Assert.assertEquals("Yes", row.getCell(1).getStringCellValue()); + Assert.assertEquals(CellType.STRING, row.getCell(2).getCellTypeEnum()); + Assert.assertEquals(CellType.NUMERIC, row.getCell(3).getCellTypeEnum()); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java new file mode 100644 index 000000000..1cd4c5232 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFieldServiceTest.java @@ -0,0 +1,121 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.devgateway.toolkit.persistence.excel.info.ClassFields; +import org.devgateway.toolkit.persistence.excel.info.ClassFieldsDefault; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.Persistable; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ExcelFieldServiceTest { + private class TestClass implements Persistable { + private static final long serialVersionUID = 1L; + + private Long id; + + private List label; + + @ExcelExport(justExport = true) + private OtherClass otherClass1; + + @ExcelExport(separateSheet = true) + private OtherClass otherClass2; + + public TestClass(final Long id) { + this.id = id; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + } + + private class OtherClass { + private Boolean valid; + + @ExcelExport(name = "money") + private Integer amount; + } + + @Test + public void getFieldType() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check basic field type", FieldType.basic, ExcelFieldService.getFieldType(firstField)); + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check type class for a List", FieldType.basic, ExcelFieldService.getFieldType(secondField)); + + final Field thirdField = fields.next(); // get third element + Assert.assertEquals("Check basic field type for Object", FieldType.basic, ExcelFieldService.getFieldType(thirdField)); + + final Field fourthField = fields.next(); // get fourth element + Assert.assertEquals("Check objectSeparateSheet field type Object", FieldType.objectSeparateSheet, ExcelFieldService.getFieldType(fourthField)); + } + + @Test + public void getFieldClass() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check basic field class", Long.class, ExcelFieldService.getFieldClass(firstField)); + + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check field class for a List", String.class, ExcelFieldService.getFieldClass(secondField)); + } + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"otherClass1", "otherClass2"}; + + final Iterator fields = ExcelFieldService.getFields(TestClass.class); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check get fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getObjectID() throws Exception { + final OtherClass otherClass = new OtherClass(); + Assert.assertEquals("Check object ID", Long.valueOf(-1), ExcelFieldService.getObjectID(otherClass)); + + final TestClass testclass = new TestClass((long) 10); + Assert.assertEquals("Check object ID", Long.valueOf(10), ExcelFieldService.getObjectID(testclass)); + } + + @Test + public void getFieldName() throws Exception { + final ClassFields classFields = new ClassFieldsDefault(OtherClass.class); + final Iterator fields = classFields.getFields(); + + final Field firstField = fields.next(); // get first element + Assert.assertEquals("Check field name", "valid", ExcelFieldService.getFieldName(firstField)); + + final Field secondField = fields.next(); // get second element + Assert.assertEquals("Check field name", "money", ExcelFieldService.getFieldName(secondField)); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java new file mode 100644 index 000000000..66f05c3b0 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileDefaultTest.java @@ -0,0 +1,345 @@ +package org.devgateway.toolkit.persistence.excel; + +import org.apache.log4j.Logger; +import org.apache.poi.ss.usermodel.Workbook; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.Persistable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author idobre + * @since 13/11/2017 + */ +public class ExcelFileDefaultTest { + private static final Logger logger = Logger.getLogger(ExcelFileDefaultTest.class); + + @Test + public void createWorkbook() throws Exception { + final ExcelFile excelFile = new ExcelFileDefault(createObjects()); + final Workbook workbook = excelFile.createWorkbook(); + + // try (FileOutputStream outputStream = new FileOutputStream("/Users/ionut/Downloads/file-export.xlsx")) { + // workbook.write(outputStream); + // } + + Assert.assertEquals("Number of sheets", 3, workbook.getNumberOfSheets()); + + Assert.assertNotNull("buyer sheet", workbook.getSheet("testbuyer")); + Assert.assertNotNull("contract sheet", workbook.getSheet("testcontract")); + Assert.assertNotNull("document sheet", workbook.getSheet("testdocument")); + + Assert.assertEquals("buyer name", "buyer 1", + workbook.getSheet("testbuyer").getRow(1).getCell(0).toString()); + Assert.assertEquals("buyer classification", "TP-1 - alii aliquam", + workbook.getSheet("testbuyer").getRow(2).getCell(8).toString()); + + Assert.assertEquals("contract parent", "testbuyer - 1", + workbook.getSheet("testcontract").getRow(1).getCell(0).toString()); + Assert.assertEquals("contract amount", 1000, + workbook.getSheet("testcontract").getRow(1).getCell(2).getNumericCellValue(), 0.0); + + + Assert.assertEquals("document number of rows", 2, workbook.getSheet("testdocument").getLastRowNum()); + + Assert.assertEquals("buyer address flatten", "Street 1 | Street 2", + workbook.getSheet("testbuyer").getRow(1).getCell(4).toString()); + } + + private List createObjects() { + final List objects = new ArrayList(); + + final List addresses1 = new ArrayList<>(); + final List addresses2 = new ArrayList<>(); + final TestAddress address1 = new TestAddress(Long.valueOf(1), "Street 1", "Romania"); + final TestAddress address2 = new TestAddress(Long.valueOf(2), "Street 2", "France"); + final TestAddress address3 = new TestAddress(Long.valueOf(1), "Street 3", "UK"); + final TestAddress address4 = new TestAddress(Long.valueOf(2), "Street 4", "Moldova"); + addresses1.add(address1); + addresses1.add(address2); + addresses2.add(address3); + addresses2.add(address4); + + final TestOrganization organization1 = new TestOrganization(Long.valueOf(1), "organization 1", addresses1); + final TestOrganization organization2 = new TestOrganization(Long.valueOf(2), "organization 2", addresses1); + + final List contracts1 = new ArrayList<>(); + final List contracts2 = new ArrayList<>(); + final TestContract contract1 = new TestContract(Long.valueOf(1), "ABC-100", 1000); + final TestContract contract2 = new TestContract(Long.valueOf(2), "ABC-101", 2000); + final TestContract contract3 = new TestContract(Long.valueOf(3), "ABC-102", 100); + final TestContract contract4 = new TestContract(Long.valueOf(4), "ABC-103", 7000); + contracts1.add(contract1); + contracts1.add(contract2); + contracts2.add(contract3); + contracts2.add(contract4); + + final TestDocument document1 = new TestDocument(Long.valueOf(1), "document 1", "PDF"); + final TestDocument document2 = new TestDocument(Long.valueOf(2), "document 2", "TXT"); + + final TestClassification classification1 = new TestClassification(Long.valueOf(1), "XZ-1", "dolor sit amet"); + final TestClassification classification2 = new TestClassification(Long.valueOf(2), "TP-1", "alii aliquam"); + + final TestBuyer buyer1 = new TestBuyer(Long.valueOf(1), "buyer 1", organization1, + addresses1, contracts1, document1, classification1); + final TestBuyer buyer2 = new TestBuyer(Long.valueOf(2), "buyer 2", organization2, + addresses2, contracts2, document2, classification2); + + objects.add(buyer1); + objects.add(buyer2); + + return objects; + } + + + /** ****************************************************************************** + * Test Entities + ******************************************************************************* */ + public class TestBuyer implements Persistable { + private final Long id; + + @ExcelExport + private final String name; + + @ExcelExport(name = "organization") + private final TestOrganization org; + + @ExcelExport + private final List addresses; + + @ExcelExport(separateSheet = true) + private final List contracts; + + @ExcelExport(separateSheet = true) + private final TestDocument document; + + @ExcelExport(justExport = true) + private final TestClassification classification; + + private TestBuyer(final Long id, final String name, final TestOrganization org, + final List addresses, final List contracts, + final TestDocument document, final TestClassification classification) { + this.id = id; + this.name = name; + this.org = org; + this.addresses = addresses; + this.contracts = contracts; + this.document = document; + this.classification = classification; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getName() { + return name; + } + + public TestOrganization getOrg() { + return org; + } + + public List getAddresses() { + return addresses; + } + + public List getContracts() { + return contracts; + } + + public TestDocument getDocument() { + return document; + } + + public TestClassification getClassification() { + return classification; + } + } + + public class TestOrganization implements Persistable { + private final Long id; + + @ExcelExport + private final List addresses; + + @ExcelExport + private final String name; + + private TestOrganization(final Long id, final String name, final List addresses) { + this.id = id; + this.name = name; + this.addresses = addresses; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public List getAddresses() { + return addresses; + } + + public String getName() { + return name; + } + } + + public class TestAddress implements Persistable { + private final Long id; + + @ExcelExport + private final String street; + + @ExcelExport + private final String country; + + public TestAddress(final Long id, final String street, final String country) { + this.id = id; + this.street = street; + this.country = country; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getStreet() { + return street; + } + + public String getCountry() { + return country; + } + } + + + public class TestContract implements Persistable { + private final Long id; + + @ExcelExport + private final String identifier; + + @ExcelExport + private final int amount; + + public TestContract(final Long id, final String identifier, final int amount) { + this.id = id; + this.identifier = identifier; + this.amount = amount; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getIdentifier() { + return identifier; + } + + public int getAmount() { + return amount; + } + } + + public class TestDocument implements Persistable { + private final Long id; + + @ExcelExport + private final String fileName; + + @ExcelExport + private final String type; + + public TestDocument(final Long id, final String fileName, final String type) { + this.id = id; + this.fileName = fileName; + this.type = type; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + public String getFileName() { + return fileName; + } + + public String getType() { + return type; + } + } + + public class TestClassification implements Persistable { + private final Long id; + + @ExcelExport + private final String schema; + + @ExcelExport + private final String description; + + public TestClassification(final Long id, final String schema, final String description) { + this.id = id; + this.schema = schema; + this.description = description; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return false; + } + + + @Override + public String toString() { + return schema + " - " + description; + } + + public String getSchema() { + return schema; + } + + public String getDescription() { + return description; + } + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java new file mode 100644 index 000000000..6c39ef7c1 --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsDefaultTest.java @@ -0,0 +1,61 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ClassFieldsDefaultTest { + private class TestClass { + private static final long serialVersionUID = 1L; + + private int id; + + private String label; + } + + private class TestClassImproved extends TestClass { + private static final long serialVersionUID = 1L; + + private Boolean valid; + } + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"id", "label"}; + + final ClassFields classFields = new ClassFieldsDefault(TestClass.class); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getInheritedFields() throws Exception { + final String[] expectedFields = {"id", "label", "valid"}; + + final ClassFields classFields = new ClassFieldsDefault(TestClassImproved.class, true); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared & inherited fields", expectedFields, actualFields.toArray()); + } +} diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java new file mode 100644 index 000000000..e7782005d --- /dev/null +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/info/ClassFieldsExcelExportTest.java @@ -0,0 +1,75 @@ +package org.devgateway.toolkit.persistence.excel.info; + +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author idobre + * @since 10/11/2017 + */ +public class ClassFieldsExcelExportTest { + private class TestClass { + private static final long serialVersionUID = 1L; + + @ExcelExport(name = "id") + private int id; + + @ExcelExport + private String label; + + private String description; + } + + private class TestClassImproved extends TestClass { + private static final long serialVersionUID = 1L; + + @ExcelExport + private Boolean valid; + + private Long amount; + } + + + @Test + public void getFields() throws Exception { + final String[] expectedFields = {"id", "label"}; + + final ClassFields classFields = new ClassFieldsExcelExport( + new ClassFieldsDefault(TestClass.class) + ); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared @ExcelExport fields", expectedFields, actualFields.toArray()); + } + + @Test + public void getInheritedFields() throws Exception { + final String[] expectedFields = {"id", "label", "valid"}; + + final ClassFields classFields = new ClassFieldsExcelExport( + new ClassFieldsDefault(TestClassImproved.class, true) + ); + final Iterator fields = classFields.getFields(); + + final List actualFields = new ArrayList<>(); + while (fields.hasNext()) { + final Field f = fields.next(); + actualFields.add(f.getName()); + } + + Assert.assertArrayEquals("Check declared & inherited @ExcelExport fields", + expectedFields, actualFields.toArray()); + } +} From b4eef3cedc5a2817d3cb5a300666bc38569896ed Mon Sep 17 00:00:00 2001 From: Ionut Dobre Date: Sun, 19 Nov 2017 15:54:16 +0200 Subject: [PATCH 282/702] added excel download button and some examples. --- forms/pom.xml | 18 +-- .../wicket/components/form/AJAXDownload.java | 45 ++++++ .../components/table/JpaFilterState.java | 2 + .../wicket/page/lists/AbstractListPage.html | 11 ++ .../wicket/page/lists/AbstractListPage.java | 135 +++++++++++++++++- .../wicket/page/lists/ListTestFormPage.java | 8 ++ .../forms/wicket/page/lists/ListUserPage.java | 7 + .../toolkit/persistence/dao/Person.java | 25 ++-- .../toolkit/persistence/dao/TestForm.java | 6 +- pom.xml | 1 - .../toolkit/web/spring/MvcConfig.java | 2 + 11 files changed, 235 insertions(+), 25 deletions(-) create mode 100644 forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java diff --git a/forms/pom.xml b/forms/pom.xml index b9be00a5d..4171afc2f 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -145,13 +145,13 @@ cglib 3.1 - + - com.google.javascript - closure-compiler - ${closure.compiler.version} - - + com.google.javascript + closure-compiler + ${closure.compiler.version} + + de.agilecoders.wicket.webjars @@ -246,7 +246,7 @@ org.apache.poi poi - ${pentaho.poi.version} + ${poi.version} @@ -357,11 +357,11 @@ org.hibernate - hibernate-entitymanager + hibernate-entitymanager org.hibernate - hibernate-envers + hibernate-envers diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java new file mode 100644 index 000000000..9f89f97af --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/AJAXDownload.java @@ -0,0 +1,45 @@ +package org.devgateway.toolkit.forms.wicket.components.form; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AbstractAjaxBehavior; +import org.apache.wicket.request.IRequestHandler; + +/** + * AJAX update and file download. + * + * https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow + */ +public abstract class AJAXDownload extends AbstractAjaxBehavior { + private boolean addAntiCache; + + public AJAXDownload() { + this(true); + } + + public AJAXDownload(final boolean addAntiCache) { + super(); + this.addAntiCache = addAntiCache; + } + + /** + * Call this method to initiate the download. + */ + public void initiate(final AjaxRequestTarget target) { + String url = getCallbackUrl().toString(); + + if (addAntiCache) { + url = url + (url.contains("?") ? "&" : "?"); + url = url + "antiCache=" + System.currentTimeMillis(); + } + + // the timeout is needed to let Wicket release the channel + target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);"); + } + + @Override + public void onRequest() { + getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(getHandler()); + } + + protected abstract IRequestHandler getHandler(); +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java index b7fc390ac..88d1311a6 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/JpaFilterState.java @@ -1,5 +1,6 @@ package org.devgateway.toolkit.forms.wicket.components.table; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.data.jpa.domain.Specification; import java.io.Serializable; @@ -11,6 +12,7 @@ public class JpaFilterState implements Serializable { private static final long serialVersionUID = 2241550275925712593L; + @JsonIgnore public Specification getSpecification() { return (root, query, cb) -> cb.and(); } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html index d8d134b58..07e2b2dd5 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.html @@ -2,6 +2,17 @@ +
+
+
+ +
+
+
+
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java index e01f6cc85..8893c1e2e 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java @@ -11,10 +11,17 @@ *******************************************************************************/ package org.devgateway.toolkit.forms.wicket.page.lists; +import java.io.BufferedOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.ladda.LaddaAjaxButton; import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; @@ -22,16 +29,22 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterToolbar; import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilteredColumn; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.request.IRequestCycle; +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; import org.apache.wicket.util.lang.Classes; import org.devgateway.toolkit.forms.WebConstants; import org.devgateway.toolkit.forms.exceptions.NullEditPageClassException; import org.devgateway.toolkit.forms.exceptions.NullJpaRepositoryException; +import org.devgateway.toolkit.forms.wicket.components.form.AJAXDownload; import org.devgateway.toolkit.forms.wicket.components.table.AjaxFallbackBootstrapDataTable; import org.devgateway.toolkit.forms.wicket.components.table.JpaFilterState; import org.devgateway.toolkit.forms.wicket.components.table.ResettingFilterForm; @@ -40,12 +53,17 @@ import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.forms.wicket.providers.SortableJpaRepositoryDataProvider; import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.devgateway.toolkit.persistence.excel.service.ExcelGeneratorService; import org.devgateway.toolkit.persistence.repository.BaseJpaRepository; import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapBookmarkablePageLink; import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons.Size; import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconType; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +import javax.servlet.http.HttpServletResponse; /** * @author mpostelnicu This class can be use to display a list of Categories @@ -55,11 +73,18 @@ public abstract class AbstractListPage extends BasePage { private static final long serialVersionUID = 1958350868666244087L; + protected SortableJpaRepositoryDataProvider dataProvider; + protected BootstrapBookmarkablePageLink editPageLink; + protected Form excelForm; + + @SpringBean + protected ExcelGeneratorService excelGeneratorService; + /** * Get a stub print button that does nothing - * + * * @param pageParameters * @return */ @@ -134,16 +159,22 @@ protected void onInitialize() { throw new NullEditPageClassException(); } - SortableJpaRepositoryDataProvider dataProvider = new SortableJpaRepositoryDataProvider<>(jpaRepository); + dataProvider = new SortableJpaRepositoryDataProvider<>(jpaRepository); dataProvider.setFilterState(newFilterState()); + // create the excel download form; by default this form is hidden and we should make it visible only to pages + // where we want to export entities to excel file + excelForm = new ExcelDownloadForm("excelForm"); + excelForm.setVisibilityAllowed(false); + add(excelForm); + // add the 'Edit' button columns.add(new AbstractColumn(new StringResourceModel("actionsColumn", this, null)) { private static final long serialVersionUID = -7447601118569862123L; @Override public void populateItem(final Item> cellItem, final String componentId, - final IModel model) { + final IModel model) { cellItem.add(getActionPanel(componentId, model)); } }); @@ -184,4 +215,102 @@ public JpaFilterState newFilterState() { protected String getClassName() { return Classes.simpleName(getClass()); } + + /** + * A wrapper form that is used to fire the excel download action + */ + public class ExcelDownloadForm extends Form { + public ExcelDownloadForm(final String id) { + super(id); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + final AJAXDownload download = new AJAXDownload() { + @Override + protected IRequestHandler getHandler() { + return new IRequestHandler() { + @Override + public void respond(final IRequestCycle requestCycle) { + final HttpServletResponse response = (HttpServletResponse) requestCycle + .getResponse().getContainerResponse(); + + try { + final int batchSize = 10000; + + final long count = excelGeneratorService.count( + jpaRepository, + dataProvider.getFilterState().getSpecification()); + + // if we need to export just one file then we don't create an archive + if (count <= batchSize) { + // set a maximum download of objects per excel file + final PageRequest pageRequest = new PageRequest(0, batchSize, + Sort.Direction.ASC, "id"); + + final byte[] bytes = excelGeneratorService.getExcelDownload( + jpaRepository, + dataProvider.getFilterState().getSpecification(), + pageRequest); + + response.setContentType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=excel-export.xlsx"); + response.getOutputStream().write(bytes); + } else { + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename=excel-export.zip"); + response.flushBuffer(); + final ZipOutputStream zout = new ZipOutputStream(new BufferedOutputStream( + response.getOutputStream())); + zout.setMethod(ZipOutputStream.DEFLATED); + zout.setLevel(Deflater.NO_COMPRESSION); + final int numberOfPages = (int) Math.ceil((double) count / batchSize); + for (int i = 0; i < numberOfPages; i++) { + final PageRequest pageRequest = new PageRequest(i, batchSize, + Sort.Direction.ASC, "id"); + final byte[] bytes = excelGeneratorService.getExcelDownload( + jpaRepository, + dataProvider.getFilterState().getSpecification(), + pageRequest); + final ZipEntry ze = new ZipEntry("excel-export-page " + (i + 1) + ".xlsx"); + zout.putNextEntry(ze); + zout.write(bytes, 0, bytes.length); + zout.closeEntry(); + response.flushBuffer(); + } + zout.close(); + response.flushBuffer(); + } + } catch (IOException e) { + logger.error(e); + } + + RequestCycle.get().scheduleRequestHandlerAfterCurrent(null); + } + + @Override + public void detach(final IRequestCycle requestCycle) { + // do nothing; + } + }; + } + }; + add(download); + + final LaddaAjaxButton excelButton = new LaddaAjaxButton("excelButton", + new Model<>("Excel Download"), + Buttons.Type.Warning) { + @Override + protected void onSubmit(final AjaxRequestTarget target, final Form form) { + // initiate the file download + download.initiate(target); + } + }; + excelButton.setIconType(FontAwesomeIconType.file_excel_o); + add(excelButton); + } + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java index ee6ff77c5..825dc9603 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java @@ -40,6 +40,14 @@ public ListTestFormPage(final PageParameters pageParameters) { columns.add(new TextFilteredBootstrapPropertyColumn<>(new Model<>("Text Field"), "textField", "textField")); } + @Override + protected void onInitialize() { + super.onInitialize(); + + // enable excel download + excelForm.setVisibilityAllowed(true); + } + @Override public JpaFilterState newFilterState() { return new TestFormFilterState(); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java index a1fb46f04..69b44b8cb 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListUserPage.java @@ -40,4 +40,11 @@ public ListUserPage(final PageParameters pageParameters) { columns.add(new PropertyColumn(new Model("Roles"), "roles", "roles")); } + @Override + protected void onInitialize() { + super.onInitialize(); + + // enable excel download + excelForm.setVisibilityAllowed(true); + } } \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java index 6811e0165..db14b887d 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Person.java @@ -11,25 +11,24 @@ *******************************************************************************/ package org.devgateway.toolkit.persistence.dao; -import java.io.Serializable; -import java.util.Collection; -import java.util.List; - -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.Transient; - +import com.fasterxml.jackson.annotation.JsonIgnore; import org.devgateway.toolkit.persistence.dao.categories.Group; import org.devgateway.toolkit.persistence.dao.categories.Role; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.envers.Audited; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import com.fasterxml.jackson.annotation.JsonIgnore; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.Transient; +import java.io.Serializable; +import java.util.Collection; +import java.util.List; @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @Entity @@ -37,12 +36,16 @@ public class Person extends AbstractAuditableEntity implements Serializable, UserDetails { private static final long serialVersionUID = 109780377848343674L; + @ExcelExport private String username; + @ExcelExport private String firstName; + @ExcelExport private String lastName; + @ExcelExport private String email; @JsonIgnore diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java index f6aeb1f0a..a1fda847d 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java @@ -10,7 +10,7 @@ * Development Gateway - initial API and implementation *******************************************************************************/ /** - * + * */ package org.devgateway.toolkit.persistence.dao; @@ -29,6 +29,7 @@ import org.devgateway.toolkit.persistence.dao.categories.Group; import org.devgateway.toolkit.persistence.dao.categories.Role; +import org.devgateway.toolkit.persistence.excel.annotation.ExcelExport; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.envers.Audited; @@ -44,14 +45,17 @@ public class TestForm extends AbstractAuditableEntity implements Serializable { private static final long serialVersionUID = 1L; + @ExcelExport private String textField; + @ExcelExport @Column(length = DBConstants.MAX_DEFAULT_TEXT_LENGTH) private String textArea; @Column(length = DBConstants.MAX_DEFAULT_TEXT_LENGTH) private String summernote; + @ExcelExport private Boolean checkbox; private Boolean checkboxPicker; diff --git a/pom.xml b/pom.xml index 401880149..915959deb 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,6 @@ 1.5.8.RELEASE 10.14.1.0 3.17 - 3.12 devgateway/toolkit 0.4.13 diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java index 611abf593..042453ff6 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import org.devgateway.toolkit.web.generators.GenericExcelKeyGenerator; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; @@ -35,6 +36,7 @@ public void addViewControllers(final ViewControllerRegistry registry) { @Bean(name = "genericExcelKeyGenerator") public KeyGenerator genericExcelKeyGenerator(final ObjectMapper objectMapper) { objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); return new GenericExcelKeyGenerator(objectMapper); } } From 940f378a740f4628742c489cca8e00d2e2c4033f Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 20 Nov 2017 10:33:05 +0200 Subject: [PATCH 283/702] OCE-395 added count for all release flags --- .../AbstractFlagReleaseSearchController.java | 21 +++++++++++++++++++ .../FlagI002ReleaseSearchController.java | 8 +++++++ .../FlagI004ReleaseSearchController.java | 8 +++++++ .../FlagI007ReleaseSearchController.java | 8 +++++++ .../FlagI019ReleaseSearchController.java | 8 +++++++ .../FlagI038ReleaseSearchController.java | 8 +++++++ .../FlagI077ReleaseSearchController.java | 8 +++++++ .../FlagI085ReleaseSearchController.java | 8 +++++++ .../FlagI171ReleaseSearchController.java | 8 +++++++ .../FlagI180ReleaseSearchController.java | 8 +++++++ 10 files changed, 93 insertions(+) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java index 6a5be31f3..5edcdf488 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java @@ -14,6 +14,8 @@ import javax.validation.Valid; import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; @@ -53,4 +55,23 @@ public List releaseFlagSearch(@ModelAttribute @Valid final YearFilterP List list = results.getMappedResults(); return list; } + + + @JsonView(Views.Internal.class) + public List releaseFlagCount(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + Aggregation agg = newAggregation( + match(where("flags.flaggedStats.0").exists(true).and(getFlagProperty()).is(true) + .andOperator(getYearDefaultFilterCriteria(filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), + unwind("flags.flaggedStats"), + match(where(getFlagProperty()).is(true).andOperator(getFlagTypeFilterCriteria(filter))), + group().count().as("count") + ); + + AggregationResults results = mongoTemplate.aggregate(agg, "release", + DBObject.class); + List list = results.getMappedResults(); + return list; + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI002ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI002ReleaseSearchController.java index 07521f89d..ec87dbac7 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI002ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI002ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i002") + @RequestMapping(value = "/api/flags/i002/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI004ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI004ReleaseSearchController.java index e0c83243b..50740e39c 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI004ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI004ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i004") + @RequestMapping(value = "/api/flags/i004/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI007ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI007ReleaseSearchController.java index fa04c9bdb..cd2636b45 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI007ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI007ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i007") + @RequestMapping(value = "/api/flags/i007/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI019ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI019ReleaseSearchController.java index 470720ec9..23076b65d 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI019ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI019ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i019") + @RequestMapping(value = "/api/flags/i019/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java index 0d6e69353..8d1e290c0 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i038") + @RequestMapping(value = "/api/flags/i038/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI077ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI077ReleaseSearchController.java index 35ea8b721..ad992787b 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI077ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI077ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i077") + @RequestMapping(value = "/api/flags/i077/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI085ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI085ReleaseSearchController.java index e2fa21fcd..8d3f40077 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI085ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI085ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i085") + @RequestMapping(value = "/api/flags/i085/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI171ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI171ReleaseSearchController.java index 38fbe13d5..169aa7103 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI171ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI171ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i171") + @RequestMapping(value = "/api/flags/i171/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI180ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI180ReleaseSearchController.java index 59afab51c..e71326924 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI180ReleaseSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI180ReleaseSearchController.java @@ -33,4 +33,12 @@ protected String getFlagProperty() { public List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { return super.releaseFlagSearch(filter); } + + @Override + @ApiOperation(value = "Counts releases by flag i180") + @RequestMapping(value = "/api/flags/i180/count", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + public List releaseFlagCount(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagCount(filter); + } } From 7438b8fdbf4b2aa1060ceb8680fbe1bf6b3e2600 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Mon, 20 Nov 2017 10:56:45 +0200 Subject: [PATCH 284/702] OCE-395 added count for corruption overview page --- ...rruptionRiskDashboardTablesController.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java index 197b4eb52..a565801a1 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CorruptionRiskDashboardTablesController.java @@ -13,6 +13,8 @@ import com.mongodb.DBObject; import io.swagger.annotations.ApiOperation; +import java.util.List; +import javax.validation.Valid; import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.cache.annotation.CacheConfig; @@ -26,9 +28,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; -import java.util.List; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; @@ -77,4 +78,25 @@ public List corruptionRiskOverviewTable( return list; } + @ApiOperation(value = "Counts data to show in the table on corruption risk overview page.") + @RequestMapping(value = "/api/corruptionRiskOverviewTable/count", + method = {RequestMethod.POST, RequestMethod.GET}, + produces = "application/json") + public List corruptionRiskOverviewTableCount( + @ModelAttribute @Valid final YearFilterPagingRequest filter) { + + Aggregation agg = newAggregation( + match(where("flags.flaggedStats.0").exists(true) + .andOperator(getYearDefaultFilterCriteria(filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE))), + unwind("flags.flaggedStats"), + group().count().as("count") + ); + + AggregationResults results = mongoTemplate.aggregate(agg, "release", + DBObject.class); + List list = results.getMappedResults(); + return list; + } + } \ No newline at end of file From 59386413f0122ddcaa06b11b90f29a27949f479b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 20 Nov 2017 13:14:35 +0200 Subject: [PATCH 285/702] OCE-375 Only scrolling sidebar if content is fully scrolled up --- ui/oce/corruption-risk/sidebar.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/sidebar.jsx b/ui/oce/corruption-risk/sidebar.jsx index ec5f1f6ba..08cb698ff 100644 --- a/ui/oce/corruption-risk/sidebar.jsx +++ b/ui/oce/corruption-risk/sidebar.jsx @@ -17,12 +17,15 @@ class Sidebar extends translatable(React.PureComponent) { const scrollTarget = el.querySelector('div'); const offsetTop = el.getBoundingClientRect().top; - window.addEventListener('scroll', e => { - const diff = window.scrollY - this.prevScrollY; + window.addEventListener('wheel', e => { + const { deltaY: diff } = e; this.prevScrollY = window.scrollY; let margin = parseInt(scrollTarget.style.marginTop); if (isNaN(margin)) margin = 0; - margin -= diff; + if (diff > 0 || window.scrollY === 0) { + margin -= diff; + } + const newMargin = Math.min( 0, Math.max( From 814fc75a0663c3f7ea10b3c63a86bdf283768aae Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 20 Nov 2017 13:46:30 +0200 Subject: [PATCH 286/702] OCE-380 Updated clickable crosstab box design --- ui/oce/corruption-risk/clickable-crosstab.jsx | 8 ++----- ui/oce/corruption-risk/contracts/style.less | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/clickable-crosstab.jsx b/ui/oce/corruption-risk/clickable-crosstab.jsx index a04539c08..ebae173ee 100644 --- a/ui/oce/corruption-risk/clickable-crosstab.jsx +++ b/ui/oce/corruption-risk/clickable-crosstab.jsx @@ -57,19 +57,15 @@ class ClickableCrosstab extends Crosstab { return (
-
+
{this.t('crd:corruptionType:crosstab:popup:percents') .replace('$#$', percent.toFixed(2)) .replace('$#$', rowIndicatorName) .replace('$#$', indicatorName)}
-
-
-
-

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+
{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

{rowIndicatorName}: {rowIndicatorDescription}

-

{this.t('crd:corruptionType:crosstab:popup:and')}

{indicatorName}: {indicatorDescription}

diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index f247e599f..986299fca 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -74,6 +74,27 @@ h3 { margin-top: 70px; } + + div.crosstab-box { + background: #f0f0f0; + font-size: .9em; + .title { + color: #144361; + font-size: 1.3em; + } + h5 { + color: green; + font-weight: bold; + margin-bottom: 5px; + } + p { + color: #999999; + margin-bottom: 0; + strong { + color: #333; + } + } + } } .flag-icon { From dce711c3082cb718802579529caf295a51a182fe Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 20 Nov 2017 13:47:08 +0200 Subject: [PATCH 287/702] OCE-380 Updated crosstab title --- web/public/languages/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 80fff008a..cf9183c28 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -231,7 +231,7 @@ "crd:corruptionType:indicatorTile:percentEligibleFlagged": "% Eligible Procurements Flagged", "crd:corruptionType:indicatorTile:percentProcurementsEligible": "% Procurements Eligible", "crd:corruptionType:crosstab:popup:percents": "$#$% of procurements flagged for \"$#$\" are also flagged for \"$#$\"", - "crd:corruptionType:crosstab:popup:count": "$#$ Procurements flagged with both;", + "crd:corruptionType:crosstab:popup:count": "$#$ Procurements flagged with both:", "crd:corruptionType:crosstab:popup:and": "and", "crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged": "Procurements Flagged", "crd:indicatorPage:individualIndicatorChart:popup:eligibleProcurements": "Eligible Procurements", From 8fa7ea32ec599751884824f194dc6573de9ebe32 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 20 Nov 2017 16:53:18 +0200 Subject: [PATCH 288/702] OCE-375 Reduced the sidebar to two columns --- ui/oce/corruption-risk/index.jsx | 2 +- ui/oce/corruption-risk/sidebar.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 96be3694b..84e19fe50 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -339,7 +339,7 @@ class CorruptionRiskDashboard extends React.Component { monthly={monthly} months={months} /> -
+
{this.getPage()}
diff --git a/ui/oce/corruption-risk/sidebar.jsx b/ui/oce/corruption-risk/sidebar.jsx index 08cb698ff..89ff4a3da 100644 --- a/ui/oce/corruption-risk/sidebar.jsx +++ b/ui/oce/corruption-risk/sidebar.jsx @@ -42,7 +42,7 @@ class Sidebar extends translatable(React.PureComponent) { data, requestNewData, route } = this.props; return ( -
-
+
) } @@ -141,25 +137,27 @@ class TopFlaggedContracts extends ProcurementsTable{ TopFlaggedContracts.endpoint = 'corruptionRiskOverviewTable?pageSize=10'; -class OverviewPage extends CRDPage{ - constructor(...args){ +class OverviewPage extends CRDPage { + constructor(...args) { super(...args); this.state = { corruptionType: null, topFlaggedContracts: null - } + }; } - render(){ - const {corruptionType, topFlaggedContracts} = this.state; - const { filters, translations, years, monthly, months, indicatorTypesMapping, styling, width, navigate } = this.props; + render() { + const { corruptionType, topFlaggedContracts } = this.state; + const { filters, translations, years, monthly, months, indicatorTypesMapping, styling, width, + navigate } = this.props; return (

{this.t('crd:overview:overTimeChart:title')}

this.setState({corruptionType})} + requestNewData={(_, newCorruptionType) => + this.setState({ corruptionType: newCorruptionType })} translations={translations} data={corruptionType} years={years} @@ -180,12 +178,13 @@ class OverviewPage extends CRDPage{ years={years} monthly={monthly} months={months} - requestNewData={(_, topFlaggedContracts) => this.setState({topFlaggedContracts})} + requestNewData={(_, newTopFlaggedContracts) => + this.setState({ topFlaggedContracts: newTopFlaggedContracts })} navigate={navigate} />
- ) + ); } } From 8fe88864d85959e9e361809fbaeef5c41b16f7c8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 19:09:10 +0200 Subject: [PATCH 315/702] OCE-395 More linting --- ui/oce/corruption-risk/procurements-table.jsx | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index b59d22485..38afdf0e3 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -6,7 +6,7 @@ import { POPUP_HEIGHT } from './constants'; import { getAwardAmount, mkContractLink } from './tools'; // eslint-disable-next-line no-undef -class Popup extends translatable(React.Component){ +class Popup extends translatable(React.Component) { constructor(...args) { super(...args); this.state = { @@ -14,11 +14,11 @@ class Popup extends translatable(React.Component){ }; } - getPopup(){ - const {type, flagIds} = this.props; - const {popupTop} = this.state; + getPopup() { + const { type, flagIds } = this.props; + const { popupTop } = this.state; return ( -
+
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}
@@ -39,26 +39,26 @@ class Popup extends translatable(React.Component){ const el = ReactDOM.findDOMNode(this); this.setState({ showPopup: true, - popupTop: -(POPUP_HEIGHT / 2) + (el.offsetHeight / 4) + popupTop: -(POPUP_HEIGHT / 2) + (el.offsetHeight / 4), }); } - render(){ - const {flaggedStats} = this.props; - const {showPopup} = this.state; + render() { + const { flaggedStats } = this.props; + const { showPopup } = this.state; return (
this.showPopup()} onMouseLeave={() => this.setState({ showPopup: false })} > {flaggedStats.get('count')} {showPopup && this.getPopup()}
- ) + ); } } -class ProcurementsTable extends Table{ +class ProcurementsTable extends Table { renderPopup({ flaggedStats, flagType: type, flagIds }) { const { translations } = this.props; return ( @@ -107,9 +107,9 @@ class ProcurementsTable extends Table{ // needed for the popup: flaggedStats, flagType, - flagIds - } - }) + flagIds, + }; + }); return ( this.renderPopup(data)} + dataFormat={(_, popupData) => this.renderPopup(popupData)} columnClassName="hoverable popup-left" > {this.t('crd:procurementsTable:noOfFlags')} @@ -160,6 +160,4 @@ class ProcurementsTable extends Table{ } } - - export default ProcurementsTable; From dabf85f279cc4918cec91db95e1641690c61fdb1 Mon Sep 17 00:00:00 2001 From: Alexei Date: Tue, 5 Dec 2017 19:23:13 +0200 Subject: [PATCH 316/702] OCE-395 Linting visualization and deprecating endpoint(s) class properties --- ui/oce/visualization.es6 | 65 ++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/ui/oce/visualization.es6 b/ui/oce/visualization.es6 index 57e0603fa..c2c20699e 100644 --- a/ui/oce/visualization.es6 +++ b/ui/oce/visualization.es6 @@ -1,64 +1,71 @@ -import translatable from "./translatable"; -import Component from "./pure-render-component"; -import {callFunc} from "./tools"; -import {fromJS} from "immutable"; -import URI from "urijs"; +import URI from 'urijs'; +import { fromJS } from 'immutable'; +import translatable from './translatable'; +import Component from './pure-render-component'; +import { callFunc } from './tools'; + const API_ROOT = '/api'; -let fetchEP = url => fetch(url.clone().query(""), { +const fetchEP = url => fetch(url.clone().query(''), { method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/x-www-form-urlencoded', }, credentials: 'same-origin', - body: url.query() + body: url.query(), }).then(callFunc('json')); -class Visualization extends translatable(Component){ - constructor(...args){ +class Visualization extends translatable(Component) { + constructor(...args) { super(...args); this.state = this.state || {}; this.state.loading = true; } - buildUrl(ep){ - let {filters} = this.props; - return new URI(API_ROOT + '/' + ep).addSearch(filters.toJS()); + buildUrl(ep) { + const { filters } = this.props; + return new URI(`${API_ROOT}/${ep}`).addSearch(filters.toJS()); } - fetch(){ - let {endpoint, endpoints} = this.constructor; - let {requestNewData} = this.props; + fetch() { + const { endpoint, endpoints } = this.constructor; + const { requestNewData } = this.props; let promise = false; - if(endpoint) promise = fetchEP(this.buildUrl(endpoint)); - if(endpoints) promise = Promise.all(endpoints.map(this.buildUrl.bind(this)).map(fetchEP)); - if("function" == typeof this.getCustomEP){ + if (endpoint) { + console.warn('endpoint property is deprecated', endpoint); + promise = fetchEP(this.buildUrl(endpoint)); + } + if (endpoints) { + console.warn('endpoints property is deprecated', endpoints); + promise = Promise.all(endpoints.map(this.buildUrl.bind(this)).map(fetchEP)); + } + if (typeof this.getCustomEP === 'function') { const customEP = this.getCustomEP(); - if(Array.isArray(customEP)){ + if (Array.isArray(customEP)) { promise = Promise.all(customEP.map(this.buildUrl.bind(this)).map(fetchEP)); } else { promise = fetchEP(this.buildUrl(customEP)); } } - if(!promise) return; - this.setState({loading: true}); + if (!promise) return; + this.setState({ loading: true }); promise .then(this.transform.bind(this)) .then(fromJS) .then(data => requestNewData([], data)) - .then(() => this.setState({loading: false})); + .then(() => this.setState({ loading: false })); } - transform(data){return data;} + transform(data) { return data; } - getData(){return this.props.data} + getData() { return this.props.data; } - componentDidMount(){ + componentDidMount() { this.fetch(); } - componentDidUpdate(prevProps){ - if(this.props.filters != prevProps.filters) this.fetch(); + componentDidUpdate(prevProps) { + if (this.props.filters !== prevProps.filters) this.fetch(); } } @@ -67,7 +74,7 @@ Visualization.comparable = true; Visualization.propTypes = { filters: React.PropTypes.object.isRequired, data: React.PropTypes.object, - requestNewData: React.PropTypes.func.isRequired + requestNewData: React.PropTypes.func.isRequired, }; export default Visualization; From ab22fc893d8ae7d396c39b30b5d68fc0827cf8a9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 19:26:56 +0200 Subject: [PATCH 317/702] OCE-395 Passing EP as a prop --- ui/oce/corruption-risk/overview-page.jsx | 44 ++++++++++--------- ui/oce/corruption-risk/procurements-table.jsx | 5 +++ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index cbbe3849f..72e511673 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -2,10 +2,11 @@ import { range } from '../tools'; import CustomPopupChart from './custom-popup-chart'; import CRDPage from './page'; import { colorLuminance } from './tools'; +import ProcurementsTable from './procurements-table'; const TRACES = ['COLLUSION', 'FRAUD', 'RIGGING']; -class CorruptionType extends CustomPopupChart{ +class CorruptionType extends CustomPopupChart { groupData(data) { const grouped = {}; TRACES.forEach((trace) => { @@ -16,7 +17,7 @@ class CorruptionType extends CustomPopupChart{ data.forEach((datum) => { const type = datum.get('type'); let date; - if(monthly){ + if (monthly) { const month = datum.get('month'); date = this.t(`general:months:${month}`); } else { @@ -29,7 +30,7 @@ class CorruptionType extends CustomPopupChart{ return grouped; } - getData(){ + getData() { const data = super.getData(); if (!data) return []; const { styling, months, monthly, years } = this.props; @@ -49,7 +50,7 @@ class CorruptionType extends CustomPopupChart{ values = dates.map(year => (dataForType[year] ? dataForType[year].flaggedCount : 0)); } - if (dates.length === 1){ + if (dates.length === 1) { dates.unshift(''); dates.push(' '); values.unshift(0); @@ -64,7 +65,7 @@ class CorruptionType extends CustomPopupChart{ name: this.t(`crd:corruptionType:${type}:name`), fillcolor: styling.charts.traceColors[index], line: { - color: colorLuminance(styling.charts.traceColors[index], -.3) + color: colorLuminance(styling.charts.traceColors[index], -0.3), }, }; }); @@ -82,9 +83,9 @@ class CorruptionType extends CustomPopupChart{ xanchor: 'right', yanchor: 'bottom', x: 1, - y: 1 - } - } + y: 1, + }, + }; } getPopup() { @@ -93,16 +94,16 @@ class CorruptionType extends CustomPopupChart{ const corruptionType = TRACES[traceIndex]; const { indicatorTypesMapping } = this.props; const data = this.groupData(super.getData()); - if(!data[corruptionType]) return null; + if (!data[corruptionType]) return null; const dataForPoint = data[corruptionType][year]; - if(!dataForPoint) return null; + if (!dataForPoint) return null; const indicatorCount = Object.keys(indicatorTypesMapping).filter(indicatorId => indicatorTypesMapping[indicatorId].types.indexOf(dataForPoint.type) > -1 ).length; return ( -
+
{year} @@ -121,28 +122,29 @@ class CorruptionType extends CustomPopupChart{
- ) + ); } } CorruptionType.endpoint = 'percentTotalProjectsFlaggedByYear'; -import ProcurementsTable from "./procurements-table"; - -class TopFlaggedContracts extends ProcurementsTable{ - getClassName(){ - return "table-top-flagged-contracts"; +class TopFlaggedContracts extends ProcurementsTable { + render() { + return ( + + ); } } -TopFlaggedContracts.endpoint = 'corruptionRiskOverviewTable?pageSize=10'; - class OverviewPage extends CRDPage { constructor(...args) { super(...args); this.state = { corruptionType: null, - topFlaggedContracts: null + topFlaggedContracts: null, }; } @@ -166,7 +168,7 @@ class OverviewPage extends CRDPage { styling={styling} indicatorTypesMapping={indicatorTypesMapping} width={width - 20} - margin={{t: 0, b: 40, r: 40, pad: 20}} + margin={{ t: 0, b: 40, r: 40, pad: 20 }} />
diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 38afdf0e3..2c83c70f6 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -59,6 +59,11 @@ class Popup extends translatable(React.Component) { } class ProcurementsTable extends Table { + getCustomEP() { + const { dataEP } = this.props; + return dataEP; + } + renderPopup({ flaggedStats, flagType: type, flagIds }) { const { translations } = this.props; return ( From c818025ee0de99c2a2c673326914a81b18e7241f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 19:49:23 +0200 Subject: [PATCH 318/702] OCE-395 Extracted common logic into PaginatedClass --- ui/oce/corruption-risk/contracts/index.jsx | 51 +++++----------------- ui/oce/corruption-risk/paginated-table.jsx | 47 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 39 deletions(-) create mode 100644 ui/oce/corruption-risk/paginated-table.jsx diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index ed1043a1f..52d251c74 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -1,63 +1,35 @@ import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; import { List } from 'immutable'; -import URI from 'urijs'; // eslint-disable-next-line no-unused-vars import rbtStyles from 'react-bootstrap-table/dist/react-bootstrap-table-all.min.css'; import CRDPage from '../page'; import Visualization from '../../visualization'; import TopSearch from './top-search'; import { getAwardAmount, mkContractLink } from '../tools'; +import PaginatedTable from '../paginated-table'; -class CList extends Visualization { - constructor(...args) { - super(...args); - this.state = { - pageSize: 20, - page: 1, - }; - } - +class CList extends PaginatedTable { getCustomEP() { - const { pageSize, page } = this.state; const { searchQuery } = this.props; - - let contracts = new URI('flaggedRelease/all') - .addSearch('pageSize', pageSize) - .addSearch('pageNumber', page - 1); - - let count = new URI('ocds/release/count'); - - if (searchQuery) { - contracts = contracts.addSearch('text', searchQuery); - count = count.addSearch('text', searchQuery); - } - - return [ - contracts, - count, - ]; - } - - transform([contracts, count]) { - return { - contracts, - count, - }; + const eps = super.getCustomEP(); + return searchQuery ? + eps.map(ep => ep.addSearch('text', searchQuery)) : + eps; } componentDidUpdate(prevProps, prevState) { const propsChanged = ['filters', 'searchQuery'].some(key => this.props[key] !== prevProps[key]); - const stateChanged = ['pageSize', 'page'].some(key => this.state[key] !== prevState[key]); - if (propsChanged || stateChanged) { + if (propsChanged) { this.fetch(); + } else { + super.componentDidUpdate(prevProps, prevState); } } - render() { const { data, navigate } = this.props; - const contracts = data.get('contracts', List()); + const contracts = data.get('data', List()); const count = data.get('count', 0); const { pageSize, page } = this.state; @@ -171,6 +143,8 @@ export default class Contracts extends CRDPage { } this.setState({ list: newData })} @@ -178,7 +152,6 @@ export default class Contracts extends CRDPage { translations={translations} searchQuery={searchQuery} /> -
); } diff --git a/ui/oce/corruption-risk/paginated-table.jsx b/ui/oce/corruption-risk/paginated-table.jsx new file mode 100644 index 000000000..02803c9a1 --- /dev/null +++ b/ui/oce/corruption-risk/paginated-table.jsx @@ -0,0 +1,47 @@ +import URI from 'urijs'; +import Visualization from '../visualization'; + +class PaginatedTable extends Visualization { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.pageSize = 20; + this.state.page = 1; + } + + getCustomEP() { + const { pageSize, page } = this.state; + const { dataEP, countEP } = this.props; + + let data = new URI(dataEP) + .addSearch('pageSize', pageSize) + .addSearch('pageNumber', page - 1); + + let count = new URI(countEP); + + return [ + data, + count, + ]; + } + + transform([data, count]) { + return { + data, + count, + }; + } + + componentDidUpdate(_, prevState) { + const stateChanged = ['pageSize', 'page'].some(key => this.state[key] !== prevState[key]); + if (stateChanged) { + this.fetch(); + } + } + + render() { + throw 'Abstract!'; + } +} + +export default PaginatedTable; From a04f9a7dea1ac9c95514e900410d8b99d4f33b4d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 19:58:02 +0200 Subject: [PATCH 319/702] OCE-395 Pagination for overview table --- ui/oce/corruption-risk/overview-page.jsx | 15 ++-------- ui/oce/corruption-risk/procurements-table.jsx | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 72e511673..cd5fec393 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -128,17 +128,6 @@ class CorruptionType extends CustomPopupChart { CorruptionType.endpoint = 'percentTotalProjectsFlaggedByYear'; -class TopFlaggedContracts extends ProcurementsTable { - render() { - return ( - - ); - } -} - class OverviewPage extends CRDPage { constructor(...args) { super(...args); @@ -173,7 +162,9 @@ class OverviewPage extends CRDPage {

{this.t('crd:overview:topFlagged:title')}

- { + const contracts = data.get('data', List()); + const count = data.get('count', 0); + + const { pageSize, page } = this.state; + + const jsData = contracts.map((contract) => { const tenderAmount = contract.getIn(['tender', 'value', 'amount'], 'N/A') + ' ' + contract.getIn(['tender', 'value', 'currency'], ''); @@ -121,6 +123,19 @@ class ProcurementsTable extends Table { data={jsData} striped bordered={false} + pagination + remote + fetchInfo={{ + dataTotalSize: count, + }} + options={{ + page, + onPageChange: newPage => this.setState({ page: newPage }), + sizePerPage: pageSize, + sizePerPageList: [20, 50, 100, 200].map(value => ({ text: value, value })), + onSizePerPageList: newPageSize => this.setState({ pageSize: newPageSize }), + paginationPosition: 'both', + }} > {this.t('crd:procurementsTable:status')} From a94a036a6824fd360df2f70e59c6dc6c5373dcc8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 20:17:21 +0200 Subject: [PATCH 320/702] OCE-395 Fixed count for procurements table --- ui/oce/corruption-risk/procurements-table.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 37cdba946..2b291f8a9 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -79,7 +79,7 @@ class ProcurementsTable extends PaginatedTable { if (!data) return null; const contracts = data.get('data', List()); - const count = data.get('count', 0); + const count = data.getIn(['count', 0, 'count'], 0); const { pageSize, page } = this.state; From 99c6170b900fe06c6cb23143c76415762bb66ea1 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 20:21:06 +0200 Subject: [PATCH 321/702] OCE-395 Linted individual indicator page --- .../corruption-risk/individual-indicator.jsx | 125 +++++++++--------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx index 1569c0605..e79a9ce2c 100644 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ b/ui/oce/corruption-risk/individual-indicator.jsx @@ -1,35 +1,33 @@ -import CustomPopupChart from "./custom-popup-chart"; -import {Map} from "immutable"; -import {pluckImm} from "../tools"; -import Table from "../visualizations/tables/index"; -import translatable from "../translatable"; -import CRDPage from "./page"; -import {colorLuminance} from "./tools"; +import CustomPopupChart from './custom-popup-chart'; +import { pluckImm } from '../tools'; +import translatable from '../translatable'; +import CRDPage from './page'; +import { colorLuminance } from './tools'; -class IndividualIndicatorChart extends CustomPopupChart{ - getCustomEP(){ - const {indicator} = this.props; +class IndividualIndicatorChart extends CustomPopupChart { + getCustomEP() { + const { indicator } = this.props; return `flags/${indicator}/stats`; } - getData(){ + getData() { const data = super.getData(); - const {traceColors} = this.props.styling.charts; - if(!data) return []; - const {monthly} = this.props; - let dates = monthly ? - data.map(datum => { - const month = datum.get('month'); - return this.t(`general:months:${month}`); - }).toJS() : - data.map(pluckImm('year')).toJS(); + const { traceColors } = this.props.styling.charts; + if (!data) return []; + const { monthly } = this.props; + const dates = monthly ? + data.map((datum) => { + const month = datum.get('month'); + return this.t(`general:months:${month}`); + }).toJS() : + data.map(pluckImm('year')).toJS(); - let totalTrueValues = data.map(pluckImm('totalTrue', 0)).toJS(); - let totalPrecondMetValues = data.map(pluckImm('totalPrecondMet', 0)).toJS(); + const totalTrueValues = data.map(pluckImm('totalTrue', 0)).toJS(); + const totalPrecondMetValues = data.map(pluckImm('totalPrecondMet', 0)).toJS(); - if(dates.length == 1){ - dates.unshift(""); - dates.push(" "); + if (dates.length === 1) { + dates.unshift(''); + dates.push(' '); totalTrueValues.unshift(0); totalTrueValues.push(0); totalPrecondMetValues.unshift(0); @@ -45,7 +43,7 @@ class IndividualIndicatorChart extends CustomPopupChart{ hoverinfo: 'none', fillcolor: traceColors[0], line: { - color: colorLuminance(traceColors[0], -.3), + color: colorLuminance(traceColors[0], -0.3), }, }, { x: dates, @@ -56,52 +54,52 @@ class IndividualIndicatorChart extends CustomPopupChart{ hoverinfo: 'none', fillcolor: traceColors[1], line: { - color: colorLuminance(traceColors[1], -.3) - } + color: colorLuminance(traceColors[1], -0.3), + }, }]; } - getLayout(){ + getLayout() { return { legend: { orientation: 'h', xanchor: 'right', yanchor: 'bottom', x: 1, - y: 1 + y: 1, }, hovermode: 'closest', xaxis: { type: 'category', - showgrid: false + showgrid: false, }, - yaxis: {} - } + yaxis: {}, + }; } - getPopup(){ - const {indicator, monthly} = this.props; - const {popup} = this.state; - const {year} = popup; + getPopup() { + const { monthly } = this.props; + const { popup } = this.state; + const { year } = popup; const data = super.getData(); - if(!data) return null; + if (!data) return null; let datum; - if(monthly){ - datum = data.find(datum => { + if (monthly) { + datum = data.find((datum) => { const month = datum.get('month'); - return year == this.t(`general:months:${month}`); - }) + return year === this.t(`general:months:${month}`); + }); } else { - datum = data.find(datum => datum.get('year') == year); + datum = data.find(datum => datum.get('year') === year); } return ( -
+
{year}
-
+
{this.t('crd:indicatorPage:individualIndicatorChart:popup:procurementsFlagged')}
{datum.get('totalTrue')}
@@ -112,16 +110,16 @@ class IndividualIndicatorChart extends CustomPopupChart{
{this.t('crd:indicatorPage:individualIndicatorChart:popup:percentEligible')}
{datum.get('percentPrecondMet').toFixed(2)} %
-
+
- ) + ); } } import ProcurementsTable from "./procurements-table"; -class ProjectTable extends ProcurementsTable{ - getCustomEP(){ +class ProjectTable extends ProcurementsTable { + getCustomEP() { const {corruptionType, indicator} = this.props; return `flags/${indicator}/releases?pageSize=10&flagType=${corruptionType}`; } @@ -131,37 +129,36 @@ class ProjectTable extends ProcurementsTable{ } } -class IndividualIndicatorPage extends translatable(CRDPage){ - constructor(...args){ +class IndividualIndicatorPage extends translatable(CRDPage) { + constructor(...args) { super(...args); - this.state = { - } + this.state = {}; } - render(){ - const {chart, table} = this.state; + render() { + const { chart, table } = this.state; const { corruptionType, indicator, translations, filters, years, monthly, months, width - , styling, navigate } = this.props; + , styling, navigate } = this.props; return (

{this.t(`crd:indicators:${indicator}:name`)}

- {this.t("crd:indicators:general:indicator")} + {this.t('crd:indicators:general:indicator')}   {this.t(`crd:indicators:${indicator}:indicator`)}

- {this.t("crd:indicators:general:eligibility")} + {this.t('crd:indicators:general:eligibility')}   {this.t(`crd:indicators:${indicator}:eligibility`)}

- {this.t("crd:indicators:general:thresholds")} + {this.t('crd:indicators:general:thresholds')}   {this.t(`crd:indicators:${indicator}:thresholds`)}

- {this.t("crd:indicators:general:description")} + {this.t('crd:indicators:general:description')}   {this.t(`crd:indicators:${indicator}:description`)}

@@ -176,11 +173,11 @@ class IndividualIndicatorPage extends translatable(CRDPage){ years={years} monthly={monthly} months={months} - requestNewData={(_, data) => this.setState({chart: data})} + requestNewData={(_, data) => this.setState({ chart: data })} data={chart} width={width - 20} styling={styling} - margin={{t: 0, b: 80, r: 40, pad: 40}} + margin={{ t: 0, b: 80, r: 40, pad: 40 }} />
@@ -190,7 +187,7 @@ class IndividualIndicatorPage extends translatable(CRDPage){ this.setState({table: data})} + requestNewData={(_, data) => this.setState({ table: data })} data={table} translations={translations} filters={filters} @@ -201,7 +198,7 @@ class IndividualIndicatorPage extends translatable(CRDPage){ />
- ) + ); } } From 32e737ca25ca55820991d146301674db80ec8dbf Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Tue, 5 Dec 2017 20:28:47 +0200 Subject: [PATCH 322/702] OCE-395 Pagination for individuali indicator tables --- .../corruption-risk/individual-indicator.jsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/ui/oce/corruption-risk/individual-indicator.jsx b/ui/oce/corruption-risk/individual-indicator.jsx index e79a9ce2c..6122968fd 100644 --- a/ui/oce/corruption-risk/individual-indicator.jsx +++ b/ui/oce/corruption-risk/individual-indicator.jsx @@ -3,6 +3,7 @@ import { pluckImm } from '../tools'; import translatable from '../translatable'; import CRDPage from './page'; import { colorLuminance } from './tools'; +import ProcurementsTable from './procurements-table'; class IndividualIndicatorChart extends CustomPopupChart { getCustomEP() { @@ -116,19 +117,6 @@ class IndividualIndicatorChart extends CustomPopupChart { } } -import ProcurementsTable from "./procurements-table"; - -class ProjectTable extends ProcurementsTable { - getCustomEP() { - const {corruptionType, indicator} = this.props; - return `flags/${indicator}/releases?pageSize=10&flagType=${corruptionType}`; - } - - getClassName(){ - return "table-project-table"; - } -} - class IndividualIndicatorPage extends translatable(CRDPage) { constructor(...args) { super(...args); @@ -184,7 +172,9 @@ class IndividualIndicatorPage extends translatable(CRDPage) {

{this.t('crd:indicatorPage:projectTable:title').replace('$#$', this.t(`crd:indicators:${indicator}:name`))}

- this.setState({ table: data })} From 7522ee3ddd045c252f682c9bedb93a0106380778 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 6 Dec 2017 15:24:24 +0200 Subject: [PATCH 323/702] OCE-417 Increased Filters bar z-index --- ui/oce/corruption-risk/style.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index e48148b61..7099f4691 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -64,7 +64,7 @@ left: 0; right: 0; position: fixed; - z-index: 2; + z-index: 10; .title{ text-transform: uppercase; float: left; From 07a9092a6d78929173aa3a08938bf88381c04b9d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 6 Dec 2017 15:35:57 +0200 Subject: [PATCH 324/702] OCE-418 Formatting sidebar count --- ui/oce/corruption-risk/total-flags.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/total-flags.jsx b/ui/oce/corruption-risk/total-flags.jsx index 1df79d8e5..8054aefc3 100644 --- a/ui/oce/corruption-risk/total-flags.jsx +++ b/ui/oce/corruption-risk/total-flags.jsx @@ -58,7 +58,7 @@ class Counter extends backendYearFilterable(Visualization) { return (
- {this.getCount()} + {this.getCount().toLocaleString()}
{this.getTitle()} From 45dda2d23efdc42b6ba178051044e7a8e9923530 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 6 Dec 2017 15:36:13 +0200 Subject: [PATCH 325/702] OCE-418 Scaling font size for sidebar text --- ui/oce/corruption-risk/style.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index e48148b61..1d6ca2892 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -205,12 +205,12 @@ border-bottom: 1px solid #e7ebef; } .text{ - font-size: 1.3em; + font-size: 1vw; padding-top: .5em; } .count{ float: right; - font-size: 2em; + font-size: 1.5vw; font-weight: bold; color: #144361; } From 134f5138737b228549b36d52a2c7d9511cb74069 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 6 Dec 2017 21:39:01 +0200 Subject: [PATCH 326/702] OCE-405 Donuts title --- ui/oce/corruption-risk/contracts/single/index.jsx | 1 + web/public/languages/en_US.json | 1 + web/public/languages/es_ES.json | 1 + 3 files changed, 3 insertions(+) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index fb1e2cef2..fb4e190b4 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -259,6 +259,7 @@ export default class Contract extends CRDPage { />

+ {this.t('crd:contracts:contractStatistics')}

Date: Wed, 6 Dec 2017 22:18:18 +0200 Subject: [PATCH 327/702] OCE-405 Spacing for donuts title --- .../contracts/single/index.jsx | 2 +- ui/oce/corruption-risk/contracts/style.less | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index fb4e190b4..c6c9f8a2c 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -257,7 +257,7 @@ export default class Contract extends CRDPage { requestNewData={(_, contract) => this.setState({contract})} translations={translations} /> -
+

{this.t('crd:contracts:contractStatistics')}

diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 47cedded9..5bb42e1c1 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -107,7 +107,7 @@ } section.info { - margin-bottom: 60px; + margin-bottom: 40px; &>.row{ margin-left: 0; @@ -121,5 +121,22 @@ font-size: 2em; } } -} + section.contract-statistics { + h2 { + margin-bottom: 40px; + @media screen and (max-width: 1500px) { + margin-bottom: 20px; + } + + @media screen and (max-width: 1400px) { + margin-bottom: 10px; + } + + @media screen and (max-width: 1300px) { + margin-bottom: 0; + } + + } + } +} From 96d0b8a409c482c369fbf1be69e3be9b98796d65 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 00:41:42 +0200 Subject: [PATCH 328/702] OCE-405 Linting --- .../contracts/single/index.jsx | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index c6c9f8a2c..51bf19824 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -2,18 +2,18 @@ import { Map, List, Set } from 'immutable'; import CRDPage from '../../page'; import Visualization from '../../../visualization'; import translatable from '../../../translatable'; -import styles from '../style.less'; import TopSearch from '../top-search'; import NrOfBidders from './donuts/nr-of-bidders'; import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; import Crosstab from '../../clickable-crosstab'; -import { CORRUPTION_TYPES } from '../../constants'; +// eslint-disable-next-line no-unused-vars +import styles from '../style.less'; const EMPTY_SET = Set(); class Info extends translatable(Visualization) { - getCustomEP(){ + getCustomEP() { const { id } = this.props; return `flaggedRelease/ocid/${id}`; } @@ -22,11 +22,10 @@ class Info extends translatable(Visualization) { const { data, supplier } = this.props; const title = data.getIn(['tender', 'title']); - const suppliers = data.get('awards', List()).flatMap(award => award.get('suppliers')); const startDate = data.getIn(['tender', 'tenderPeriod', 'startDate']); const endDate = data.getIn(['tender', 'tenderPeriod', 'endDate']); - const award = data.get('awards', List()).find(award => - award.get('status') != 'unsuccessful') || Map(); + const award = data.get('awards', List()).find(a => + a.get('status') !== 'unsuccessful') || Map(); const flagCount = data.get('flags', List()).filter(flag => flag.get && flag.get('value')).count(); return ( @@ -41,7 +40,7 @@ class Info extends translatable(Visualization) {
{data.get('tag', []).join(', ')}
- Flag icon + Flag icon   {flagCount}  {flagCount === 1 ? 'Flag' : 'Flags'} @@ -77,7 +76,6 @@ class Info extends translatable(Visualization) { this.t('general:undefined') } - this.setState({showAllSuppliers: true})}> @@ -142,24 +140,24 @@ class Info extends translatable(Visualization) { } export default class Contract extends CRDPage { - constructor(...args){ + constructor(...args) { super(...args); this.state = { contract: Map(), crosstab: Map(), - indicators: {} - } + indicators: {}, + }; } groupIndicators({ indicatorTypesMapping }, { contract }) { if (!indicatorTypesMapping || !Object.keys(indicatorTypesMapping).length || !contract) return; const newIndicators = {}; - contract.get('flags', List()) .forEach((flag, name) => { - if(flag.get && flag.get('value')) { - indicatorTypesMapping[name].types.forEach(type => { + contract.get('flags', List()).forEach((flag, name) => { + if (flag.get && flag.get('value')) { + indicatorTypesMapping[name].types.forEach((type) => { newIndicators[type] = newIndicators[type] || []; newIndicators[type].push(name); - }) + }); } }); this.setState({ indicators: newIndicators }); @@ -171,8 +169,8 @@ export default class Contract extends CRDPage { } componentWillUpdate(nextProps, nextState) { - const indicatorsChanged = this.props.indicatorTypesMapping != nextProps.indicatorTypesMapping; - const contractChanged = this.state.contract != nextState.contract; + const indicatorsChanged = this.props.indicatorTypesMapping !== nextProps.indicatorTypesMapping; + const contractChanged = this.state.contract !== nextState.contract; if (indicatorsChanged || contractChanged) { this.groupIndicators(nextProps, nextState); } @@ -211,8 +209,8 @@ export default class Contract extends CRDPage { data={crosstab.get(corruptionType)} indicators={indicators[corruptionType]} requestNewData={(_, data) => { - const { crosstab } = this.state; - this.setState({ crosstab: crosstab.set(corruptionType, data)}) + const { crosstab } = this.state; + this.setState({ crosstab: crosstab.set(corruptionType, data)}) }} />
@@ -222,10 +220,9 @@ export default class Contract extends CRDPage { } render() { - const { contract, nrOfBidders, nrContracts, percentPESpending, crosstab, - indicators } = this.state; + const { contract, nrOfBidders, nrContracts, percentPESpending } = this.state; - const { id, translations, doSearch, indicatorTypesMapping, filters, allYears, + const { id, translations, doSearch, filters, allYears, years: selectedYears, width, months, monthly } = this.props; const years = Set(allYears).equals(selectedYears) ? @@ -241,7 +238,7 @@ export default class Contract extends CRDPage { const procuringEntityId = contract.getIn(['tender', 'procuringEntity', 'id']) || contract.getIn(['tender', 'procuringEntity', 'identifier', 'id']); - const donutSize = width / 3 - 100; + const donutSize = width / 3 - 100; return (
@@ -254,7 +251,7 @@ export default class Contract extends CRDPage { data={contract} supplier={supplier} filters={filters} - requestNewData={(_, contract) => this.setState({contract})} + requestNewData={(_, contract) => this.setState({ contract })} translations={translations} />
From c978cc20c684b47e7c34a700d43996e3140d23e9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 00:57:34 +0200 Subject: [PATCH 329/702] OCE-405 Started dealing with wiring props routine --- .../contracts/single/index.jsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 51bf19824..d9f3f4fb3 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -12,6 +12,21 @@ import styles from '../style.less'; const EMPTY_SET = Set(); +const ROUTINE_PROPS = ['filters', 'years', 'months', 'monthly', 'translations', 'width'] + +function cherrypickProps(source, keys) { + const target = {}; + keys.forEach(key => target[key] = source[key]); + return target; +} + +function wireProps(parent, prefix) { + const props = cherrypickProps(parent.props, ROUTINE_PROPS); + props.data = parent.state[prefix]; + props.requestNewData = (_, data) => parent.setState({ [prefix]: data }); + return props; +} + class Info extends translatable(Visualization) { getCustomEP() { const { id } = this.props; @@ -254,6 +269,7 @@ export default class Contract extends CRDPage { requestNewData={(_, contract) => this.setState({ contract })} translations={translations} /> +

{this.t('crd:contracts:contractStatistics')} @@ -262,14 +278,7 @@ export default class Contract extends CRDPage { this.setState({ nrOfBidders })} - translations={translations} - width={donutSize} + {...wireProps(this, 'nrOfBidders')} />

@@ -310,3 +319,4 @@ export default class Contract extends CRDPage { ); } } + From df9de0156f0b92423791eccfea4068b25b32045e Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 01:42:37 +0200 Subject: [PATCH 330/702] OCE-405 Making eslint happy --- ui/oce/corruption-risk/index.jsx | 173 +++++++++++++++---------------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index fc20c9b09..425e6fc75 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -68,31 +68,6 @@ class CorruptionRiskDashboard extends React.Component { })); } - languageSwitcher() { - const { TRANSLATIONS } = this.constructor; - const { locale: selectedLocale } = this.state; - if (Object.keys(TRANSLATIONS).length <= 1) return null; - return Object.keys(TRANSLATIONS).map(locale => ( - this.setLocale(locale)} - className={cn({active: locale === selectedLocale})} - > - {locale.split('_')[0]} - - )); - /* return Object.keys(TRANSLATIONS).map(locale => - * ( - * {`${locale}), - * );*/ - } - setLocale(locale) { this.setState({ locale }); localStorage.oceLocale = locale; @@ -174,44 +149,49 @@ class CorruptionRiskDashboard extends React.Component { width={width} /> ); - } else { - return ( - - ); } + return ( + + ); } - loginBox() { - if (this.state.user.loggedIn) { - return ( - - - - ); - } - return ( - - ); + getTranslations() { + const { TRANSLATIONS } = this.constructor; + const { locale } = this.state; + return TRANSLATIONS[locale]; } - toggleDashboardSwitcher(e) { - e.stopPropagation(); - const { dashboardSwitcherOpen } = this.state; - this.setState({ dashboardSwitcherOpen: !dashboardSwitcherOpen }); + t(str) { + const { locale } = this.state; + const { TRANSLATIONS } = this.constructor; + return TRANSLATIONS[locale][str] || TRANSLATIONS.en_US[str] || str; + } + + fetchUserInfo() { + const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); + fetchJson(noCacheUrl).then(({ authenticated, disabledApiSecurity }) => { + this.setState({ + user: { + loggedIn: authenticated, + }, + showLandingPopup: !authenticated || disabledApiSecurity, + disabledApiSecurity, + }); + }); + } + + fetchIndicatorTypesMapping() { + fetchJson('/api/indicatorTypesMapping').then(data => this.setState({ indicatorTypesMapping: data })); } fetchYears() { @@ -230,38 +210,47 @@ class CorruptionRiskDashboard extends React.Component { }); } - fetchIndicatorTypesMapping() { - fetchJson('/api/indicatorTypesMapping').then(data => this.setState({ indicatorTypesMapping: data })); - } - - fetchUserInfo() { - const noCacheUrl = new URI('/isAuthenticated').addSearch('time', Date.now()); - fetchJson(noCacheUrl).then(({ authenticated, disabledApiSecurity }) => { - this.setState({ - user: { - loggedIn: authenticated, - }, - showLandingPopup: !authenticated || disabledApiSecurity, - disabledApiSecurity, - }); - }); + toggleDashboardSwitcher(e) { + e.stopPropagation(); + const { dashboardSwitcherOpen } = this.state; + this.setState({ dashboardSwitcherOpen: !dashboardSwitcherOpen }); } - t(str) { - const { locale } = this.state; - const { TRANSLATIONS } = this.constructor; - return TRANSLATIONS[locale][str] || TRANSLATIONS.en_US[str] || str; + loginBox() { + if (this.state.user.loggedIn) { + return ( + + + + ); + } + return ( + + ); } - getTranslations(){ + languageSwitcher() { const { TRANSLATIONS } = this.constructor; - const { locale } = this.state; - return TRANSLATIONS[locale]; + const { locale: selectedLocale } = this.state; + if (Object.keys(TRANSLATIONS).length <= 1) return null; + return Object.keys(TRANSLATIONS).map(locale => ( + this.setLocale(locale)} + className={cn({ active: locale === selectedLocale })} + > + {locale.split('_')[0]} + + )); } render() { - const { dashboardSwitcherOpen, filterBoxIndex, currentFiltersState, - appliedFilters, data, indicatorTypesMapping, allYears, allMonths, showLandingPopup, + const { dashboardSwitcherOpen, filterBoxIndex, currentFiltersState, appliedFilters, data, + indicatorTypesMapping, allYears, allMonths, showLandingPopup, disabledApiSecurity } = this.state; const { onSwitch, route, navigate } = this.props; @@ -281,7 +270,7 @@ class CorruptionRiskDashboard extends React.Component { redirectToLogin={!disabledApiSecurity} requestClosing={() => this.setState({ showLandingPopup: false })} translations={translations} - languageSwitcher={this.languageSwitcher.bind(this)} + languageSwitcher={(...args) => this.languageSwitcher(...args)} /> }
@@ -290,14 +279,18 @@ class CorruptionRiskDashboard extends React.Component {

this.toggleDashboardSwitcher(e)} + onClick={e => this.toggleDashboardSwitcher(e)} > {this.t('crd:title')}

{dashboardSwitcherOpen && @@ -313,11 +306,11 @@ class CorruptionRiskDashboard extends React.Component {
this.setState({ currentFiltersState })} + onUpdate={newState => this.setState({ currentFiltersState: newState })} onApply={filtersToApply => this.setState({ - filterBoxIndex: null, - appliedFilters: filtersToApply, - currentFiltersState: filtersToApply, + filterBoxIndex: null, + appliedFilters: filtersToApply, + currentFiltersState: filtersToApply, })} translations={translations} currentBoxIndex={filterBoxIndex} @@ -361,6 +354,6 @@ CorruptionRiskDashboard.propTypes = { CorruptionRiskDashboard.TRANSLATIONS = { en_US: require('../../../web/public/languages/en_US.json'), es_ES: require('../../../web/public/languages/es_ES.json'), -} +}; export default CorruptionRiskDashboard; From 30fc037afbf86af5ccff793a2838cf7d1f1cbf42 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 01:53:58 +0200 Subject: [PATCH 331/702] OCE-405 Wired single contract page state --- ui/oce/corruption-risk/index.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 425e6fc75..1da614da6 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -147,6 +147,9 @@ class CorruptionRiskDashboard extends React.Component { monthly={monthly} months={months} width={width} + data={data.get('contract', Map())} + requestNewData={(path, newData) => + this.setState({ data: this.state.data.setIn(['contract'].concat(path), newData) })} /> ); } From 1517130944876e54897ef1f4cebb3221b520e5e3 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 01:56:14 +0200 Subject: [PATCH 332/702] OCE-405 Moved wireProps to tools --- .../contracts/single/index.jsx | 16 +------------ ui/oce/corruption-risk/tools.jsx | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index d9f3f4fb3..a3fa7634f 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -7,26 +7,12 @@ import NrOfBidders from './donuts/nr-of-bidders'; import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; import Crosstab from '../../clickable-crosstab'; +import { wireProps } from '../../tools'; // eslint-disable-next-line no-unused-vars import styles from '../style.less'; const EMPTY_SET = Set(); -const ROUTINE_PROPS = ['filters', 'years', 'months', 'monthly', 'translations', 'width'] - -function cherrypickProps(source, keys) { - const target = {}; - keys.forEach(key => target[key] = source[key]); - return target; -} - -function wireProps(parent, prefix) { - const props = cherrypickProps(parent.props, ROUTINE_PROPS); - props.data = parent.state[prefix]; - props.requestNewData = (_, data) => parent.setState({ [prefix]: data }); - return props; -} - class Info extends translatable(Visualization) { getCustomEP() { const { id } = this.props; diff --git a/ui/oce/corruption-risk/tools.jsx b/ui/oce/corruption-risk/tools.jsx index aa275dac2..399bb2b05 100644 --- a/ui/oce/corruption-risk/tools.jsx +++ b/ui/oce/corruption-risk/tools.jsx @@ -39,3 +39,27 @@ export const mkContractLink = navigate => (content, { id }) => ( {content} ); + +const ROUTINE_PROPS = ['filters', 'years', 'months', 'monthly', 'translations', 'width'] + +export const copyProps = (keys, source, target) => + keys.forEach(key => target[key] = source[key]); + +export function cherrypickProps(keys, source) { + const target = {}; + copyProps(keys, source, target); + return target; +} + +export function wireProps(parent, prefix) { + const { props: parentProps } = parent; + const props = cherrypickProps(ROUTINE_PROPS, parentProps); + if (prefix) { + props.data = parentProps.data.get(prefix); + props.requestNewData = (path, data) => + parentProps.requestNewData(path.concat(prefix), data); + } else { + copyProps(['data', 'requestNewData'], parentProps, props); + } + return props; +} From fe1a7bc1913ac82436f836ac53239805a8e5b829 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 01:58:00 +0200 Subject: [PATCH 333/702] OCE-405 New custom popup chart stub --- ui/oce/corruption-risk/contracts/single/index.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index a3fa7634f..dc883e5e5 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -7,6 +7,7 @@ import NrOfBidders from './donuts/nr-of-bidders'; import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; import Crosstab from '../../clickable-crosstab'; +import CustomPopup from '../../custom-popup'; import { wireProps } from '../../tools'; // eslint-disable-next-line no-unused-vars import styles from '../style.less'; @@ -261,10 +262,11 @@ export default class Contract extends CRDPage { {this.t('crd:contracts:contractStatistics')}
-
From ff9684a1ecf6cf554e4c6bd8c3e84b1f4bc3bfba Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 02:04:46 +0200 Subject: [PATCH 334/702] OCE-405 Forgot to commit custom-popup.jsx lol --- ui/oce/corruption-risk/custom-popup.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ui/oce/corruption-risk/custom-popup.jsx diff --git a/ui/oce/corruption-risk/custom-popup.jsx b/ui/oce/corruption-risk/custom-popup.jsx new file mode 100644 index 000000000..145ac656d --- /dev/null +++ b/ui/oce/corruption-risk/custom-popup.jsx @@ -0,0 +1,12 @@ +class CustomPopup extends React.Component { + render() { + const { Chart } = this.props; + return ( + + ); + } +} + +export default CustomPopup; From e9daf64cbca6cf5a21ac4f11734d17b229a4da47 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 05:17:05 +0200 Subject: [PATCH 335/702] OCE-405 Finished new custom popup component --- ui/oce/corruption-risk/custom-popup.jsx | 44 ++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/custom-popup.jsx b/ui/oce/corruption-risk/custom-popup.jsx index 145ac656d..0340be8ba 100644 --- a/ui/oce/corruption-risk/custom-popup.jsx +++ b/ui/oce/corruption-risk/custom-popup.jsx @@ -1,10 +1,46 @@ +import ReactDOM from 'react-dom'; + class CustomPopup extends React.Component { + constructor(...args) { + super(...args); + this.state = this.state || {}; + this.state = { + show: true, + x: 0, + y: 0 + } + } + + componentDidMount() { + super.componentDidMount && super.componentDidMount(); + const chartContainer = ReactDOM.findDOMNode(this) + .querySelector('.js-plotly-plot'); + + chartContainer.on('plotly_hover', e => this.setState({ show: true })); + chartContainer.on('plotly_unhover', e => this.setState({ show: false })); + chartContainer.addEventListener('mousemove', this.movePopup.bind(this)); + } + + movePopup({ offsetX: x, offsetY: y }) { + this.setState({ x, y }); + } + render() { - const { Chart } = this.props; + const { Popup, Chart } = this.props; + const { show, x, y } = this.state; return ( - +
+ {show && + + } + +
); } } From 3351a6add60f9c3a59bfcf6d45236cc8bf6a605b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 06:28:02 +0200 Subject: [PATCH 336/702] OCE-405 CustomPopup HOC now passes point to popup child --- ui/oce/corruption-risk/custom-popup.jsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/custom-popup.jsx b/ui/oce/corruption-risk/custom-popup.jsx index 0340be8ba..02bad7cf9 100644 --- a/ui/oce/corruption-risk/custom-popup.jsx +++ b/ui/oce/corruption-risk/custom-popup.jsx @@ -5,10 +5,10 @@ class CustomPopup extends React.Component { super(...args); this.state = this.state || {}; this.state = { - show: true, + show: false, x: 0, - y: 0 - } + y: 0, + }; } componentDidMount() { @@ -16,18 +16,25 @@ class CustomPopup extends React.Component { const chartContainer = ReactDOM.findDOMNode(this) .querySelector('.js-plotly-plot'); - chartContainer.on('plotly_hover', e => this.setState({ show: true })); - chartContainer.on('plotly_unhover', e => this.setState({ show: false })); + chartContainer.on('plotly_hover', e => this.showPopup(e)); + chartContainer.on('plotly_unhover', () => this.setState({ show: false })); chartContainer.addEventListener('mousemove', this.movePopup.bind(this)); } + showPopup({ points }) { + this.setState({ + show: true, + points, + }); + } + movePopup({ offsetX: x, offsetY: y }) { this.setState({ x, y }); } render() { const { Popup, Chart } = this.props; - const { show, x, y } = this.state; + const { show, x, y, points } = this.state; return (
{show && @@ -35,6 +42,7 @@ class CustomPopup extends React.Component { {...this.props} y={y} x={x} + points={points} /> } Date: Fri, 8 Dec 2017 06:28:28 +0200 Subject: [PATCH 337/702] OCE-405 Added custom donut popup --- .../contracts/single/donuts/popup/index.jsx | 31 +++++++++++++++++++ .../contracts/single/donuts/popup/style.less | 7 +++++ 2 files changed, 38 insertions(+) create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/popup/style.less diff --git a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx new file mode 100644 index 000000000..06317fe7e --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx @@ -0,0 +1,31 @@ +// eslint-disable-next-line no-unused-vars +import style from './style.less'; + +const POPUP_WIDTH = 250; +const POPUP_HEIGHT = 75; + +class DonutPopup extends React.Component { + render() { + const { x, y, points } = this.props; + const { v: value, label } = points[0]; + const left = x - (POPUP_WIDTH / 2) + 30; + const top = y - POPUP_HEIGHT - 12; + return ( +
+ {label} +
+
+ ) + } +} + +export default DonutPopup; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/popup/style.less b/ui/oce/corruption-risk/contracts/single/donuts/popup/style.less new file mode 100644 index 000000000..057d4a219 --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/popup/style.less @@ -0,0 +1,7 @@ +.dashboard-corruption-risk .content { + .crd-popup.donut-popup { + color: #fefefe; + font-weight: bold; + padding: 15px; + } +} From 457d2fa390b696f78328c6407d057b5f0041dff2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 06:36:24 +0200 Subject: [PATCH 338/702] OCE-405 New string for nr bidders vs avg donut --- .../contracts/single/donuts/nr-of-bidders.jsx | 7 +++++-- .../contracts/single/donuts/popup/index.jsx | 6 +++--- ui/oce/corruption-risk/contracts/single/index.jsx | 5 ++++- web/public/languages/en_US.json | 4 +++- web/public/languages/es_ES.json | 4 +++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index 056ac8d4d..d32973838 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -37,12 +37,15 @@ NrOfBidders.Donut = class extends CenterTextDonut.Donut { if (isNaN(avg) || isNaN(count)) return []; return [{ - labels: ['This contract', 'Average'], + labels: [ + this.t('crd:contract:nrBiddersVsAvg:thisLabel'), + this.t('crd:contract:nrBiddersVsAvg:avgLabel') + ], values: [count == 1 ? count : count * avg, avg], hoverlabel: { bgcolor: '#144361' }, - hoverinfo: 'label', + hoverinfo: 'none', textinfo: 'none', hole: 0.8, type: 'pie', diff --git a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx index 06317fe7e..5864ec959 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx @@ -1,7 +1,7 @@ // eslint-disable-next-line no-unused-vars import style from './style.less'; -const POPUP_WIDTH = 250; +const POPUP_WIDTH = 300; const POPUP_HEIGHT = 75; class DonutPopup extends React.Component { @@ -12,7 +12,7 @@ class DonutPopup extends React.Component { const top = y - POPUP_HEIGHT - 12; return (
- {label} + {label.replace('$#$', value.toFixed(2))}
) diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index dc883e5e5..680a83ef1 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -8,6 +8,7 @@ import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; import Crosstab from '../../clickable-crosstab'; import CustomPopup from '../../custom-popup'; +import DonutPopup from './donuts/popup'; import { wireProps } from '../../tools'; // eslint-disable-next-line no-unused-vars import styles from '../style.less'; @@ -222,7 +223,7 @@ export default class Contract extends CRDPage { } render() { - const { contract, nrOfBidders, nrContracts, percentPESpending } = this.state; + const { contract, nrContracts, percentPESpending } = this.state; const { id, translations, doSearch, filters, allYears, years: selectedYears, width, months, monthly } = this.props; @@ -266,7 +267,9 @@ export default class Contract extends CRDPage { count={contract.getIn(['tender', 'tenderers'], List()).count()} contract={contract} {...wireProps(this, 'nrOfBidders')} + Popup={DonutPopup} Chart={NrOfBidders} + width={donutSize} />
diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 6ffad6108..69efd5e70 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -297,5 +297,7 @@ "crd:contracts:contractStatistics": "Contract Statistics", "crd:contracts:noFlags": "This procurement has no flags", "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", - "crd:sidebar:totalContracts:title": "Total Procurements" + "crd:sidebar:totalContracts:title": "Total Procurements", + "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", + "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 64b03aad8..42f754234 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -297,5 +297,7 @@ "crd:contracts:contractStatistics": "Contract Statistics", "crd:contracts:noFlags": "This procurement has no flags", "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", - "crd:sidebar:totalContracts:title": "Total Procurements" + "crd:sidebar:totalContracts:title": "Total Procurements", + "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", + "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements'" } From 0f33fa1aa25992426b2b27b849374d9edd830ceb Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 07:22:57 +0200 Subject: [PATCH 339/702] OCE-405 Formatting value only when required and fixed replace --- .../corruption-risk/contracts/single/donuts/popup/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx index 5864ec959..0be54cb32 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx @@ -10,6 +10,9 @@ class DonutPopup extends React.Component { const { v: value, label } = points[0]; const left = x - (POPUP_WIDTH / 2) + 30; const top = y - POPUP_HEIGHT - 12; + const formattedValue = Math.round(value) === value ? + value : + value.toFixed(2); return (
- {label.replace('$#$', value.toFixed(2))} + {label.replace(/\$\#\$/g, formattedValue)}
) From 76e9b1507ca79a70b9afb6db7dfe4e25d40fb10b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 07:23:32 +0200 Subject: [PATCH 340/702] OCE-405 New text for nr contract with this PE chart --- .../single/donuts/nr-contract-with-pe.jsx | 7 +++++-- ui/oce/corruption-risk/contracts/single/index.jsx | 14 +++++--------- web/public/languages/en_US.json | 4 +++- web/public/languages/es_ES.json | 4 +++- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx index ce06bf14a..9a9ff1897 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-contract-with-pe.jsx @@ -56,12 +56,15 @@ NrOfContractsWithPE.Donut = class extends CenterTextDonut.Donut { const data = super.getData(); if (!data) return []; return [{ - labels: ['Won by this supplier', 'Contracts with this PE'], + labels: [ + this.t('crd:contract:nrContractWithPE:match'), + this.t('crd:contract:nrContractWithPE:total') + ], values: [data.get('thisPE'), data.get('total')], hoverlabel: { bgcolor: '#144361' }, - hoverinfo: 'label', + hoverinfo: 'none', textinfo: 'none', hole: 0.8, type: 'pie', diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index 680a83ef1..a6d100f0b 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -223,7 +223,7 @@ export default class Contract extends CRDPage { } render() { - const { contract, nrContracts, percentPESpending } = this.state; + const { contract, percentPESpending } = this.state; const { id, translations, doSearch, filters, allYears, years: selectedYears, width, months, monthly } = this.props; @@ -274,16 +274,12 @@ export default class Contract extends CRDPage {
{procuringEntityId && supplier && - this.setState({ nrContracts })} - translations={translations} + {...wireProps(this, 'nrContracts')} + Popup={DonutPopup} + Chart={NrOfContractsWithThisPE} width={donutSize} /> } diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 69efd5e70..944457280 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -299,5 +299,7 @@ "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", "crd:sidebar:totalContracts:title": "Total Procurements", "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", - "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements" + "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements", + "crd:contract:nrContractWithPE:total": "X: The total number of procurements issued by the procuring entity", + "crd:contract:nrContractWithPE:match": "X: This procurement is 1 of X between this supplier and this procuring entity" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 42f754234..23b2f7f68 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -299,5 +299,7 @@ "crd:contracts:clickCrosstabHint": "Click a cell on the charts below to release detailed information", "crd:sidebar:totalContracts:title": "Total Procurements", "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", - "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements'" + "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements", + "crd:contract:nrContractWithPE:total": "$#$: The total number of procurements issued by the procuring entity", + "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of $#$ between this supplier and this procuring entity" } From 1987f00085266d6226a8c2e434912cc9e671b575 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 08:04:23 +0200 Subject: [PATCH 341/702] OCE-405 Custom popup for 3rd donuts. Added text. Fixed proportion bug. --- .../index.jsx} | 33 +++++++++-------- .../donuts/percent-pe-spending/popup.jsx | 35 +++++++++++++++++++ .../contracts/single/donuts/popup/index.jsx | 6 ++-- .../contracts/single/index.jsx | 13 +++---- web/public/languages/en_US.json | 6 ++-- web/public/languages/es_ES.json | 4 ++- 6 files changed, 69 insertions(+), 28 deletions(-) rename ui/oce/corruption-risk/contracts/single/donuts/{percent-pe-spending.jsx => percent-pe-spending/index.jsx} (67%) create mode 100644 ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx similarity index 67% rename from ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx rename to ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx index 08beef6dc..85046f8ac 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/index.jsx @@ -1,4 +1,4 @@ -import CenterTextDonut from './index.jsx'; +import CenterTextDonut from '../index.jsx'; class PercentPESpending extends CenterTextDonut { getCenterText() { @@ -24,26 +24,26 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { return `percentageAmountAwarded?procuringEntityId=${procuringEntityId}&supplierId=${supplierId}`; } - transform(data){ + transform(data) { try { const { percentage, totalAwarded, totalAwardedToSuppliers } = data[0]; return { percentage, total: totalAwarded.sum, - toSuppliers: totalAwardedToSuppliers.sum - } - } catch(e) { + toSuppliers: totalAwardedToSuppliers.sum, + }; + } catch (e) { return { percentage: 0, total: 0, - toSuppliers: 0 - } + toSuppliers: 0, + }; } } componentDidUpdate(prevProps, ...rest) { - const peChanged = this.props.procuringEntityId != prevProps.procuringEntityId; - const supplierChanged = this.props.supplierId != prevProps.supplierId; + const peChanged = this.props.procuringEntityId !== prevProps.procuringEntityId; + const supplierChanged = this.props.supplierId !== prevProps.supplierId; if (peChanged || supplierChanged) { this.fetch(); } else { @@ -54,13 +54,18 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { getData() { const data = super.getData(); if (!data || !data.count()) return []; + const toSuppliers = data.get('toSuppliers'); + const total = data.get('total'); return [{ - labels: ['Awarded to this supplier', 'Awarded by this PE'], - values: [data.get('toSuppliers'), data.get('total')], + labels: [ + this.t('crd:contract:percentPEspending:this'), + this.t('crd:contract:percentPEspending:match'), + ], + values: [toSuppliers, total-toSuppliers], hoverlabel: { - bgcolor: '#144361' + bgcolor: '#144361', }, - hoverinfo: 'label', + hoverinfo: 'none', textinfo: 'none', hole: 0.8, type: 'pie', @@ -76,6 +81,6 @@ PercentPESpending.Donut = class extends CenterTextDonut.Donut { paper_bgcolor: 'rgba(0,0,0,0)', }; } -} +}; export default PercentPESpending; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx new file mode 100644 index 000000000..d002fb86b --- /dev/null +++ b/ui/oce/corruption-risk/contracts/single/donuts/percent-pe-spending/popup.jsx @@ -0,0 +1,35 @@ +// eslint-disable-next-line no-unused-vars +const POPUP_WIDTH = 300; +const POPUP_HEIGHT = 90; + +class PercentPEPopup extends React.Component { + render() { + const { x, y, points, data } = this.props; + const total = data.get('total'); + const { v, label } = points[0]; + const left = x - (POPUP_WIDTH / 2) + 30; + const top = y - POPUP_HEIGHT - 12; + const value = v / total * 100; + const formattedValue = Math.round(value) === value ? + value : + value.toFixed(2); + + return ( +
+ {label.replace(/\$#\$/g, formattedValue)} +
+
+ ); + } +} + +export default PercentPEPopup; diff --git a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx index 0be54cb32..d43118050 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/popup/index.jsx @@ -21,13 +21,13 @@ class DonutPopup extends React.Component { height: POPUP_HEIGHT, left, top, - color: 'white' + color: 'white', }} > - {label.replace(/\$\#\$/g, formattedValue)} + {label.replace(/\$#\$/g, formattedValue)}
- ) + ); } } diff --git a/ui/oce/corruption-risk/contracts/single/index.jsx b/ui/oce/corruption-risk/contracts/single/index.jsx index a6d100f0b..a47984f1c 100644 --- a/ui/oce/corruption-risk/contracts/single/index.jsx +++ b/ui/oce/corruption-risk/contracts/single/index.jsx @@ -6,6 +6,7 @@ import TopSearch from '../top-search'; import NrOfBidders from './donuts/nr-of-bidders'; import NrOfContractsWithThisPE from './donuts/nr-contract-with-pe'; import PercentPESpending from './donuts/percent-pe-spending'; +import PercentPESpendingPopup from './donuts/percent-pe-spending/popup'; import Crosstab from '../../clickable-crosstab'; import CustomPopup from '../../custom-popup'; import DonutPopup from './donuts/popup'; @@ -286,16 +287,12 @@ export default class Contract extends CRDPage {
{procuringEntityId && supplier && - this.setState({ percentPESpending })} - translations={translations} + {...wireProps(this, 'percentPESpending')} + Popup={PercentPESpendingPopup} + Chart={PercentPESpending} width={donutSize} /> } diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 944457280..7cfeef325 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -300,6 +300,8 @@ "crd:sidebar:totalContracts:title": "Total Procurements", "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements", - "crd:contract:nrContractWithPE:total": "X: The total number of procurements issued by the procuring entity", - "crd:contract:nrContractWithPE:match": "X: This procurement is 1 of X between this supplier and this procuring entity" + "crd:contract:nrContractWithPE:total": "$#$: The total number of procurements issued by the procuring entity", + "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of X between this supplier and this procuring entity", + "crd:contract:percentPEspending:this": "$#$%: Percentage of this procuring entity's spending that has been won by this supplier", + "crd:contract:percentPEspending:match": "$#$%: Percentage of this procuring entity's spending that has been won by other suppliers" } diff --git a/web/public/languages/es_ES.json b/web/public/languages/es_ES.json index 23b2f7f68..029290718 100644 --- a/web/public/languages/es_ES.json +++ b/web/public/languages/es_ES.json @@ -301,5 +301,7 @@ "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements", "crd:contract:nrContractWithPE:total": "$#$: The total number of procurements issued by the procuring entity", - "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of $#$ between this supplier and this procuring entity" + "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of $#$ between this supplier and this procuring entity", + "crd:contract:percentPEspending:this": "$#$%: Percentage of this procuring entity's spending that has been won by this supplier", + "crd:contract:percentPEspending:match": "$#$%: Percentage of this procuring entity's spending that has been won by other suppliers" } From 9572655fd66a39006a4ad4ae289d9ca60c98ed36 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 8 Dec 2017 08:06:42 +0200 Subject: [PATCH 342/702] OCE-405 Added missing placeholder --- web/public/languages/en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/public/languages/en_US.json b/web/public/languages/en_US.json index 7cfeef325..46ec41083 100644 --- a/web/public/languages/en_US.json +++ b/web/public/languages/en_US.json @@ -301,7 +301,7 @@ "crd:contract:nrBiddersVsAvg:thisLabel": "$#$: Number of bidders on this procurement", "crd:contract:nrBiddersVsAvg:avgLabel": "$#$: is the average number of bidders for all procurements", "crd:contract:nrContractWithPE:total": "$#$: The total number of procurements issued by the procuring entity", - "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of X between this supplier and this procuring entity", + "crd:contract:nrContractWithPE:match": "$#$: This procurement is 1 of $#$ between this supplier and this procuring entity", "crd:contract:percentPEspending:this": "$#$%: Percentage of this procuring entity's spending that has been won by this supplier", "crd:contract:percentPEspending:match": "$#$%: Percentage of this procuring entity's spending that has been won by other suppliers" } From bae48271e6f02eea1bf9e7ce2bceda3ef8fe44df Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 8 Dec 2017 15:37:28 +0200 Subject: [PATCH 343/702] Also run Liquibase after JPA initialization #198 --- .../spring/DatabaseConfiguration.java | 39 ++++++++++++------- .../spring/PersistenceApplication.java | 26 ++++++++++++- .../spring/SpringLiquibaseRunner.java | 31 +++++++++++++++ 3 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java index b8e0aa95f..6228a5c58 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/DatabaseConfiguration.java @@ -1,25 +1,20 @@ -/******************************************************************************* +/** * Copyright (c) 2015 Development Gateway, Inc and others. - * + *

* All rights reserved. This program and the accompanying materials * are made available under the terms of the MIT License (MIT) * which accompanies this distribution, and is available at * https://opensource.org/licenses/MIT - * + *

* Contributors: * Development Gateway - initial API and implementation - *******************************************************************************/ + */ /** - * + * */ package org.devgateway.toolkit.persistence.spring; -import java.io.PrintWriter; -import java.net.InetAddress; -import java.util.Properties; - -import javax.naming.NamingException; - +import liquibase.integration.spring.SpringLiquibase; import org.apache.derby.drda.NetworkServerControl; import org.apache.log4j.Logger; import org.apache.tomcat.jdbc.pool.DataSource; @@ -33,6 +28,12 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import javax.naming.NamingException; +import javax.persistence.EntityManagerFactory; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.Properties; + /** * @author mpostelnicu * @@ -85,7 +86,7 @@ public class DatabaseConfiguration { * toolkitDS/driver=org.apache.derby.jdbc.ClientDriver toolkitDS/user=app * toolkitDS/password=app * toolkitDS/url=jdbc:derby://localhost//derby/toolkit - * + * * @return */ @Bean @@ -106,11 +107,11 @@ public SimpleNamingContextBuilder jndiBuilder() { /** * Creates a {@link javax.sql.DataSource} based on Tomcat {@link DataSource} - * + * * @return */ @Bean - @DependsOn(value = { "derbyServer" }) + @DependsOn(value = {"derbyServer"}) public DataSource dataSource() { PoolProperties pp = new PoolProperties(); pp.setJmxEnabled(true); @@ -129,7 +130,7 @@ public DataSource dataSource() { /** * Graciously starts a Derby Database Server when the application starts up - * + * * @return * @throws Exception */ @@ -143,4 +144,12 @@ public NetworkServerControl derbyServer() throws Exception { return nsc; } + @Bean + public SpringLiquibaseRunner liquibaseAfterJPA(final SpringLiquibase springLiquibase, + final EntityManagerFactory entityManagerFactory) { + logger.info("Instantiating SpringLiquibaseRunner after initialization of entityManager using factory " + + entityManagerFactory); + return new SpringLiquibaseRunner(springLiquibase); + } + } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java index 72bde9cf2..172356353 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java @@ -11,11 +11,18 @@ *******************************************************************************/ package org.devgateway.toolkit.persistence.spring; +import org.apache.catalina.startup.Tomcat; import org.devgateway.toolkit.persistence.dao.GenericPersistable; import org.devgateway.toolkit.persistence.repository.RoleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @@ -24,9 +31,8 @@ /** * Run this application only when you need access to Spring Data JPA but without * Wicket frontend - * - * @author mpostelnicu * + * @author mpostelnicu */ @SpringBootApplication @EnableJpaRepositories(basePackageClasses = RoleRepository.class) @@ -36,7 +42,23 @@ @ComponentScan("org.devgateway.toolkit") public class PersistenceApplication { + private Logger logger = LoggerFactory.getLogger(PersistenceApplication.class); + public static void main(final String[] args) { SpringApplication.run(PersistenceApplication.class, args); } + + @Bean + public TomcatEmbeddedServletContainerFactory tomcatFactory(final + @Qualifier("liquibaseAfterJPA") SpringLiquibaseRunner + liquibaseAfterJPA) { + logger.info("Instantiating tomcat after initialization of " + liquibaseAfterJPA); + return new TomcatEmbeddedServletContainerFactory() { + @Override + protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(final Tomcat tomcat) { + tomcat.enableNaming(); + return super.getTomcatEmbeddedServletContainer(tomcat); + } + }; + } } \ No newline at end of file diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java new file mode 100644 index 000000000..b027e0f14 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/SpringLiquibaseRunner.java @@ -0,0 +1,31 @@ +package org.devgateway.toolkit.persistence.spring; + +import liquibase.integration.spring.SpringLiquibase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Octavian Ciubotaru + */ +public class SpringLiquibaseRunner implements InitializingBean { + + private Logger logger = LoggerFactory.getLogger(SpringLiquibaseRunner.class); + + private final SpringLiquibase springLiquibase; + + public SpringLiquibaseRunner(final SpringLiquibase springLiquibase) { + this.springLiquibase = springLiquibase; + } + + @Override + public void afterPropertiesSet() throws Exception { + logger.info("Attempting to run liquibase second time"); + springLiquibase.afterPropertiesSet(); + } + + @Override + public String toString() { + return getClass().getName() + " (" + springLiquibase + ")"; + } +} \ No newline at end of file From 1912c88226c86d44862ca69d3795dc0af1caac12 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 8 Dec 2017 17:26:18 +0200 Subject: [PATCH 344/702] #198 checkstyle --- .../toolkit/persistence/spring/PersistenceApplication.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java index 172356353..5c3f47356 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/PersistenceApplication.java @@ -49,9 +49,8 @@ public static void main(final String[] args) { } @Bean - public TomcatEmbeddedServletContainerFactory tomcatFactory(final - @Qualifier("liquibaseAfterJPA") SpringLiquibaseRunner - liquibaseAfterJPA) { + public TomcatEmbeddedServletContainerFactory tomcatFactory(@Qualifier("liquibaseAfterJPA") final + SpringLiquibaseRunner liquibaseAfterJPA) { logger.info("Instantiating tomcat after initialization of " + liquibaseAfterJPA); return new TomcatEmbeddedServletContainerFactory() { @Override From 0d5029eb88508ffd1ab0078fe5ca26d8881559a6 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Dec 2017 16:35:46 +0200 Subject: [PATCH 345/702] OCE-383 Using wireProps for overviewpage --- ui/oce/corruption-risk/index.jsx | 3 +++ ui/oce/corruption-risk/overview-page.jsx | 14 +++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 1da614da6..a567676ce 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -160,6 +160,9 @@ class CorruptionRiskDashboard extends React.Component { years={years} monthly={monthly} months={months} + data={data.get('overview', Map())} + requestNewData={(path, newData) => + this.setState({ data: this.state.data.setIn(['overview'].concat(path), newData) })} indicatorTypesMapping={indicatorTypesMapping} styling={styling} width={width} diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index cd5fec393..9e964a8f6 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -1,7 +1,7 @@ import { range } from '../tools'; import CustomPopupChart from './custom-popup-chart'; import CRDPage from './page'; -import { colorLuminance } from './tools'; +import { colorLuminance, wireProps } from './tools'; import ProcurementsTable from './procurements-table'; const TRACES = ['COLLUSION', 'FRAUD', 'RIGGING']; @@ -132,13 +132,12 @@ class OverviewPage extends CRDPage { constructor(...args) { super(...args); this.state = { - corruptionType: null, topFlaggedContracts: null, }; } render() { - const { corruptionType, topFlaggedContracts } = this.state; + const { topFlaggedContracts } = this.state; const { filters, translations, years, monthly, months, indicatorTypesMapping, styling, width, navigate } = this.props; return ( @@ -146,14 +145,7 @@ class OverviewPage extends CRDPage {

{this.t('crd:overview:overTimeChart:title')}

- this.setState({ corruptionType: newCorruptionType })} - translations={translations} - data={corruptionType} - years={years} - monthly={monthly} - months={months} + {...wireProps(this, 'corruptionType')} styling={styling} indicatorTypesMapping={indicatorTypesMapping} width={width - 20} From 933c588127e7cfe72647b72fb036872aa61ed29e Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Dec 2017 16:36:55 +0200 Subject: [PATCH 346/702] OCE-383 Rewired top flagged contracts --- ui/oce/corruption-risk/overview-page.jsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 9e964a8f6..1eae241dc 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -155,16 +155,9 @@ class OverviewPage extends CRDPage {

{this.t('crd:overview:topFlagged:title')}

- this.setState({ topFlaggedContracts: newTopFlaggedContracts })} navigate={navigate} />
From 6c788c176226527d1d3b90c1481e376a385ce1b7 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Dec 2017 16:40:17 +0200 Subject: [PATCH 347/702] OCE-383 Cleanup --- ui/oce/corruption-risk/overview-page.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page.jsx index 1eae241dc..ec0e555e1 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page.jsx @@ -137,9 +137,7 @@ class OverviewPage extends CRDPage { } render() { - const { topFlaggedContracts } = this.state; - const { filters, translations, years, monthly, months, indicatorTypesMapping, styling, width, - navigate } = this.props; + const { indicatorTypesMapping, styling, width, navigate } = this.props; return (
From 5931e60627b766a5b344272daaa19d41532b61ec Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 11 Dec 2017 17:12:43 +0200 Subject: [PATCH 348/702] OCE-383 Dynamic height and top for top contracts chart popup --- ui/oce/corruption-risk/custom-popup-chart.jsx | 2 +- .../index.jsx} | 28 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) rename ui/oce/corruption-risk/{overview-page.jsx => overview-page/index.jsx} (86%) diff --git a/ui/oce/corruption-risk/custom-popup-chart.jsx b/ui/oce/corruption-risk/custom-popup-chart.jsx index a169831f8..832e7ca30 100644 --- a/ui/oce/corruption-risk/custom-popup-chart.jsx +++ b/ui/oce/corruption-risk/custom-popup-chart.jsx @@ -53,7 +53,7 @@ class CustomPopupChart extends Chart { left, year, traceName, - traceIndex + traceIndex, }, }); } diff --git a/ui/oce/corruption-risk/overview-page.jsx b/ui/oce/corruption-risk/overview-page/index.jsx similarity index 86% rename from ui/oce/corruption-risk/overview-page.jsx rename to ui/oce/corruption-risk/overview-page/index.jsx index ec0e555e1..a60bf64b7 100644 --- a/ui/oce/corruption-risk/overview-page.jsx +++ b/ui/oce/corruption-risk/overview-page/index.jsx @@ -1,8 +1,9 @@ -import { range } from '../tools'; -import CustomPopupChart from './custom-popup-chart'; -import CRDPage from './page'; -import { colorLuminance, wireProps } from './tools'; -import ProcurementsTable from './procurements-table'; +import { range } from '../../tools'; +import CustomPopupChart from '../custom-popup-chart'; +import CRDPage from '../page'; +import { colorLuminance, wireProps } from '../tools'; +import ProcurementsTable from '../procurements-table'; +import { POPUP_HEIGHT } from '../constants'; const TRACES = ['COLLUSION', 'FRAUD', 'RIGGING']; @@ -102,8 +103,21 @@ class CorruptionType extends CustomPopupChart { indicatorTypesMapping[indicatorId].types.indexOf(dataForPoint.type) > -1 ).length; + const percentFlaggedLabel = this.t('crd:overview:overTimeChart:percentFlagged'); + + let height = POPUP_HEIGHT; + let { top } = popup; + if (percentFlaggedLabel.length > 30) { + const delta = 30; + height += delta; + if (popup.toTheLeft) { + top += delta / 2; + } + top -= delta; + } + return ( -
+
{year} @@ -117,7 +131,7 @@ class CorruptionType extends CustomPopupChart {
{dataForPoint.flaggedCount}
{this.t('crd:overview:overTimeChart:totalProcurementsFlagged')}
{dataForPoint.flaggedProjectCount}
-
{this.t('crd:overview:overTimeChart:percentFlagged')}
+
{percentFlaggedLabel}
{dataForPoint.percent.toFixed(2)}%
From 08c2465ea40519fdb7e277eca5e25da44d8dfaa2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 13 Dec 2017 08:20:25 +0200 Subject: [PATCH 349/702] OCE-407 Controlling top search input --- ui/oce/corruption-risk/contracts/top-search.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/top-search.jsx b/ui/oce/corruption-risk/contracts/top-search.jsx index 40d4c762a..02285e0a4 100644 --- a/ui/oce/corruption-risk/contracts/top-search.jsx +++ b/ui/oce/corruption-risk/contracts/top-search.jsx @@ -1,22 +1,26 @@ import translatable from '../../translatable'; class TopSearch extends translatable(React.Component) { - componentDidMount(){ - const { searchQuery } = this.props; - if (searchQuery) this.input.value = searchQuery; + constructor(props, ...rest){ + super(props, ...rest); + this.state = { + inputValue: props.searchQuery + } } render() { const { doSearch } = this.props; + const { inputValue } = this.state; return (
- doSearch(this.input.value)}> + doSearch(inputValue)}>
this.input = c} + value={inputValue} + onChange={e => this.setState({ inputValue: e.target.value })} />
From aa07755d3c8c67112b093c104bab06365c918cca Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 13 Dec 2017 10:04:54 +0200 Subject: [PATCH 350/702] OCE-407 Added hint text --- .../corruption-risk/contracts/top-search.jsx | 35 ---------- .../contracts/top-search/index.jsx | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+), 35 deletions(-) delete mode 100644 ui/oce/corruption-risk/contracts/top-search.jsx create mode 100644 ui/oce/corruption-risk/contracts/top-search/index.jsx diff --git a/ui/oce/corruption-risk/contracts/top-search.jsx b/ui/oce/corruption-risk/contracts/top-search.jsx deleted file mode 100644 index 02285e0a4..000000000 --- a/ui/oce/corruption-risk/contracts/top-search.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import translatable from '../../translatable'; - -class TopSearch extends translatable(React.Component) { - constructor(props, ...rest){ - super(props, ...rest); - this.state = { - inputValue: props.searchQuery - } - } - - render() { - const { doSearch } = this.props; - const { inputValue } = this.state; - return ( -
- doSearch(inputValue)}> -
- this.setState({ inputValue: e.target.value })} - /> -
- -
-
- -
- ) - } -} - -export default TopSearch; diff --git a/ui/oce/corruption-risk/contracts/top-search/index.jsx b/ui/oce/corruption-risk/contracts/top-search/index.jsx new file mode 100644 index 000000000..78c7653da --- /dev/null +++ b/ui/oce/corruption-risk/contracts/top-search/index.jsx @@ -0,0 +1,66 @@ +import translatable from '../../../translatable'; +import style from './style.less'; + +class TopSearch extends translatable(React.Component) { + constructor(props, ...rest) { + super(props, ...rest); + this.state = { + inputValue: props.searchQuery || '', + exactMatch: false + }; + } + + render() { + const { doSearch } = this.props; + const { inputValue } = this.state; + const hasSpecialChars = inputValue.indexOf('-') > -1; + return ( +
+
+
doSearch(inputValue)}> +
+ this.setState({ inputValue: e.target.value })} + /> +
+ +
+
+ +
+ {hasSpecialChars &&
+
+
+ „-“ is a special character meaning „without“. +
+ For example + „ + foo +  - + bar + “ + will look for contracts containing foo but not bar. +
+ Enable literal search if this is not the desired behavior. +
+ +   + +
+
} +
+ ); + } +} + +export default TopSearch; From a4f089f89beec6b503b5f8aea4bc02d547242e69 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 13 Dec 2017 10:26:39 +0200 Subject: [PATCH 351/702] OCE-407 Implemented exact search --- .../contracts/top-search/index.jsx | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/top-search/index.jsx b/ui/oce/corruption-risk/contracts/top-search/index.jsx index 78c7653da..c96fa9d7b 100644 --- a/ui/oce/corruption-risk/contracts/top-search/index.jsx +++ b/ui/oce/corruption-risk/contracts/top-search/index.jsx @@ -1,19 +1,38 @@ import translatable from '../../../translatable'; +// eslint-disable-next-line no-unused-vars import style from './style.less'; +const isExactMatch = a => a.indexOf('"') === 0 && + a.lastIndexOf('"') === a.length - 1 && + a.length > 1; + class TopSearch extends translatable(React.Component) { constructor(props, ...rest) { super(props, ...rest); this.state = { inputValue: props.searchQuery || '', - exactMatch: false }; } + toggleExactMatch() { + const { inputValue } = this.state; + const exactMatch = isExactMatch(inputValue); + const newValue = exactMatch ? + inputValue.slice(1, -1) : + `"${inputValue}"`; + + this.setState( + { inputValue: newValue }, + this.props.doSearch.bind(null, newValue), + ); + } + render() { const { doSearch } = this.props; const { inputValue } = this.state; const hasSpecialChars = inputValue.indexOf('-') > -1; + const exactMatch = isExactMatch(inputValue); + return (
@@ -36,21 +55,24 @@ class TopSearch extends translatable(React.Component) {
„-“ is a special character meaning „without“. -
+
For example „ foo  - bar “ - will look for contracts containing foo but not bar. -
- Enable literal search if this is not the desired behavior. + will look for contracts containing +   + foo but not bar. +
+ Use quotes or enable literal search if this is not the desired behavior.
this.toggleExactMatch()} />  
diff --git a/ui/oce/corruption-risk/donut/two-rows-center-text/style.less b/ui/oce/corruption-risk/suppliers/single/style.less similarity index 100% rename from ui/oce/corruption-risk/donut/two-rows-center-text/style.less rename to ui/oce/corruption-risk/suppliers/single/style.less From d27d5f784f3d2bb994e2c05b01eeb7f273fa290e Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 21 Dec 2017 07:00:23 +0200 Subject: [PATCH 377/702] OCE-427 Processing lables for donuts --- ui/oce/corruption-risk/donut/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/donut/index.jsx b/ui/oce/corruption-risk/donut/index.jsx index b4a06ee72..6b17fe20f 100644 --- a/ui/oce/corruption-risk/donut/index.jsx +++ b/ui/oce/corruption-risk/donut/index.jsx @@ -14,7 +14,7 @@ class Donut extends backendYearFilterable(Chart) { getData() { const { data, values } = this.props; return [{ - labels: ['a', 'b'], + labels: values.map(pluck('label')), values: data, hoverlabel: { bgcolor: '#144361', From 690a0669c9e2c9344628510ebeb28c1582570d8d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 21 Dec 2017 07:02:11 +0200 Subject: [PATCH 378/702] OCE-427 Provisional mocked labels --- .../suppliers/single/donuts/amount-lost-vs-won.jsx | 2 ++ ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx | 4 +++- .../suppliers/single/donuts/nr-lost-vs-won.jsx | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx index d5490d4d6..6496efba3 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/amount-lost-vs-won.jsx @@ -28,8 +28,10 @@ class AmountWonVsLost extends React.Component { subtitle="Won vs Lost" values={[{ color: '#72c47e', + label: 'Won', }, { color: '#2e833a', + label: 'Lost', }]} /> ); diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx index e08cce6a1..148df6af7 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx @@ -4,7 +4,6 @@ import { pluck } from '../../../../tools'; class CenterText extends React.PureComponent { render() { const { data, values } = this.props; - console.log(values); const [fst, snd, thrd] = data; const [fstColor, sndColor, thrdColor] = values.map(pluck('color')); return ( @@ -27,10 +26,13 @@ class TotalFlags extends React.Component { subtitle="by Risk Type" values={[{ color: '#fbc42c', + label: 'Fraud', }, { color: '#3372b2', + label: 'Process Rigging' }, { color: '#30a0f5', + label: 'Collusion', }]} /> ); diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx index 188643d99..3968e4609 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx @@ -29,8 +29,10 @@ class NrWonVsLost extends React.PureComponent { subtitle="Won vs Lost" values={[{ color: '#165781', + label: 'Won', }, { color: '#5fa0c9', + label: 'Lost', }]} /> ); From 57407c5db34f73780fe19fd6f508864475ccb047 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 22 Dec 2017 14:27:29 +0200 Subject: [PATCH 379/702] OCE-389 Properly redirecting after login --- ui/oce/corruption-risk/constants.es6 | 2 +- ui/oce/corruption-risk/index.jsx | 13 ++++++++----- ui/oce/corruption-risk/landing-popup.jsx | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ui/oce/corruption-risk/constants.es6 b/ui/oce/corruption-risk/constants.es6 index 66030dafb..9824ce1db 100644 --- a/ui/oce/corruption-risk/constants.es6 +++ b/ui/oce/corruption-risk/constants.es6 @@ -1,4 +1,4 @@ -export const LOGIN_URL = '/login?referrer=/ui/index.html%23!/crd'; +export const LOGIN_URL = '/login?referrer=/ui/index.html'; export const POPUP_HEIGHT = 150; export const POPUP_WIDTH = 400; export const CORRUPTION_TYPES = ['FRAUD', 'RIGGING', 'COLLUSION']; diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index a07a348ed..8203a3e08 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -246,11 +246,14 @@ class CorruptionRiskDashboard extends React.Component { ); } - return ( - - ); + const hash = encodeURIComponent(location.hash); + return ( + + + + ); } languageSwitcher() { diff --git a/ui/oce/corruption-risk/landing-popup.jsx b/ui/oce/corruption-risk/landing-popup.jsx index e7a105381..9e29c1acb 100644 --- a/ui/oce/corruption-risk/landing-popup.jsx +++ b/ui/oce/corruption-risk/landing-popup.jsx @@ -13,7 +13,8 @@ class LandingPopup extends translatable(React.Component) { onClose(){ const {redirectToLogin, requestClosing} = this.props; if(redirectToLogin){ - location.href = LOGIN_URL; + const hash = encodeURIComponent(location.hash); + location.href = `${LOGIN_URL}${hash}`; } else { requestClosing(); } From d516bbbb4714d5a85262622bbe10a93ae33c92e8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 22 Dec 2017 14:33:08 +0200 Subject: [PATCH 380/702] OCE-389 Handling `popstate` event --- ui/oce/router.es6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/oce/router.es6 b/ui/oce/router.es6 index d1c18a46c..8dfc8f76e 100644 --- a/ui/oce/router.es6 +++ b/ui/oce/router.es6 @@ -13,6 +13,9 @@ export const getRoute = () => { export const navigate = (...params) => { location.hash = `${PREFIX}/${params.join('/')}`; +}; + +window.addEventListener('popstate', () => { const route = getRoute(); listeners.forEach(listener => listener(route)); -}; +}); From d1fadafab6ac2ac99fb81b15f532a3116a586007 Mon Sep 17 00:00:00 2001 From: Mihai Postelnicu Date: Fri, 22 Dec 2017 17:11:04 +0200 Subject: [PATCH 381/702] OCE-433 - Endpoint for supplier donut #1 --- .../controller/AwardsWonLostController.java | 96 +++++++++++++++++++ .../FrequentTenderersController.java | 13 +-- .../controller/GenericOCDSController.java | 15 ++- .../request/DefaultFilterPagingRequest.java | 12 ++- .../resources/allowedApiEndpoints.properties | 3 +- 5 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 web/src/main/java/org/devgateway/ocds/web/rest/controller/AwardsWonLostController.java diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/AwardsWonLostController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/AwardsWonLostController.java new file mode 100644 index 000000000..55efcc4dc --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/AwardsWonLostController.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.web.rest.controller; + +import com.mongodb.DBObject; +import io.swagger.annotations.ApiOperation; +import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.Fields; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +/** + * @author mpostelnicu + */ +@RestController +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") +@Cacheable +public class AwardsWonLostController extends GenericOCDSController { + + + @ApiOperation(value = "Counts the won procurements, receives any filters, but most important here" + + " is the supplierId") + @RequestMapping(value = "/api/procurementsWon", + method = {RequestMethod.POST, RequestMethod.GET}, + produces = "application/json") + public List procurementsWon(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + Aggregation agg = newAggregation( + match(where("awards.status").is("active") + .andOperator(getYearDefaultFilterCriteria( + filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE + ))), + unwind("awards"), + match(getYearDefaultFilterCriteria( + filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE + )), + group(Fields.UNDERSCORE_ID), + group().count().as("cnt"), + project("cnt").andExclude(Fields.UNDERSCORE_ID) + ); + return releaseAgg(agg); + } + + + @ApiOperation(value = "Counts the lost procurements, receives any filters, but most important here" + + " is the supplierId. This will use bidder filter") + @RequestMapping(value = "/api/procurementsLost", + method = {RequestMethod.POST, RequestMethod.GET}, + produces = "application/json") + public List procurementsLost(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + Aggregation agg = newAggregation( + match(getYearDefaultFilterCriteria( + filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE + )), + unwind("bids"), + match(getYearDefaultFilterCriteria( + filter, + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE + )), + group(Fields.UNDERSCORE_ID), + group().count().as("cnt"), + project("cnt").andExclude(Fields.UNDERSCORE_ID) + ); + return releaseAgg(agg); + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java index 8ea952609..f2048fb55 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java @@ -13,8 +13,6 @@ import com.mongodb.DBObject; import io.swagger.annotations.ApiOperation; -import java.util.List; -import javax.validation.Valid; import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.cache.annotation.CacheConfig; @@ -31,15 +29,10 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.validation.Valid; +import java.util.List; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.project; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.skip; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.query.Criteria.where; /** diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index e958fdd09..ed98988a0 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Fields; import org.springframework.data.mongodb.core.aggregation.GroupOperation; import org.springframework.data.mongodb.core.aggregation.MatchOperation; @@ -74,6 +75,10 @@ protected Date getStartDate(final int year) { return start; } + protected List releaseAgg(Aggregation agg) { + return mongoTemplate.aggregate(agg, "release", DBObject.class).getMappedResults(); + } + /** * Creates a mongo $cond that calculates percentage of expression1/expression2. * Checks for division by zero. @@ -408,6 +413,10 @@ protected Criteria getSupplierIdCriteria(final DefaultFilterPagingRequest filter return createFilterCriteria("awards.suppliers._id", filter.getSupplierId(), filter); } + protected Criteria getBidderIdCriteria(final DefaultFilterPagingRequest filter) { + return createFilterCriteria("bids.details.tenderers._id", filter.getBidderId(), filter); + } + /** * Appends the procurement method for this filter, this will fitler based * on tender.procurementMethod @@ -484,7 +493,8 @@ protected Criteria getDefaultFilterCriteria(final DefaultFilterPagingRequest fil getFlaggedCriteria(filter), getFlagTypeFilterCriteria(filter), getElectronicSubmissionCriteria(filter), - getAwardStatusFilterCriteria(filter)); + getAwardStatusFilterCriteria(filter), + getBidderIdCriteria(filter)); } protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest filter, final String dateProperty) { @@ -502,7 +512,8 @@ protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest fi getFlaggedCriteria(filter), getFlagTypeFilterCriteria(filter), getYearFilterCriteria(filter, dateProperty), - getAwardStatusFilterCriteria(filter)); + getAwardStatusFilterCriteria(filter), + getBidderIdCriteria(filter)); } protected MatchOperation getMatchDefaultFilterOperation(final DefaultFilterPagingRequest filter) { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java index 2e7b82608..d3abe4935 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java @@ -40,11 +40,13 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { + " matches elements that are NOT in the TreeSet of Ids") private TreeSet notProcuringEntityId; - // @EachPattern(regexp = "^[\\p{L}0-9]*$") @ApiModelProperty(value = "This is the id of the organization/supplier entity. " + "Corresponds to the OCDS Organization.identifier") private TreeSet supplierId; + @ApiModelProperty(value = "This is the new bidder format bids.details.tenderers._id") + private TreeSet bidderId; + @ApiModelProperty(value = "This will filter after tender.items.deliveryLocation._id") private TreeSet tenderLoc; @@ -212,4 +214,12 @@ public TreeSet getAwardStatus() { public void setAwardStatus(TreeSet awardStatus) { this.awardStatus = awardStatus; } + + public TreeSet getBidderId() { + return bidderId; + } + + public void setBidderId(TreeSet bidderId) { + this.bidderId = bidderId; + } } diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties index f049bc606..73856ec78 100644 --- a/web/src/main/resources/allowedApiEndpoints.properties +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -70,4 +70,5 @@ allowedApiEndpoints=/api/tenderPriceByProcurementMethod**,\ /api/activeAwardsCount**,\ /api/translations/**,\ /api/ocds/release/count**,\ -/api/percentageAmountAwarded** \ No newline at end of file +/api/percentageAmountAwarded**,\ +/api/awardsByStatusCount** \ No newline at end of file From 1eeedbd423ddcc40cc13c958acbdb48eee90f6ce Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 09:10:09 +0200 Subject: [PATCH 382/702] OCE-409 Updated searchbox hint text --- ui/oce/corruption-risk/top-search/index.jsx | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/ui/oce/corruption-risk/top-search/index.jsx b/ui/oce/corruption-risk/top-search/index.jsx index 39f7d12c2..ad2b3be98 100644 --- a/ui/oce/corruption-risk/top-search/index.jsx +++ b/ui/oce/corruption-risk/top-search/index.jsx @@ -51,23 +51,8 @@ class TopSearch extends translatable(React.Component) {
- {hasSpecialChars &&
+
-
- „-“ is a special character meaning „without“. -
- For example - „ - foo -  - - bar - “ - will look for contracts containing -   - foo but not bar. -
- Use quotes or enable literal search if this is not the desired behavior. -
 
-
} +
); } From e6005e8653e19ed353c87c92d706659f2fa7253b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 09:22:47 +0200 Subject: [PATCH 383/702] OCE-436 Passing indicatorTypesMapping to contract page --- ui/oce/corruption-risk/index.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 8203a3e08..90276cd4b 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -83,7 +83,7 @@ class CorruptionRiskDashboard extends React.Component { const styling = this.constructor.STYLING || this.props.styling; const [page] = route; - const { appliedFilters, indicatorTypesMapping, width, data, allYears } = this.state; + const { appliedFilters, indicatorTypesMapping, width, data } = this.state; const { filters, years, months } = this.destructFilters(appliedFilters); const monthly = years.count() === 1; @@ -132,6 +132,9 @@ class CorruptionRiskDashboard extends React.Component { Component: ContractPage, sgSlug: 'contract', plSlug: 'contracts', + additionalProps: { + indicatorTypesMapping, + }, }); } else if (page === 'suppliers') { return this.renderArchive(SuppliersPage, 'suppliers'); @@ -284,7 +287,7 @@ class CorruptionRiskDashboard extends React.Component { ); } - renderSingle({ Component, sgSlug, plSlug }) { + renderSingle({ Component, sgSlug, plSlug, additionalProps }) { const { route, navigate } = this.props; const [, id] = route; return ( @@ -292,6 +295,7 @@ class CorruptionRiskDashboard extends React.Component { {...this.wireProps(sgSlug)} id={id} doSearch={query => navigate(plSlug, query)} + {...additionalProps} /> ); } From 137ba7004f5c694ace4f7e27570547fc9f58a127 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 13:32:15 +0200 Subject: [PATCH 384/702] OCE-412 Updated .oce-3-line-text and 3lining title and PEname columns for contracts --- ui/oce/corruption-risk/contracts/index.jsx | 4 +++- ui/oce/corruption-risk/style.less | 7 ++++++- ui/oce/corruption-risk/tools.jsx | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 66206c878..438e7dc2a 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -7,6 +7,8 @@ import { getAwardAmount, mkContractLink, wireProps } from '../tools'; import PaginatedTable from '../paginated-table'; import Archive from '../archive'; +const _3LineText = content =>
{content}
+ class CList extends PaginatedTable { getCustomEP() { const { searchQuery } = this.props; @@ -89,7 +91,7 @@ class CList extends PaginatedTable { {this.t('crd:general:contract:title')} - + {this.t('crd:contracts:list:procuringEntity')} diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index b7b10dcb9..f0bf879a6 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -542,7 +542,12 @@ .oce-3-line-text{ overflow: hidden; - max-height: 3.9em; + max-height: 4em; + display: block; + &:hover { + overflow: visible; + max-height: none; + } } .crd-landing-popup{ diff --git a/ui/oce/corruption-risk/tools.jsx b/ui/oce/corruption-risk/tools.jsx index 399bb2b05..5ab31b6ec 100644 --- a/ui/oce/corruption-risk/tools.jsx +++ b/ui/oce/corruption-risk/tools.jsx @@ -35,6 +35,7 @@ export const mkContractLink = navigate => (content, { id }) => ( navigate('contract', id)} + className="oce-3-line-text" > {content} From e4f02f93e224a50df29df1e3a3d0e9dec4441027 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 14:02:07 +0200 Subject: [PATCH 385/702] OCUA-34 OCE-412 Moved _3LineText to tools and applied to procurement tables --- ui/oce/corruption-risk/contracts/index.jsx | 4 +--- ui/oce/corruption-risk/contracts/style.less | 6 ++---- ui/oce/corruption-risk/procurements-table.jsx | 5 ++--- ui/oce/corruption-risk/tools.jsx | 2 ++ 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 438e7dc2a..953ac5ae1 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -3,12 +3,10 @@ import { List } from 'immutable'; // eslint-disable-next-line no-unused-vars import rbtStyles from 'react-bootstrap-table/dist/react-bootstrap-table-all.min.css'; import CRDPage from '../page'; -import { getAwardAmount, mkContractLink, wireProps } from '../tools'; +import { getAwardAmount, mkContractLink, wireProps, _3LineText } from '../tools'; import PaginatedTable from '../paginated-table'; import Archive from '../archive'; -const _3LineText = content =>
{content}
- class CList extends PaginatedTable { getCustomEP() { const { searchQuery } = this.props; diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index e384fa861..1ba75455e 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -14,10 +14,8 @@ } } -.contract-page, .contracts-page { - .react-bs-table table td, .react-bs-table table th{ - white-space: normal; - } +.react-bs-table table td, .react-bs-table table th{ + white-space: normal; } .contract-page { diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index 2b291f8a9..a794d01af 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -1,10 +1,9 @@ import ReactDOM from 'react-dom'; import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; import { List } from 'immutable'; -import Table from '../visualizations/tables'; import translatable from '../translatable'; import { POPUP_HEIGHT } from './constants'; -import { getAwardAmount, mkContractLink } from './tools'; +import { getAwardAmount, mkContractLink, _3LineText } from './tools'; import PaginatedTable from './paginated-table'; // eslint-disable-next-line no-undef @@ -149,7 +148,7 @@ class ProcurementsTable extends PaginatedTable { {this.t('crd:procurementsTable:title')}
- + {this.t('crd:procurementsTable:procuringEntity')} diff --git a/ui/oce/corruption-risk/tools.jsx b/ui/oce/corruption-risk/tools.jsx index 5ab31b6ec..58ecbe195 100644 --- a/ui/oce/corruption-risk/tools.jsx +++ b/ui/oce/corruption-risk/tools.jsx @@ -64,3 +64,5 @@ export function wireProps(parent, prefix) { } return props; } + +export const _3LineText = content =>
{content}
From 7610c1545ac15dda2411cc3b2997249c7abde37b Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 14:30:00 +0200 Subject: [PATCH 386/702] OCUA-34 Increased ocid column, decreased date column --- ui/oce/corruption-risk/contracts/index.jsx | 10 ++++++++-- ui/oce/corruption-risk/contracts/style.less | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/index.jsx b/ui/oce/corruption-risk/contracts/index.jsx index 953ac5ae1..656b91a9d 100644 --- a/ui/oce/corruption-risk/contracts/index.jsx +++ b/ui/oce/corruption-risk/contracts/index.jsx @@ -81,7 +81,13 @@ class CList extends PaginatedTable { {this.t('crd:contracts:baseInfo:status')}
- + {this.t('crd:procurementsTable:contractID')} @@ -101,7 +107,7 @@ class CList extends PaginatedTable { {this.t('crd:contracts:list:awardAmount')} - + {this.t('crd:procurementsTable:tenderDate')} diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 1ba75455e..592103adf 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -2,7 +2,15 @@ margin-bottom: 40px; table { th, td{ - width: 12.5%; + @width: 12.5%; + @ocidDateDelta: 3%; + width: @width; + &.ocid { + width: @width + @ocidDateDelta; + } + &.date { + width: @width - @ocidDateDelta; + } } th{ color: #a1adbc; From 3718dbbe98143100b0f7e9f885c5030d72c23c5d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 14:39:10 +0200 Subject: [PATCH 387/702] OCE-406 Removed space above crosstabs --- ui/oce/corruption-risk/contracts/style.less | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/style.less b/ui/oce/corruption-risk/contracts/style.less index 592103adf..e06e35991 100644 --- a/ui/oce/corruption-risk/contracts/style.less +++ b/ui/oce/corruption-risk/contracts/style.less @@ -34,10 +34,6 @@ padding-top: 50px; } - h3 { - margin-top: 70px; - } - div.crosstab-box { background: #f0f0f0; font-size: .9em; From 2959078e6b209403e8c45b0f7ebc9e3e222699e6 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 17:04:45 +0200 Subject: [PATCH 388/702] OCE-420 Linting --- ui/oce/corruption-risk/crosstab.jsx | 113 ++++++++++++++-------------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/ui/oce/corruption-risk/crosstab.jsx b/ui/oce/corruption-risk/crosstab.jsx index b0b9cfcb1..469685618 100644 --- a/ui/oce/corruption-risk/crosstab.jsx +++ b/ui/oce/corruption-risk/crosstab.jsx @@ -1,37 +1,38 @@ -import Table from "../visualizations/tables/index"; +import Table from '../visualizations/tables/index'; -class Crosstab extends Table{ - getCustomEP(){ - const {indicators} = this.props; +class Crosstab extends Table { + getCustomEP() { + const { indicators } = this.props; return indicators.map(indicator => `flags/${indicator}/crosstab`); } - componentDidUpdate(prevProps, ...args){ - if(this.props.indicators != prevProps.indicators){ + componentDidUpdate(prevProps, ...args) { + if (this.props.indicators !== prevProps.indicators) { this.fetch(); } super.componentDidUpdate(prevProps, ...args); } - transform(data){ - const {indicators} = this.props; - let matrix = {}, x = 0, y = 0; - for(x = 0; x{rowIndicatorName} {rowData.getIn([rowIndicatorID, 'count'])} {rowData.map((datum, indicatorID) => { - const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); - const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); - if(indicatorID == rowIndicatorID){ - return — - } else { - const percent = datum.get('percent'); - const count = datum.get('count'); - const color = Math.round(255 - 255 * (percent/100)) - const style = {backgroundColor: `rgb(${color}, 255, ${color})`} - return ( - - {percent && percent.toFixed(2)} % -
-
-
- {this.t('crd:corruptionType:crosstab:popup:percents') - .replace('$#$', percent.toFixed(2)) - .replace('$#$', rowIndicatorName) - .replace('$#$', indicatorName)} -
-
-
-
-
-

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

-

{rowIndicatorName}: {rowIndicatorDescription}

-

{this.t('crd:corruptionType:crosstab:popup:and')}

-

{indicatorName}: {indicatorDescription}

-
-
-
-
- - ) - } + const indicatorName = this.t(`crd:indicators:${indicatorID}:name`); + const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); + if (indicatorID === rowIndicatorID) { + return —; + } else { + const percent = datum.get('percent'); + const count = datum.get('count'); + const color = Math.round(255 - 255 * (percent / 100)); + const style = { backgroundColor: `rgb(${color}, 255, ${color})` }; + return ( + + {percent && percent.toFixed(2)} % +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
+
+
+

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+

{rowIndicatorName}: {rowIndicatorDescription}

+

{this.t('crd:corruptionType:crosstab:popup:and')}

+

{indicatorName}: {indicatorDescription}

+
+
+
+
+ + ); + } }).toArray()} ) } - render(){ - const {indicators, data} = this.props; - if(!data) return null; - if(!data.count()) return null; + render() { + const { data } = this.props; + if (!data) return null; + if (!data.count()) return null; return ( @@ -103,7 +104,7 @@ class Crosstab extends Table{ {data.map((rowData, indicatorID) => this.row(rowData, indicatorID)).toArray()}
- ) + ); } } From 29b8cf7db48276a3fdcdc464860b7f0eb7fd74ad Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 17:33:12 +0200 Subject: [PATCH 389/702] OCE-420 Updated popup-right CSS --- ui/oce/corruption-risk/style.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index f0bf879a6..45027ff5b 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -522,10 +522,11 @@ &.popup-left:hover .crd-popup{ left: auto; right: 100%; - top: -100%; + top: 50%; bottom: auto; margin-left: 0; margin-right: @arrowSize; + transform: translateY(-50%); } } From 5f9c9bf1a054f2ce599708bd619919caadf1c0ed Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 17:39:54 +0200 Subject: [PATCH 390/702] OCE-420 Fix for pagination covering the popup --- ui/oce/corruption-risk/style.less | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/oce/corruption-risk/style.less b/ui/oce/corruption-risk/style.less index 45027ff5b..0fed9da24 100644 --- a/ui/oce/corruption-risk/style.less +++ b/ui/oce/corruption-risk/style.less @@ -497,6 +497,7 @@ padding: 15px; height: auto; display: none; + z-index: 10; .and{ text-transform: uppercase; color: #387ab2; From d08cb22c42f6a89447dcc9e4225161c86f001366 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 17:40:08 +0200 Subject: [PATCH 391/702] OCE-420 Fix for procurement tables popup mispositioning --- ui/oce/corruption-risk/procurements-table.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/procurements-table.jsx b/ui/oce/corruption-risk/procurements-table.jsx index a794d01af..09769c9b5 100644 --- a/ui/oce/corruption-risk/procurements-table.jsx +++ b/ui/oce/corruption-risk/procurements-table.jsx @@ -19,7 +19,7 @@ class Popup extends translatable(React.Component) { const { type, flagIds } = this.props; const { popupTop } = this.state; return ( -
+
{this.t('crd:procurementsTable:associatedFlags').replace('$#$', this.t(`crd:corruptionType:${type}:name`))}
From 5bf8516a0a4a9c5e55293aa581ced9b326193da7 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 25 Dec 2017 17:40:36 +0200 Subject: [PATCH 392/702] OCE-420 Left popups for crosstabs --- ui/oce/corruption-risk/crosstab.jsx | 60 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/ui/oce/corruption-risk/crosstab.jsx b/ui/oce/corruption-risk/crosstab.jsx index 469685618..4892362d8 100644 --- a/ui/oce/corruption-risk/crosstab.jsx +++ b/ui/oce/corruption-risk/crosstab.jsx @@ -1,3 +1,4 @@ +import cn from 'classnames'; import Table from '../visualizations/tables/index'; class Crosstab extends Table { @@ -42,6 +43,7 @@ class Crosstab extends Table { row(rowData, rowIndicatorID){ const rowIndicatorName = this.t(`crd:indicators:${rowIndicatorID}:name`); const rowIndicatorDescription = this.t(`crd:indicators:${rowIndicatorID}:indicator`); + const lastKey = rowData.lastKeyOf(rowData.last()); return ( {rowIndicatorName} @@ -51,37 +53,37 @@ class Crosstab extends Table { const indicatorDescription = this.t(`crd:indicators:${indicatorID}:indicator`); if (indicatorID === rowIndicatorID) { return —; - } else { - const percent = datum.get('percent'); - const count = datum.get('count'); - const color = Math.round(255 - 255 * (percent / 100)); - const style = { backgroundColor: `rgb(${color}, 255, ${color})` }; - return ( - - {percent && percent.toFixed(2)} % -
-
-
- {this.t('crd:corruptionType:crosstab:popup:percents') - .replace('$#$', percent.toFixed(2)) - .replace('$#$', rowIndicatorName) - .replace('$#$', indicatorName)} -
-
-
-
-
-

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

-

{rowIndicatorName}: {rowIndicatorDescription}

-

{this.t('crd:corruptionType:crosstab:popup:and')}

-

{indicatorName}: {indicatorDescription}

-
+ } + const percent = datum.get('percent'); + const count = datum.get('count'); + const color = Math.round(255 - 255 * (percent / 100)); + const style = { backgroundColor: `rgb(${color}, 255, ${color})` }; + const isLast = indicatorID === lastKey; + return ( + + {percent && percent.toFixed(2)} % +
+
+
+ {this.t('crd:corruptionType:crosstab:popup:percents') + .replace('$#$', percent.toFixed(2)) + .replace('$#$', rowIndicatorName) + .replace('$#$', indicatorName)} +
+
+
+
+
+

{this.t('crd:corruptionType:crosstab:popup:count').replace('$#$', count)}

+

{rowIndicatorName}: {rowIndicatorDescription}

+

{this.t('crd:corruptionType:crosstab:popup:and')}

+

{indicatorName}: {indicatorDescription}

-
- - ); - } +
+
+ + ); }).toArray()} ) From 9967ba74adf1a9a06e39c737d0046f16399a0cce Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 17:03:15 +0200 Subject: [PATCH 393/702] OCE-414 wireProps now accepts an array as slug --- ui/oce/corruption-risk/index.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 90276cd4b..e5ab5f953 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -178,14 +178,16 @@ class CorruptionRiskDashboard extends React.Component { } wireProps(slug) { + wireProps(_slug) { + const slug = Array.isArray(_slug) ? _slug : [_slug]; const translations = this.getTranslations(); const { appliedFilters, width } = this.state; const { filters, years, months } = this.destructFilters(appliedFilters); return { translations, - data: this.state.data.get(slug, Map()), + data: this.state.data.getIn(slug, Map()), requestNewData: (path, newData) => - this.setState({ data: this.state.data.setIn([slug].concat(path), newData) }), + this.setState({ data: this.state.data.setIn(slug.concat(path), newData) }), filters, years, monthly: years.count() === 1, From aa764b31bcdbde017d36368233e4b6bc85b3a623 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 17:50:34 +0200 Subject: [PATCH 394/702] OCE-414 Not sending years if all are checked --- ui/oce/corruption-risk/index.jsx | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index e5ab5f953..1fae35a6e 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -97,14 +97,10 @@ class CorruptionRiskDashboard extends React.Component { return ( navigate('indicator', corruptionType, individualIndicator)} - filters={filters} - translations={translations} corruptionType={corruptionType} - years={years} - monthly={monthly} - months={months} width={width} styling={styling} /> @@ -113,13 +109,9 @@ class CorruptionRiskDashboard extends React.Component { const [, corruptionType, individualIndicator] = route; return ( - this.setState({ data: this.state.data.setIn(['overview'].concat(path), newData) })} + {...this.wireProps('overview')} indicatorTypesMapping={indicatorTypesMapping} styling={styling} width={width} @@ -177,12 +162,15 @@ class CorruptionRiskDashboard extends React.Component { return TRANSLATIONS[locale]; } - wireProps(slug) { wireProps(_slug) { const slug = Array.isArray(_slug) ? _slug : [_slug]; const translations = this.getTranslations(); - const { appliedFilters, width } = this.state; - const { filters, years, months } = this.destructFilters(appliedFilters); + const { appliedFilters, allYears, width } = this.state; + const { filters, years: selectedYears, months } = this.destructFilters(appliedFilters); + const years = Set(allYears).equals(selectedYears) ? + Set() : + selectedYears; + return { translations, data: this.state.data.getIn(slug, Map()), From 22f3e8992fc09d962d962706ea373b3616636921 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 18:04:35 +0200 Subject: [PATCH 395/702] OCE-414 frontendDateFilterable no longer filters data if no years are sent --- .../frontend-date-filterable.es6 | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/ui/oce/visualizations/frontend-date-filterable.es6 b/ui/oce/visualizations/frontend-date-filterable.es6 index 26c0e8516..865459025 100644 --- a/ui/oce/visualizations/frontend-date-filterable.es6 +++ b/ui/oce/visualizations/frontend-date-filterable.es6 @@ -1,28 +1,31 @@ -import {Set} from "immutable"; -import {cacheFn} from "../tools"; +import { Set } from 'immutable'; +import { cacheFn } from '../tools'; -let frontendDateFilterable = Class => { - class Filterable extends Class{ - buildUrl(...args){ +const frontendDateFilterable = (Class) => { + class Filterable extends Class { + buildUrl(...args) { const url = super.buildUrl(...args); return this.props.monthly ? url.addSearch('monthly', true).addSearch('year', this.props.years.toArray()) : url; } - getData(){ - let data = super.getData(); - let {years, monthly, months} = this.props; - if(!data) return data; - return monthly ? - this.constructor.filterDataByMonth(data, months) : - this.constructor.filterDataByYears(data, years); + getData() { + const data = super.getData(); + const { years, monthly, months } = this.props; + if (!data) return data; + if (monthly) { + return this.constructor.filterDataByMonth(data, months); + } else if (years.count()) { + return this.constructor.filterDataByYears(data, years); + } + return data; } - componentDidUpdate(prevProps){ - const monthlyToggled = this.props.monthly != prevProps.monthly; - const isMonthlyAndYearChanged = this.props.monthly && this.props.years != prevProps.years; - if(monthlyToggled || isMonthlyAndYearChanged){ + componentDidUpdate(prevProps) { + const monthlyToggled = this.props.monthly !== prevProps.monthly; + const isMonthlyAndYearChanged = this.props.monthly && this.props.years !== prevProps.years; + if (monthlyToggled || isMonthlyAndYearChanged) { this.fetch(); } else super.componentDidUpdate(prevProps); } @@ -31,11 +34,9 @@ let frontendDateFilterable = Class => { Filterable.propTypes = Filterable.propTypes || {}; Filterable.propTypes.years = React.PropTypes.object.isRequired; - Filterable.computeYears = cacheFn(data => { - if(!data) return Set(); - return Set(data.map(datum => { - return +datum.get('year') - })); + Filterable.computeYears = cacheFn((data) => { + if (!data) return Set(); + return Set(data.map(datum => +datum.get('year'))); }); const filterDataByDate = (field, data, dates) => From bf02a7e3c173246b605c9df99445b53a331263f4 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 18:07:33 +0200 Subject: [PATCH 396/702] OCE-414 Wiring props to sidebar --- ui/oce/corruption-risk/index.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index 1fae35a6e..ee0a5df23 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -363,19 +363,15 @@ class CorruptionRiskDashboard extends React.Component { allMonths={allMonths} /> this.setState({ data: this.state.data.setIn(path, newData) })} - filters={filters} - years={years} allYears={allYears} - monthly={monthly} - months={months} />
{this.getPage()} From c3fb95d6ec6c5db5e17e29300ed447729e722034 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 18:28:43 +0200 Subject: [PATCH 397/702] OCE-405 Removed nrOfBidders voodoo --- .../corruption-risk/contracts/single/donuts/nr-of-bidders.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx index d32973838..b20aa98bb 100644 --- a/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx +++ b/ui/oce/corruption-risk/contracts/single/donuts/nr-of-bidders.jsx @@ -35,13 +35,12 @@ NrOfBidders.Donut = class extends CenterTextDonut.Donut { const avg = super.getData(); const { count } = this.props; if (isNaN(avg) || isNaN(count)) return []; - return [{ labels: [ this.t('crd:contract:nrBiddersVsAvg:thisLabel'), this.t('crd:contract:nrBiddersVsAvg:avgLabel') ], - values: [count == 1 ? count : count * avg, avg], + values: [count, avg], hoverlabel: { bgcolor: '#144361' }, From d65fab8a09d4f642f5adbbda9aae6afb515ce39d Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 19:48:26 +0200 Subject: [PATCH 398/702] OCE-414 Cleanup --- ui/oce/corruption-risk/index.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/oce/corruption-risk/index.jsx b/ui/oce/corruption-risk/index.jsx index ee0a5df23..6c2c25de4 100644 --- a/ui/oce/corruption-risk/index.jsx +++ b/ui/oce/corruption-risk/index.jsx @@ -79,14 +79,10 @@ class CorruptionRiskDashboard extends React.Component { getPage() { const { route, navigate } = this.props; - const translations = this.getTranslations(); const styling = this.constructor.STYLING || this.props.styling; const [page] = route; - const { appliedFilters, indicatorTypesMapping, width, data } = this.state; - - const { filters, years, months } = this.destructFilters(appliedFilters); - const monthly = years.count() === 1; + const { appliedFilters, indicatorTypesMapping, width } = this.state; if (page === 'type') { const [, corruptionType] = route; From 8df30cb864fa67a4c3d4d1461840bea7c5fb7aee Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 19:48:50 +0200 Subject: [PATCH 399/702] OCE-414 Special case for unselected years for overiew table --- ui/oce/corruption-risk/overview-page/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/oce/corruption-risk/overview-page/index.jsx b/ui/oce/corruption-risk/overview-page/index.jsx index a60bf64b7..2030cc819 100644 --- a/ui/oce/corruption-risk/overview-page/index.jsx +++ b/ui/oce/corruption-risk/overview-page/index.jsx @@ -46,9 +46,12 @@ class CorruptionType extends CustomPopupChart { .map(month => this.t(`general:months:${month}`)); values = dates.map(month => (dataForType[month] ? dataForType[month].flaggedCount : 0)); - } else { + } else if (years.count()) { dates = years.sort().toArray(); values = dates.map(year => (dataForType[year] ? dataForType[year].flaggedCount : 0)); + } else { + dates = Object.keys(dataForType).sort(); + values = dates.map(year => dataForType[year] ? dataForType[year].flaggedCount : 0); } if (dates.length === 1) { From cb710239c2763aae20ee17e60fb5e27ad14b9991 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Wed, 27 Dec 2017 20:18:41 +0200 Subject: [PATCH 400/702] OCE-428 Mocks for supplier list --- ui/oce/corruption-risk/suppliers/index.jsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/suppliers/index.jsx b/ui/oce/corruption-risk/suppliers/index.jsx index 3474d1e0d..2f33126a6 100644 --- a/ui/oce/corruption-risk/suppliers/index.jsx +++ b/ui/oce/corruption-risk/suppliers/index.jsx @@ -42,7 +42,11 @@ class SList extends PaginatedTable { const jsData = data.get('data', List()).map((supplier) => { return { id: supplier.get('id'), - name: supplier.get('name') + name: supplier.get('name'), + wins: -Math.round(Math.random() * 4), + winAmount: -Math.round(Math.random() * 1000000), + lost: -Math.round(Math.random() * 4), + flags: -Math.round(Math.random() * 4), } }).toJS(); @@ -65,11 +69,23 @@ class SList extends PaginatedTable { paginationPosition: 'both', }} > + + Name + ID - - Name + + Wins + + + Total won + + + Losses + + + Total No. Flags ); From e8fce2d5866772b137df35b748b85de33e00e928 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Dec 2017 14:42:00 +0200 Subject: [PATCH 401/702] OCE-440 Real data for supplier 3rd donut --- ui/oce/corruption-risk/donut/index.jsx | 11 ++++---- .../single/donuts/amount-lost-vs-won.jsx | 7 +++-- .../suppliers/single/donuts/nr-flags.jsx | 26 +++++++++---------- .../single/donuts/nr-lost-vs-won.jsx | 7 +++-- .../suppliers/single/index.jsx | 4 +-- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/ui/oce/corruption-risk/donut/index.jsx b/ui/oce/corruption-risk/donut/index.jsx index 6b17fe20f..cad1abaa8 100644 --- a/ui/oce/corruption-risk/donut/index.jsx +++ b/ui/oce/corruption-risk/donut/index.jsx @@ -8,14 +8,14 @@ import DonutPopup from './popup'; class Donut extends backendYearFilterable(Chart) { getCustomEP() { - return 'ocds/release/count'; + return this.props.endpoint || 'ocds/release/count'; } getData() { - const { data, values } = this.props; + const { data } = this.props; return [{ - labels: values.map(pluck('label')), - values: data, + labels: data.map(pluck('label')), + values: data.map(pluck('value')), hoverlabel: { bgcolor: '#144361', }, @@ -24,7 +24,7 @@ class Donut extends backendYearFilterable(Chart) { hole: 0.8, type: 'pie', marker: { - colors: values.map(pluck('color')), + colors: data.map(pluck('color')), }, }]; } @@ -45,6 +45,7 @@ class DonutWrapper extends React.PureComponent {
@@ -26,12 +27,14 @@ class AmountWonVsLost extends React.Component { CenterText={CenterText} title="$ Amount" subtitle="Won vs Lost" - values={[{ + data={[{ color: '#72c47e', label: 'Won', + value: 1000000, }, { color: '#2e833a', label: 'Lost', + value: 2000000, }]} /> ); diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx index 148df6af7..f3a7db0f5 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx @@ -1,11 +1,12 @@ +import { List } from 'immutable'; import Donut from '../../../donut'; import { pluck } from '../../../../tools'; class CenterText extends React.PureComponent { render() { - const { data, values } = this.props; - const [fst, snd, thrd] = data; - const [fstColor, sndColor, thrdColor] = values.map(pluck('color')); + const { data } = this.props; + const [fst, snd, thrd] = data.map(pluck('value')); + const [fstColor, sndColor, thrdColor] = data.map(pluck('color')); return (
{fst}/ @@ -16,24 +17,23 @@ class CenterText extends React.PureComponent { } } +const COLORS = ['#fbc42c', '#3372b2', '#30a0f5'] + class TotalFlags extends React.Component { render() { + const data = (this.props.data || List()) .map((datum, index) => ({ + color: COLORS[index], + label: datum.get('type'), + value: datum.get('indicatorCount'), + })).toJS(); return ( ); } diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx index 3968e4609..df50d250b 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-lost-vs-won.jsx @@ -1,9 +1,10 @@ +import { pluck } from '../../../../tools'; import Donut from '../../../donut'; class CenterText extends React.Component { render() { const { data } = this.props; - const [fst, snd] = data; + const [fst, snd] = data.map(pluck('value')); const sum = fst + snd; const percent = (fst / sum) * 100; return ( @@ -27,12 +28,14 @@ class NrWonVsLost extends React.PureComponent { CenterText={CenterText} title="# and % Contracts" subtitle="Won vs Lost" - values={[{ + data={[{ color: '#165781', label: 'Won', + value: 1, }, { color: '#5fa0c9', label: 'Lost', + value: 2, }]} /> ); diff --git a/ui/oce/corruption-risk/suppliers/single/index.jsx b/ui/oce/corruption-risk/suppliers/single/index.jsx index ecdec8010..10c61112a 100644 --- a/ui/oce/corruption-risk/suppliers/single/index.jsx +++ b/ui/oce/corruption-risk/suppliers/single/index.jsx @@ -114,14 +114,12 @@ class Supplier extends CRDPage {
From bf9785b762845f8825ca96d0a56b492a61c7b5e2 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Dec 2017 14:42:17 +0200 Subject: [PATCH 402/702] OCE-440 Wireprops now accepts array as path --- ui/oce/corruption-risk/tools.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/oce/corruption-risk/tools.jsx b/ui/oce/corruption-risk/tools.jsx index 58ecbe195..f819c4996 100644 --- a/ui/oce/corruption-risk/tools.jsx +++ b/ui/oce/corruption-risk/tools.jsx @@ -52,11 +52,12 @@ export function cherrypickProps(keys, source) { return target; } -export function wireProps(parent, prefix) { +export function wireProps(parent, _prefix) { const { props: parentProps } = parent; const props = cherrypickProps(ROUTINE_PROPS, parentProps); - if (prefix) { - props.data = parentProps.data.get(prefix); + if (_prefix) { + const prefix = Array.isArray(_prefix) ? _prefix : [_prefix]; + props.data = parentProps.data.getIn(prefix); props.requestNewData = (path, data) => parentProps.requestNewData(path.concat(prefix), data); } else { From d183a802e3a552908f878b61f5e275adcaaa706c Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Dec 2017 15:20:32 +0200 Subject: [PATCH 403/702] OCE-440 Real data for supplier 3rd donut and bugfixes --- .../suppliers/single/donuts/nr-flags.jsx | 15 +++++++-------- ui/oce/corruption-risk/suppliers/single/index.jsx | 15 ++++++++++++--- .../corruption-risk/suppliers/single/style.less | 8 +++++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx index f3a7db0f5..2fc4902af 100644 --- a/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx +++ b/ui/oce/corruption-risk/suppliers/single/donuts/nr-flags.jsx @@ -1,17 +1,16 @@ import { List } from 'immutable'; import Donut from '../../../donut'; import { pluck } from '../../../../tools'; +import translatable from '../../../../translatable'; class CenterText extends React.PureComponent { render() { const { data } = this.props; - const [fst, snd, thrd] = data.map(pluck('value')); - const [fstColor, sndColor, thrdColor] = data.map(pluck('color')); return ( -
- {fst}/ - {snd}/ - {thrd} +
+ {data.map(({ color, value }) => + {value} + )}
); } @@ -19,11 +18,11 @@ class CenterText extends React.PureComponent { const COLORS = ['#fbc42c', '#3372b2', '#30a0f5'] -class TotalFlags extends React.Component { +class TotalFlags extends translatable(React.PureComponent) { render() { const data = (this.props.data || List()) .map((datum, index) => ({ color: COLORS[index], - label: datum.get('type'), + label: this.t(`crd:corruptionType:${datum.get('type')}:name`), value: datum.get('indicatorCount'), })).toJS(); return ( diff --git a/ui/oce/corruption-risk/suppliers/single/index.jsx b/ui/oce/corruption-risk/suppliers/single/index.jsx index 10c61112a..71d0de065 100644 --- a/ui/oce/corruption-risk/suppliers/single/index.jsx +++ b/ui/oce/corruption-risk/suppliers/single/index.jsx @@ -1,4 +1,4 @@ -import { Map } from 'immutable'; +import { Map, Set } from 'immutable'; import TopSearch from '../../top-search'; import translatable from '../../../translatable'; import Visualization from '../../../visualization'; @@ -9,6 +9,7 @@ import NrLostVsWon from './donuts/nr-lost-vs-won'; import AmountLostVsWon from './donuts/amount-lost-vs-won'; import NrFlags from './donuts/nr-flags'; import styles from './style.less'; +import { cacheFn } from '../../../tools'; class Info extends translatable(Visualization) { getCustomEP() { @@ -84,8 +85,15 @@ class Info extends translatable(Visualization) { } class Supplier extends CRDPage { + constructor(...args) { + super(...args); + this.injectSupplierFilter = cacheFn((filters, supplierId) => { + return filters.update('supplierId', Set(), supplierIds => supplierIds.add(supplierId)); + }); + } + render() { - const { translations, width, doSearch, id } = this.props; + const { translations, width, doSearch, id, filters } = this.props; const donutSize = width / 3 - 100; return ( @@ -108,7 +116,7 @@ class Supplier extends CRDPage { {...wireProps(this, 'nr-lost-vs-won')} width={donutSize} data={[1, 2]} - /> + />
diff --git a/ui/oce/corruption-risk/suppliers/single/style.less b/ui/oce/corruption-risk/suppliers/single/style.less index 0e0e0195d..f16288146 100644 --- a/ui/oce/corruption-risk/suppliers/single/style.less +++ b/ui/oce/corruption-risk/suppliers/single/style.less @@ -1,4 +1,10 @@ .center-text.two-rows .secondary { font-size: .3em; margin-top: -1em; -} \ No newline at end of file +} + +.total-flags-center-text span:not(:last-child)::after{ + display: inline-block; + content: '/'; + color: #144361; +} From 65ae66b70fb333beca54611fdf930aa45219adec Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Dec 2017 16:37:13 +0200 Subject: [PATCH 404/702] OCE-426 No data msg for nr flags chart --- ui/oce/corruption-risk/donut/index.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/donut/index.jsx b/ui/oce/corruption-risk/donut/index.jsx index cad1abaa8..66c074db3 100644 --- a/ui/oce/corruption-risk/donut/index.jsx +++ b/ui/oce/corruption-risk/donut/index.jsx @@ -11,8 +11,13 @@ class Donut extends backendYearFilterable(Chart) { return this.props.endpoint || 'ocds/release/count'; } + hasNoData() { + return this.props.data && !this.props.data.length; + } + getData() { const { data } = this.props; + if (!data.length) return []; return [{ labels: data.map(pluck('label')), values: data.map(pluck('value')), @@ -39,7 +44,7 @@ class Donut extends backendYearFilterable(Chart) { class DonutWrapper extends React.PureComponent { render() { - const { title, subtitle, className, values, data, CenterText } = this.props; + const { title, subtitle, className, data, CenterText } = this.props; return (
@@ -48,12 +53,9 @@ class DonutWrapper extends React.PureComponent { endpoint={this.props.endpoint} margin={{ b: 0, t: 0, r: 0, l: 0, pad: 0 }} height={300} - data={data} - values={values} />

From e2f077f809c2068a5398592f04d74f898f6c844f Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 29 Dec 2017 16:38:02 +0200 Subject: [PATCH 405/702] OCE-426 Unmocked flagcount --- ui/oce/corruption-risk/suppliers/single/index.jsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ui/oce/corruption-risk/suppliers/single/index.jsx b/ui/oce/corruption-risk/suppliers/single/index.jsx index 71d0de065..901e1e454 100644 --- a/ui/oce/corruption-risk/suppliers/single/index.jsx +++ b/ui/oce/corruption-risk/suppliers/single/index.jsx @@ -1,4 +1,5 @@ import { Map, Set } from 'immutable'; +import { List } from 'immutable'; import TopSearch from '../../top-search'; import translatable from '../../../translatable'; import Visualization from '../../../visualization'; @@ -9,7 +10,9 @@ import NrLostVsWon from './donuts/nr-lost-vs-won'; import AmountLostVsWon from './donuts/amount-lost-vs-won'; import NrFlags from './donuts/nr-flags'; import styles from './style.less'; -import { cacheFn } from '../../../tools'; +import { cacheFn, pluckImm } from '../../../tools'; + +const add = (a, b) => a + b; class Info extends translatable(Visualization) { getCustomEP() { @@ -18,12 +21,14 @@ class Info extends translatable(Visualization) { } render() { - const { data } = this.props; + const { data, flagCount: _flagCount } = this.props; + const flagCount = (_flagCount || List()) + .map(pluckImm('indicatorCount')).reduce(add, 0); + if(!data) return null; const address = data.get('address'); const contact = data.get('contactPoint'); - const flagCount = -1; return (
@@ -93,7 +98,7 @@ class Supplier extends CRDPage { } render() { - const { translations, width, doSearch, id, filters } = this.props; + const { translations, width, doSearch, id, filters, data } = this.props; const donutSize = width / 3 - 100; return ( @@ -107,6 +112,7 @@ class Supplier extends CRDPage { {...wireProps(this, 'info')} id={id} filters={Map()} + flagCount={data.get('nr-flags')} />
From 454aaf07ea653199ace7f6ad5d467e2a9c81ea04 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Thu, 4 Jan 2018 18:45:34 +0200 Subject: [PATCH 406/702] OCE-439 Added new visualization component --- ui/oce/corruption-risk/new-visualization.jsx | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 ui/oce/corruption-risk/new-visualization.jsx diff --git a/ui/oce/corruption-risk/new-visualization.jsx b/ui/oce/corruption-risk/new-visualization.jsx new file mode 100644 index 000000000..b22ed8412 --- /dev/null +++ b/ui/oce/corruption-risk/new-visualization.jsx @@ -0,0 +1,47 @@ +import URI from 'urijs'; +import { callFunc } from '../tools'; + +const API_ROOT = '/api'; + +const fetchEP = url => fetch(url.clone().query(''), { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + credentials: 'same-origin', + body: url.query(), +}).then(callFunc('json')); + +const cloneChild = (component, props) => + React.cloneElement( + React.Children.only(component.props.children), + props + ); + +class Visualization extends React.PureComponent { + fetch() { + const { filters, endpoint, requestNewData } = this.props; + const uri = new URI(`${API_ROOT}/${endpoint}`).addSearch(filters.toJS()); + fetchEP(uri).then(requestNewData.bind(null, [])); + } + + componentDidMount() { + this.fetch(); + } + + componentDidUpdate(prevProps) { + if (['filters', 'endpoint'].some(prop => this.props[prop] != prevProps[prop])) { + this.fetch(); + } + } + + render() { + const { data } = this.props; + if (!data) return null; + return cloneChild(this, { + data + }); + } +} + +export default Visualization; From 78739af3ccd551ac584858cd50a35cd10f3110b0 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 5 Jan 2018 13:08:41 +0200 Subject: [PATCH 407/702] OCE-439 Renamed NewVisualization to DataFetcher --- .../{new-visualization.jsx => data-fetcher.jsx} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename ui/oce/corruption-risk/{new-visualization.jsx => data-fetcher.jsx} (89%) diff --git a/ui/oce/corruption-risk/new-visualization.jsx b/ui/oce/corruption-risk/data-fetcher.jsx similarity index 89% rename from ui/oce/corruption-risk/new-visualization.jsx rename to ui/oce/corruption-risk/data-fetcher.jsx index b22ed8412..1c3e19ab1 100644 --- a/ui/oce/corruption-risk/new-visualization.jsx +++ b/ui/oce/corruption-risk/data-fetcher.jsx @@ -18,7 +18,7 @@ const cloneChild = (component, props) => props ); -class Visualization extends React.PureComponent { +class DataFetcher extends React.PureComponent { fetch() { const { filters, endpoint, requestNewData } = this.props; const uri = new URI(`${API_ROOT}/${endpoint}`).addSearch(filters.toJS()); @@ -31,6 +31,7 @@ class Visualization extends React.PureComponent { componentDidUpdate(prevProps) { if (['filters', 'endpoint'].some(prop => this.props[prop] != prevProps[prop])) { + this.props.requestNewData([], null); this.fetch(); } } @@ -44,4 +45,4 @@ class Visualization extends React.PureComponent { } } -export default Visualization; +export default DataFetcher; From 8dece9bb8f266e94b0e1a767d7f99f0dc1b955be Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 5 Jan 2018 14:15:19 +0200 Subject: [PATCH 408/702] OCE-439 Moved cloneChild to tools --- ui/oce/corruption-risk/data-fetcher.jsx | 7 +------ ui/oce/corruption-risk/tools.jsx | 6 ++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/oce/corruption-risk/data-fetcher.jsx b/ui/oce/corruption-risk/data-fetcher.jsx index 1c3e19ab1..b1c87f602 100644 --- a/ui/oce/corruption-risk/data-fetcher.jsx +++ b/ui/oce/corruption-risk/data-fetcher.jsx @@ -1,4 +1,5 @@ import URI from 'urijs'; +import { cloneChild } from './tools'; import { callFunc } from '../tools'; const API_ROOT = '/api'; @@ -12,12 +13,6 @@ const fetchEP = url => fetch(url.clone().query(''), { body: url.query(), }).then(callFunc('json')); -const cloneChild = (component, props) => - React.cloneElement( - React.Children.only(component.props.children), - props - ); - class DataFetcher extends React.PureComponent { fetch() { const { filters, endpoint, requestNewData } = this.props; diff --git a/ui/oce/corruption-risk/tools.jsx b/ui/oce/corruption-risk/tools.jsx index f819c4996..8887ce553 100644 --- a/ui/oce/corruption-risk/tools.jsx +++ b/ui/oce/corruption-risk/tools.jsx @@ -67,3 +67,9 @@ export function wireProps(parent, _prefix) { } export const _3LineText = content =>
{content}
+ +export const cloneChild = (component, props) => + React.cloneElement( + React.Children.only(component.props.children), + props + ); From d2ba9ca5078eb9c242bc090b2128732c015e9181 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 5 Jan 2018 14:15:40 +0200 Subject: [PATCH 409/702] OCE-439 Default props for DataFetcher --- ui/oce/corruption-risk/data-fetcher.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/oce/corruption-risk/data-fetcher.jsx b/ui/oce/corruption-risk/data-fetcher.jsx index b1c87f602..f789be506 100644 --- a/ui/oce/corruption-risk/data-fetcher.jsx +++ b/ui/oce/corruption-risk/data-fetcher.jsx @@ -1,4 +1,5 @@ import URI from 'urijs'; +import { Map } from 'immutable'; import { cloneChild } from './tools'; import { callFunc } from '../tools'; @@ -40,4 +41,9 @@ class DataFetcher extends React.PureComponent { } } +DataFetcher.defaultProps = { + filters: Map(), + requestNewData: () => null, +} + export default DataFetcher; From e5ffe95bb48bb94b12f54e636525fd0ee048f4f8 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 5 Jan 2018 14:15:57 +0200 Subject: [PATCH 410/702] OCE-439 Added BackendDateFilterable HOC decorator --- .../backend-date-filterable.jsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 ui/oce/corruption-risk/backend-date-filterable.jsx diff --git a/ui/oce/corruption-risk/backend-date-filterable.jsx b/ui/oce/corruption-risk/backend-date-filterable.jsx new file mode 100644 index 000000000..db14e32af --- /dev/null +++ b/ui/oce/corruption-risk/backend-date-filterable.jsx @@ -0,0 +1,40 @@ +import { Map, Set } from 'immutable'; +import { cloneChild } from './tools'; + +class BackendDateFilterable extends React.PureComponent { + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.decoratedFilters = this.decorateFilters(this.props); + } + + decorateFilters({ filters, years, months }) { + const monthly = years.count() === 1; + return filters + .set('year', years) + .set('monthly', monthly) + .set('month', monthly && months.count() != 12 ? months : Set()) + ; + } + + componentWillUpdate(nextProps) { + if (['filters', 'years', 'months'].some(prop => this.props[prop] != nextProps[prop])) { + this.setState({ + decoratedFilters: this.decorateFilters(nextProps), + }) + } + } + + render() { + const decoratedProps = Object.assign({}, this.props, { + filters: this.state.decoratedFilters, + }); + delete decoratedProps.years; + delete decoratedProps.months; + delete decoratedProps.monthly; + const { years } = this.props; + return cloneChild(this, decoratedProps); + } +} + +export default BackendDateFilterable; From 8fb07cd615eb52c7b846ab6c336c8d64d56d13a9 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Fri, 5 Jan 2018 15:24:56 +0200 Subject: [PATCH 411/702] OCE-439 Added PlotlyChart decoratable --- ui/oce/corruption-risk/plotly-chart.jsx | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ui/oce/corruption-risk/plotly-chart.jsx diff --git a/ui/oce/corruption-risk/plotly-chart.jsx b/ui/oce/corruption-risk/plotly-chart.jsx new file mode 100644 index 000000000..6e122e4f3 --- /dev/null +++ b/ui/oce/corruption-risk/plotly-chart.jsx @@ -0,0 +1,34 @@ +import Plotly from 'plotly.js/lib/core'; +import ReactIgnore from '../react-ignore'; + +class PlotlyChart extends React.PureComponent { + componentDidMount() { + const { data, layout } = this.props; + Plotly.newPlot( + this.chartContainer, + data, + layout, + ); + } + + componentWillUnmount() { + Plotly.Plots.purge(this.container); + } + + render() { + return ( +
+ +
{ this.chartContainer = c; }} /> + +
+ ); + } +} + +PlotlyChart.defaultProps = { + data: [], + layout: {}, +} + +export default PlotlyChart; From 14b5a16f4bf6446adfc40379b7617407432bcdb5 Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 8 Jan 2018 16:02:00 +0200 Subject: [PATCH 412/702] OCE-439 New plotly chart updating --- ui/oce/corruption-risk/plotly-chart.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ui/oce/corruption-risk/plotly-chart.jsx b/ui/oce/corruption-risk/plotly-chart.jsx index 6e122e4f3..38df675cb 100644 --- a/ui/oce/corruption-risk/plotly-chart.jsx +++ b/ui/oce/corruption-risk/plotly-chart.jsx @@ -15,6 +15,17 @@ class PlotlyChart extends React.PureComponent { Plotly.Plots.purge(this.container); } + componentDidUpdate(prevProps) { + const { data, layout } = this.props; + if (data !== prevProps[data]) { + this.chartContainer.data = data; + this.chartContainer.layout = layout; + setTimeout(() => Plotly.redraw(this.chartContainer)); + } else if (layout !== prevProps[layout]) { + setTimeout(() => Plotly.relayout(this.chartContainer, layout)); + } + } + render() { return (
From 667c56afa3eed13a0a7092ac07475404720fe19a Mon Sep 17 00:00:00 2001 From: Alexei Savca Date: Mon, 8 Jan 2018 16:03:41 +0200 Subject: [PATCH 413/702] OCE-439 Tagged bar chart --- .../suppliers/single/index.jsx | 78 +++++++++++++++ ui/oce/corruption-risk/tagged-bar-chart.jsx | 96 +++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 ui/oce/corruption-risk/tagged-bar-chart.jsx diff --git a/ui/oce/corruption-risk/suppliers/single/index.jsx b/ui/oce/corruption-risk/suppliers/single/index.jsx index 901e1e454..933a35c44 100644 --- a/ui/oce/corruption-risk/suppliers/single/index.jsx +++ b/ui/oce/corruption-risk/suppliers/single/index.jsx @@ -11,6 +11,8 @@ import AmountLostVsWon from './donuts/amount-lost-vs-won'; import NrFlags from './donuts/nr-flags'; import styles from './style.less'; import { cacheFn, pluckImm } from '../../../tools'; +import PlotlyChart from '../../plotly-chart'; +import TaggedBarChart from '../../tagged-bar-chart'; const add = (a, b) => a + b; @@ -100,6 +102,7 @@ class Supplier extends CRDPage { render() { const { translations, width, doSearch, id, filters, data } = this.props; const donutSize = width / 3 - 100; + const barChartWidth = width / 2 - 100; return (
@@ -138,6 +141,81 @@ class Supplier extends CRDPage { />
+
+

+ {this.t('crd:contracts:flagAnalysis')} +   + ({this.t('crd:contracts:clickCrosstabHint')}) +

+
+ +
+
+ +
+
); } diff --git a/ui/oce/corruption-risk/tagged-bar-chart.jsx b/ui/oce/corruption-risk/tagged-bar-chart.jsx new file mode 100644 index 000000000..fcd8197f8 --- /dev/null +++ b/ui/oce/corruption-risk/tagged-bar-chart.jsx @@ -0,0 +1,96 @@ +import { pluck } from '../tools'; +import PlotlyChart from './plotly-chart'; + +function mkGradient(id, colors) { + const stops = []; + const step = 100 / colors.length; + colors.forEach((color, index) => { + stops.push({ + color, + offset: step * index, + }); + stops.push({ + color, + offset: step * (index + 1), + }); + }); + + return ( + + {stops.map(({color, offset}) => + + )} + + ) +} + +class TaggedBarChart extends React.PureComponent { + render() { + const { width, tags, data } = this.props; + const fstTag = Object.keys(tags)[0]; + const x = data.map(pluck('x')); + const dataSize = data.length; + const plotlyData = {}; + const gradients = {}; + let style = ''; + + Object.keys(tags).map(slug => { + const { color, name } = tags[slug]; + plotlyData[slug] = { + x, + y: [0], + name, + type: 'bar', + marker: { + color, + }, + } + }); + + data.forEach((datum, index) => { + plotlyData[fstTag].y[index] = datum.y; + if (datum.tags.length > 1) { + const gradientSlug = datum.tags.join('_'); + gradients[gradientSlug] = gradients[gradientSlug] || + datum.tags.map(tag => tags[tag].color); + style += `#tagged-bar-chart .point:nth-child(${index + 1}) path {` + + `fill: url(#${gradientSlug})!important;}\n`; + } + }); + + return ( +
+ + + {Object.entries(gradients).map(([slug, colors]) => mkGradient(slug, colors))} + + +