diff --git a/.travis.yml b/.travis.yml index fb63a9941..6261fb85f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,9 @@ cache: - ui/node env: global: - - MAVEN_OPTS="-XX:MaxPermSize=2g -Xmx4g" - - JAVA_OPTS="-XX:MaxPermSize=2g -Xmx4g" -script: mvn test -B --quiet + - MAVEN_OPTS="-XX:MaxMetaspaceSize=256m -Xmx4g" + - JAVA_OPTS="-XX:MaxMetaspaceSize=256m -Xmx4g" +install: mvn install -T 64C -Dskip.frontend.build=true -DskipTests=true -Dmaven.javadoc.skip=true -B -V +script: mvn test -T 64C -Dskip.frontend.build=true -B --quiet jdk: - oraclejdk8 diff --git a/README.md b/README.md index da9843eef..d7652a743 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,18 @@ ## Presentation -OCVN is a project that allows importing the Vietnam public procurement data, available in the common MS Excel format, into a native [Open Contracting Data Standard (OCDS)](http://standard.open-contracting.org/) NoSQL storage, and then run visual data analytics (display a *live* dashboard with charts, maps and data tables as well as custom comparison charts). Since the data is natively stored in the OCDS format, it can be readily exported in this format without any transformation required, and with great throughput. +OCE is a project that allows importing the Vietnam public procurement data, available in the common MS Excel format, into a native [Open Contracting Data Standard (OCDS)](http://standard.open-contracting.org/) NoSQL storage, and then run visual data analytics (display a *live* dashboard with charts, maps and data tables as well as custom comparison charts). Since the data is natively stored in the OCDS format, it can be readily exported in this format without any transformation required, and with great throughput. -Find out more about the OCVN goals [here](http://www.developmentgateway.org/2015/11/12/open-contracting-vietnam/). +## Visual Identity +SVG and raster version of the logo and favicon can be found in the [`docs/images`](./docs/images/) directory. + +### Logo: +![DT Toolkit logo](./docs/images/raster/toolkit-logo-0256.png) + +### Favicon: +![DT Toolkit favicon](./docs/images/raster/toolkit-favicon-0032.png) + +# Modules The project uses open source technologies exclusively, with the following list of key components: diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000..ca4d97ca1 --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml index 5d58f9d92..214f40a47 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -40,8 +40,7 @@ - - + @@ -108,8 +107,7 @@ - - + diff --git a/docs/identity.md b/docs/identity.md new file mode 100644 index 000000000..d7a6800fa --- /dev/null +++ b/docs/identity.md @@ -0,0 +1,29 @@ + +# Identity +The DG Toolkit logo is based on this image form Wikimedia Commons: +> Wikimedia Commons is an online repository of free-use images, sound, and other media files. It is a project of the Wikimedia Foundation. +> https://commons.wikimedia.org/wiki/File:Leaf_Simple_Green_L.svg + +## Logo +![DT Toolkit logo](./images/raster/toolkit-logo-0256.png) + + +## Favicon +![DT Toolkit favicon](./images/raster/toolkit-favicon-0032.png) + + +## Favicon files +Favicon specific were generated using http://realfavicongenerator.net/ + +Insert the following code in the section of your pages: +``` + + + + + + + + +``` +And serve the content of the `images/favicons` directory from the root of your website. diff --git a/docs/images/favicons/android-chrome-192x192.png b/docs/images/favicons/android-chrome-192x192.png new file mode 100644 index 000000000..d68c51111 Binary files /dev/null and b/docs/images/favicons/android-chrome-192x192.png differ diff --git a/docs/images/favicons/android-chrome-512x512.png b/docs/images/favicons/android-chrome-512x512.png new file mode 100644 index 000000000..9792ed3bb Binary files /dev/null and b/docs/images/favicons/android-chrome-512x512.png differ diff --git a/docs/images/favicons/apple-touch-icon.png b/docs/images/favicons/apple-touch-icon.png new file mode 100644 index 000000000..916cf64c1 Binary files /dev/null and b/docs/images/favicons/apple-touch-icon.png differ diff --git a/docs/images/favicons/browserconfig.xml b/docs/images/favicons/browserconfig.xml new file mode 100644 index 000000000..4e40787da --- /dev/null +++ b/docs/images/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ffffff + + + diff --git a/docs/images/favicons/favicon-16x16.png b/docs/images/favicons/favicon-16x16.png new file mode 100644 index 000000000..1d9061719 Binary files /dev/null and b/docs/images/favicons/favicon-16x16.png differ diff --git a/docs/images/favicons/favicon-32x32.png b/docs/images/favicons/favicon-32x32.png new file mode 100644 index 000000000..3b743a525 Binary files /dev/null and b/docs/images/favicons/favicon-32x32.png differ diff --git a/docs/images/favicons/favicon.ico b/docs/images/favicons/favicon.ico new file mode 100644 index 000000000..cbd55ba40 Binary files /dev/null and b/docs/images/favicons/favicon.ico differ diff --git a/docs/images/favicons/manifest.json b/docs/images/favicons/manifest.json new file mode 100644 index 000000000..80f17706b --- /dev/null +++ b/docs/images/favicons/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "DG Toolkit", + "icons": [ + { + "src": "\/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image\/png" + }, + { + "src": "\/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image\/png" + } + ], + "theme_color": "#ffffff", + "display": "standalone" +} diff --git a/docs/images/favicons/mstile-144x144.png b/docs/images/favicons/mstile-144x144.png new file mode 100644 index 000000000..71dce65e1 Binary files /dev/null and b/docs/images/favicons/mstile-144x144.png differ diff --git a/docs/images/favicons/mstile-150x150.png b/docs/images/favicons/mstile-150x150.png new file mode 100644 index 000000000..a52e8874c Binary files /dev/null and b/docs/images/favicons/mstile-150x150.png differ diff --git a/docs/images/favicons/mstile-310x150.png b/docs/images/favicons/mstile-310x150.png new file mode 100644 index 000000000..315ddc4f0 Binary files /dev/null and b/docs/images/favicons/mstile-310x150.png differ diff --git a/docs/images/favicons/mstile-310x310.png b/docs/images/favicons/mstile-310x310.png new file mode 100644 index 000000000..70a923cce Binary files /dev/null and b/docs/images/favicons/mstile-310x310.png differ diff --git a/docs/images/favicons/mstile-70x70.png b/docs/images/favicons/mstile-70x70.png new file mode 100644 index 000000000..bcba5901a Binary files /dev/null and b/docs/images/favicons/mstile-70x70.png differ diff --git a/docs/images/favicons/safari-pinned-tab.svg b/docs/images/favicons/safari-pinned-tab.svg new file mode 100644 index 000000000..431bcb63f --- /dev/null +++ b/docs/images/favicons/safari-pinned-tab.svg @@ -0,0 +1,35 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/docs/images/raster/toolkit-favicon-0016.png b/docs/images/raster/toolkit-favicon-0016.png new file mode 100644 index 000000000..1fc6ce533 Binary files /dev/null and b/docs/images/raster/toolkit-favicon-0016.png differ diff --git a/docs/images/raster/toolkit-favicon-0032.png b/docs/images/raster/toolkit-favicon-0032.png new file mode 100644 index 000000000..4f5f35246 Binary files /dev/null and b/docs/images/raster/toolkit-favicon-0032.png differ diff --git a/docs/images/raster/toolkit-favicon-0048.png b/docs/images/raster/toolkit-favicon-0048.png new file mode 100644 index 000000000..56966b56e Binary files /dev/null and b/docs/images/raster/toolkit-favicon-0048.png differ diff --git a/docs/images/raster/toolkit-favicon-0064.png b/docs/images/raster/toolkit-favicon-0064.png new file mode 100644 index 000000000..7be14a071 Binary files /dev/null and b/docs/images/raster/toolkit-favicon-0064.png differ diff --git a/docs/images/raster/toolkit-logo-0048.png b/docs/images/raster/toolkit-logo-0048.png new file mode 100644 index 000000000..54a0b2fe9 Binary files /dev/null and b/docs/images/raster/toolkit-logo-0048.png differ diff --git a/docs/images/raster/toolkit-logo-0064.png b/docs/images/raster/toolkit-logo-0064.png new file mode 100644 index 000000000..ec6e4b74b Binary files /dev/null and b/docs/images/raster/toolkit-logo-0064.png differ diff --git a/docs/images/raster/toolkit-logo-0128.png b/docs/images/raster/toolkit-logo-0128.png new file mode 100644 index 000000000..7c83ef076 Binary files /dev/null and b/docs/images/raster/toolkit-logo-0128.png differ diff --git a/docs/images/raster/toolkit-logo-0256.png b/docs/images/raster/toolkit-logo-0256.png new file mode 100644 index 000000000..a7c899d02 Binary files /dev/null and b/docs/images/raster/toolkit-logo-0256.png differ diff --git a/docs/images/raster/toolkit-logo-0512.png b/docs/images/raster/toolkit-logo-0512.png new file mode 100644 index 000000000..6c37a7f81 Binary files /dev/null and b/docs/images/raster/toolkit-logo-0512.png differ diff --git a/docs/images/raster/toolkit-logo-1024.png b/docs/images/raster/toolkit-logo-1024.png new file mode 100644 index 000000000..478333e4f Binary files /dev/null and b/docs/images/raster/toolkit-logo-1024.png differ diff --git a/docs/images/raster/toolkit-logo-2048.png b/docs/images/raster/toolkit-logo-2048.png new file mode 100644 index 000000000..f3ef994c1 Binary files /dev/null and b/docs/images/raster/toolkit-logo-2048.png differ diff --git a/docs/images/toolkit-favicon.svg b/docs/images/toolkit-favicon.svg new file mode 100644 index 000000000..cd261bc4c --- /dev/null +++ b/docs/images/toolkit-favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/toolkit-logo-leaf.svg b/docs/images/toolkit-logo-leaf.svg new file mode 100644 index 000000000..a0e2ca63d --- /dev/null +++ b/docs/images/toolkit-logo-leaf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/toolkit-logo.svg b/docs/images/toolkit-logo.svg new file mode 100644 index 000000000..a603f7998 --- /dev/null +++ b/docs/images/toolkit-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java index 899ae0072..ff21ee166 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java @@ -34,7 +34,7 @@ private WebConstants() { public static final String PARAM_REVISION_ID = "revisionId"; public static final String PARAM_ENTITY_CLASS = "class"; - public final static String LANGUAGE_PARAM = "lang"; + public static final String LANGUAGE_PARAM = "lang"; public static final class StringValidators { public static final StringValidator MAXIMUM_LENGTH_VALIDATOR_ONE_LINE_TEXT = StringValidator @@ -46,6 +46,6 @@ public static final class StringValidators { // add more languages here. It is pointless to make this dynamic because the // wicket i18n is in .properties files so we need // to change the src code anyway. - public static final List availableLocales = Collections.unmodifiableList(Arrays.asList(new Locale("en"))); + public static final List AVAILABLE_LOCALES = Collections.unmodifiableList(Arrays.asList(new Locale("en"))); } 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 53a486b05..bc744ea96 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 @@ -39,33 +39,6 @@ public void setTemplateMessage(final SimpleMailMessage templateMessage) { this.templateMessage = templateMessage; } - /** - * Send a validation email to the user - * @param person - * @param urlEnable URL with a key, to validate the user email - */ - public void sendEmailToEnable(final Person person, final String urlEnable) { - - SimpleMailMessage msg = new SimpleMailMessage(); - msg.setTo(person.getEmail()); - msg.setFrom("support@developmentgateway.org"); - msg.setSubject("Activate your account"); - msg.setText("Dear " + person.getFirstName() + " " + person.getLastName() + ",\n\n" - + "To re-enable your account, please use the following link:\n\n" - + urlEnable + person.getSecret() + "\n\n" - + "The system will then prompt you to log in with your current password." - + " If you don't know your current password, please use the 'Forgot your password?' button." - + " Note that the email address you enter must match the address connected to your account.\n\n" - + "Thank you,\n" - + "DG Team"); - try { - javaMailSenderImpl.send(msg); - } catch (MailException e) { - e.printStackTrace(); - } - - } - /** * 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 ... 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 819bcec86..22e07f3c5 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 @@ -107,7 +107,8 @@ public void getReportsStat() { // get the reports cache "reportsCache", declared in ehcache.xml Cache cache = cm.getCache("reportsCache"); - List cacheKeys = cache.getKeys(); + @SuppressWarnings("unchecked") + List cacheKeys = cache.getKeys(); long size = 0; for (String k : cacheKeys) { logger.info("key: " + k); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/CheckBoxPickerBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/CheckBoxPickerBootstrapFormComponent.java index 220c52649..1a4d51fa3 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/CheckBoxPickerBootstrapFormComponent.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/CheckBoxPickerBootstrapFormComponent.java @@ -19,7 +19,8 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.button.ButtonGroup; import de.agilecoders.wicket.core.util.Attributes; import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstrapcheckbox.BootstrapCheckBoxPicker; -import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstrapcheckbox.BootstrapCheckBoxPickerConfig; +import de.agilecoders.wicket.extensions.markup.html.bootstrap. +form.checkbox.bootstrapcheckbox.BootstrapCheckBoxPickerConfig; import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconType; /** diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/FileInputBootstrapFormComponentWrapper.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/FileInputBootstrapFormComponentWrapper.java index f82998024..b2d7c1adc 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/FileInputBootstrapFormComponentWrapper.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/FileInputBootstrapFormComponentWrapper.java @@ -104,6 +104,7 @@ public FileInputBootstrapFormComponentWrapper maxFiles(final int maxFiles) { return this; } + @SuppressWarnings("unchecked") @Override protected void onInitialize() { super.onInitialize(); @@ -216,6 +217,7 @@ public String getContentType() { IndicatingAjaxLink delete = new IndicatingAjaxLink("delete") { private static final long serialVersionUID = 1L; + @SuppressWarnings("unchecked") @Override public void onClick(final AjaxRequestTarget target) { filesModel.remove(item.getModelObject()); @@ -322,6 +324,7 @@ protected void populateItem(final ListItem item) { IndicatingAjaxLink delete = new IndicatingAjaxLink("delete") { private static final long serialVersionUID = 1L; + @SuppressWarnings("unchecked") @Override public void onClick(final AjaxRequestTarget target) { filesModel.remove(item.getModelObject()); @@ -383,6 +386,7 @@ private void addBootstrapFileInputComponent() { bootstrapFileInput = new BootstrapFileInput("bootstrapFileInput", internalUploadModel, fileInputConfig) { private static final long serialVersionUID = 1L; + @SuppressWarnings("unchecked") @Override protected void onSubmit(final AjaxRequestTarget target) { super.onSubmit(target); @@ -453,6 +457,7 @@ public void onEvent(final IEvent event) { ComponentUtil.enableDisableEvent(this, event); } + @SuppressWarnings("unchecked") @Override public void convertInput() { final Collection modelObject = filesModel; 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 3a00efb69..43d6a299b 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 @@ -9,7 +9,9 @@ */ public class JpaFilterState implements Serializable { - public Specification getSpecification() { + private static final long serialVersionUID = 2241550275925712593L; + + public Specification getSpecification() { return (root, query, cb) -> cb.and(); } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/ResettingFilterForm.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/ResettingFilterForm.java index 5ec07a3a1..e1fbec234 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/ResettingFilterForm.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/ResettingFilterForm.java @@ -11,9 +11,10 @@ */ public class ResettingFilterForm extends FilterForm { - private DataTable dataTable; + private static final long serialVersionUID = 7877429240496220944L; + private DataTable dataTable; - public ResettingFilterForm(String id, IFilterStateLocator locator, DataTable dataTable) { + public ResettingFilterForm(final String id, final IFilterStateLocator locator, final DataTable dataTable) { super(id, locator); this.dataTable = dataTable; } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TestFormFilterState.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TestFormFilterState.java index 3f51ff8e3..19d5168a8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TestFormFilterState.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TestFormFilterState.java @@ -14,7 +14,8 @@ */ public class TestFormFilterState extends JpaFilterState { - private String textField; + private static final long serialVersionUID = 8005371716983257722L; + private String textField; @Override public Specification getSpecification() { @@ -31,7 +32,7 @@ public String getTextField() { return textField; } - public void setTextField(String textField) { + public void setTextField(final String textField) { this.textField = textField; } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredBootstrapPropertyColumn.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredBootstrapPropertyColumn.java index 9b1e8b25e..03f0cad54 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredBootstrapPropertyColumn.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredBootstrapPropertyColumn.java @@ -13,11 +13,14 @@ */ public class TextFilteredBootstrapPropertyColumn extends TextFilteredPropertyColumn { - public TextFilteredBootstrapPropertyColumn(IModel displayModel, S sortProperty, String propertyExpression) { + private static final long serialVersionUID = 7360465890668796668L; + + public TextFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, final String propertyExpression) { super(displayModel, sortProperty, propertyExpression); } - public TextFilteredBootstrapPropertyColumn(IModel displayModel, String propertyExpression) { + public TextFilteredBootstrapPropertyColumn(final IModel displayModel, final String propertyExpression) { super(displayModel, propertyExpression); } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredIntegerBootstrapPropertyColumn.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredIntegerBootstrapPropertyColumn.java index b3a6f47d7..4ef26c6d8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredIntegerBootstrapPropertyColumn.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/TextFilteredIntegerBootstrapPropertyColumn.java @@ -20,13 +20,17 @@ */ public class TextFilteredIntegerBootstrapPropertyColumn extends TextFilteredPropertyColumn { - public TextFilteredIntegerBootstrapPropertyColumn(IModel displayModel, S sortProperty, String propertyExpression) { - super(displayModel, sortProperty, propertyExpression); - } + private static final long serialVersionUID = 3974619896912467712L; - public TextFilteredIntegerBootstrapPropertyColumn(IModel displayModel, String propertyExpression) { - super(displayModel, propertyExpression); - } + public TextFilteredIntegerBootstrapPropertyColumn(final IModel displayModel, final S sortProperty, + final String propertyExpression) { + super(displayModel, sortProperty, propertyExpression); + } + + public TextFilteredIntegerBootstrapPropertyColumn(final IModel displayModel, + final String propertyExpression) { + super(displayModel, propertyExpression); + } @Override public Component getFilter(final String componentId, final FilterForm form) { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.html index 1b5dc4d8c..0c0e500f9 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.html @@ -11,22 +11,13 @@
- -
-
-

-
-
+

[PAGE-TITLE]

- -
- -
+ -
- +
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 64ef8b702..172d6d8c0 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 @@ -90,7 +90,7 @@ public abstract class BasePage extends GenericWebPage { * Determines if this page has a fluid container for the content or not. */ public Boolean fluidContainer() { - return true; + return false; } public static class HALRedirectPage extends RedirectPage { @@ -102,6 +102,15 @@ public HALRedirectPage() { } + public static class JminixRedirectPage extends RedirectPage { + private static final long serialVersionUID = -750983217518258464L; + + public JminixRedirectPage() { + super(WebApplication.get().getServletContext().getContextPath() + "/jminix/"); + } + + } + public static class UIRedirectPage extends RedirectPage { private static final long serialVersionUID = -750983217518258464L; @@ -150,8 +159,7 @@ public BasePage(final PageParameters parameters) { // @see https://getbootstrap.com/css/#grid if (fluidContainer()) { mainContainer.add(new CssClassNameAppender(CssClassNames.Grid.containerFluid)); - } - else { + } else { mainContainer.add(new CssClassNameAppender(CssClassNames.Grid.container)); } @@ -164,8 +172,7 @@ public BasePage(final PageParameters parameters) { // Add information about navbar position on mainHeader element. if (navbar.getPosition().equals(Navbar.Position.DEFAULT)) { mainHeader.add(new CssClassNameAppender("with-navbar-default")); - } - else { + } else { mainHeader.add(new CssClassNameAppender("with-" + navbar.getPosition().cssClassName())); } @@ -193,7 +200,7 @@ public NavbarDropDownButton newLanguageMenu() { protected List newSubMenuButtons(final String buttonMarkupId) { final List list = new ArrayList<>(); - for (final Locale l : WebConstants.availableLocales) { + for (final Locale l : WebConstants.AVAILABLE_LOCALES) { final PageParameters params = new PageParameters(BasePage.this.getPageParameters()); params.set(WebConstants.LANGUAGE_PARAM, l.getLanguage()); list.add(new MenuBookmarkablePageLink(BasePage.this.getPageClass(), params, Model.of(l @@ -268,6 +275,11 @@ protected List newSubMenuButtons(final String arg0) { list.add(new MenuBookmarkablePageLink(SpringEndpointsPage.class, null, new StringResourceModel("navbar.springendpoints", this, null)) .setIconType(FontAwesomeIconType.anchor)); + + list.add(new MenuBookmarkablePageLink(JminixRedirectPage.class, null, + new StringResourceModel("navbar.jminix", this, null)) + .setIconType(FontAwesomeIconType.bug)); + // MenuBookmarkablePageLink halBrowserLink = // new MenuBookmarkablePageLink( 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 c952c07ee..b5caa1823 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 @@ -20,4 +20,5 @@ navbar.springendpoints=Spring Endpoints navbar.users=Users navbar.adminSettings=Settings home=Home -navbar.lang=Language \ No newline at end of file +navbar.lang=Language +navbar.jminix=JMX Console 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 542e4c9a7..bf024637f 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 @@ -1,14 +1,26 @@ - - - + + EditAdminSettingsPage
+
+
+
+ + [[Excel Export Settings]] +
+
+
+
+
+
+
+
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 4bd844624..9917c142a 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 @@ -1,21 +1,21 @@ package org.devgateway.toolkit.forms.wicket.page; +import java.util.List; + import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; +import org.apache.wicket.validation.validator.RangeValidator; import org.devgateway.toolkit.forms.security.SecurityConstants; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.devgateway.toolkit.persistence.repository.AdminSettingsRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.wicketstuff.annotation.mount.MountPath; -import java.util.List; - /** * @author idobre * @since 6/22/16 @@ -23,7 +23,10 @@ @AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) @MountPath(value = "/adminsettings") public class EditAdminSettingsPage extends AbstractEditPage { - private static Logger logger = LoggerFactory.getLogger(EditAdminSettingsPage.class); + + private static final long serialVersionUID = 5742724046825803877L; + + private TextFieldBootstrapFormComponent excelBatchSize; private CheckBoxToggleBootstrapFormComponent rebootServer; @@ -54,6 +57,14 @@ protected AdminSettings newInstance() { protected void onInitialize() { super.onInitialize(); + editForm.add(new Label("excelTitle", new StringResourceModel("excelTitle", this, null))); + + excelBatchSize = new TextFieldBootstrapFormComponent<>("excelBatchSize"); + excelBatchSize.integer(); + excelBatchSize.getField().add(new RangeValidator(1, 10000)); + excelBatchSize.required(); + editForm.add(excelBatchSize); + editForm.add(new Label("systemTitle", new StringResourceModel("systemTitle", this, null))); rebootServer = new CheckBoxToggleBootstrapFormComponent("rebootServer"); 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 ac8cb69d6..985445fa1 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 @@ -1,3 +1,5 @@ page.title=Admin settings +excelTitle=Excel Export Settings +excelBatchSize.label=# of releases exported in 1 file systemTitle=System Settings -rebootServer.label=Enable server reboot warning +rebootServer.label=Enable server reboot warning (just an example) diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditGroupPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditGroupPage.html index 8efc2e480..c0095a1ab 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditGroupPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditGroupPage.html @@ -1,8 +1,12 @@ + + + EditGroupPage + -
+
- \ No newline at end of file + 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 deba0273e..20fc7ae41 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 @@ -1,21 +1,22 @@ + + + EditTestFormPage + -
-
-
-
-
-
-
-
-
-
-
-
- -
+
+
+
+
+
+
+
+
+
+
+
- \ No newline at end of file + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.html index 430f8e4ef..9ac7f61ed 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.html @@ -1,3 +1,4 @@ + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.java index b253d6569..3a8927184 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Footer.java @@ -16,12 +16,9 @@ import java.util.Calendar; import java.util.Properties; -import org.apache.wicket.markup.html.TransparentWebMarkupContainer; -import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.Model; -import org.devgateway.toolkit.forms.security.SecurityUtil; public class Footer extends Panel { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Header.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Header.html index 3f15c892c..cd8a4972a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Header.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Header.html @@ -1,14 +1,14 @@ - - + + + + + Header + - - - -
- -
- +
+ +
- \ No newline at end of file + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Homepage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Homepage.html index 8240e03da..1f2e1d6e0 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Homepage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/Homepage.html @@ -6,33 +6,31 @@ - -
- - \ No newline at end of file + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/RevisionsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/RevisionsPage.html index f8fab4b59..3e7c43192 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/RevisionsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/RevisionsPage.html @@ -18,4 +18,4 @@
- \ No newline at end of file + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/SpringEndpointsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/SpringEndpointsPage.html index fc43757fa..35452c091 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/SpringEndpointsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/SpringEndpointsPage.html @@ -1,68 +1,50 @@ - - - + + + - \ No newline at end of file + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html index 7632f0a41..4048f4be8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.html @@ -2,28 +2,22 @@ -
-
-
-
- -
+
+
+ + -
-
- - - - -
-
- -
-
- - +
+ + + + +
+ +
+
- +
diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java index 3051668e3..b575283ab 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java @@ -11,6 +11,8 @@ *******************************************************************************/ package org.devgateway.toolkit.forms.wicket.page.edit; +import java.io.Serializable; + import javax.persistence.EntityManager; import org.apache.log4j.Logger; @@ -37,7 +39,16 @@ import org.devgateway.toolkit.forms.exceptions.NullListPageClassException; import org.devgateway.toolkit.forms.util.MarkupCacheService; import org.devgateway.toolkit.forms.wicket.components.ComponentUtil; -import org.devgateway.toolkit.forms.wicket.components.form.*; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapCancelButton; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapDeleteButton; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapSubmitButton; +import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; +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.GenericBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; +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.BasePage; import org.devgateway.toolkit.forms.wicket.providers.GenericPersistableJpaRepositoryTextChoiceProvider; import org.devgateway.toolkit.persistence.dao.GenericPersistable; @@ -51,8 +62,6 @@ import de.agilecoders.wicket.core.util.Attributes; import nl.dries.wicket.hibernate.dozer.DozerModel; -import java.io.Serializable; - /** * @author mpostelnicu Page used to make editing easy, extend to get easy access * to one entity for editing @@ -439,58 +448,61 @@ public IValidator isEmail() { return EmailAddressValidator.getInstance(); } - public

& Serializable> RangeValidator

inRange(P min, P max) { + public

& Serializable> RangeValidator

inRange(final P min, final P max) { return new RangeValidator<>(min, max); } - public CheckBoxBootstrapFormComponent addCheckBox(String name) { + public CheckBoxBootstrapFormComponent addCheckBox(final String name) { CheckBoxBootstrapFormComponent checkBox = new CheckBoxBootstrapFormComponent(name); editForm.add(checkBox); return checkBox; } - public TextAreaFieldBootstrapFormComponent addTextAreaField(String name) { + public TextAreaFieldBootstrapFormComponent addTextAreaField(final String name) { TextAreaFieldBootstrapFormComponent textAreaField = new TextAreaFieldBootstrapFormComponent<>(name); editForm.add(textAreaField); return textAreaField; } - public TextFieldBootstrapFormComponent addTextField(String name) { + public TextFieldBootstrapFormComponent addTextField(final String name) { TextFieldBootstrapFormComponent textField = new TextFieldBootstrapFormComponent<>(name); editForm.add(textField); return textField; } - public TextFieldBootstrapFormComponent addIntegerTextField(String name) { + public TextFieldBootstrapFormComponent addIntegerTextField(final String name) { TextFieldBootstrapFormComponent textField = new TextFieldBootstrapFormComponent<>(name); textField.integer(); editForm.add(textField); return textField; } - public TextFieldBootstrapFormComponent addDoubleField(String name) { + public TextFieldBootstrapFormComponent addDoubleField(final String name) { TextFieldBootstrapFormComponent textField = new TextFieldBootstrapFormComponent<>(name); textField.asDouble(); editForm.add(textField); return textField; } - public DateTimeFieldBootstrapFormComponent addDateTimeField(String name) { + public DateTimeFieldBootstrapFormComponent addDateTimeField(final String name) { DateTimeFieldBootstrapFormComponent field = new DateTimeFieldBootstrapFormComponent(name); editForm.add(field); return field; } - public DateFieldBootstrapFormComponent addDateField(String name) { + public DateFieldBootstrapFormComponent addDateField(final String name) { DateFieldBootstrapFormComponent field = new DateFieldBootstrapFormComponent(name); editForm.add(field); return field; } - public Select2ChoiceBootstrapFormComponent addSelect2ChoiceField(String name, TextSearchableRepository repository) { - GenericPersistableJpaRepositoryTextChoiceProvider choiceProvider = new GenericPersistableJpaRepositoryTextChoiceProvider<>(repository); - Select2ChoiceBootstrapFormComponent component = new Select2ChoiceBootstrapFormComponent<>(name, choiceProvider); - editForm.add(component); - return component; - } + public Select2ChoiceBootstrapFormComponent addSelect2ChoiceField( + final String name, final TextSearchableRepository repository) { + GenericPersistableJpaRepositoryTextChoiceProvider choiceProvider + = new GenericPersistableJpaRepositoryTextChoiceProvider<>(repository); + Select2ChoiceBootstrapFormComponent component = new Select2ChoiceBootstrapFormComponent<>(name, + choiceProvider); + editForm.add(component); + return component; + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/category/AbstractCategoryEditPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/category/AbstractCategoryEditPage.html index cd00c472e..bef3e4976 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/category/AbstractCategoryEditPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/category/AbstractCategoryEditPage.html @@ -2,10 +2,8 @@ -

-
- -
+
+ \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage$ActionPanel.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage$ActionPanel.html index 736c41e1a..f16c6cc9a 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage$ActionPanel.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage$ActionPanel.html @@ -1,10 +1,12 @@ - - - - - + + + + + + + - \ No newline at end of file + 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 e88bcaba9..d8d134b58 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 @@ -1,18 +1,18 @@ - -
-
-
- -
-
- -
-
-
+ +
+
+
+ +
+
+ +
+
+
\ No newline at end of file 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 2957257a0..5df0fda44 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 @@ -170,7 +170,7 @@ public void populateItem(final Item> cellItem, final String co } private boolean hasFilteredColumns() { - for (IColumn column : columns) { + for (IColumn column : columns) { if (column instanceof IFilteredColumn) { return true; } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.html deleted file mode 100644 index 6549f9b9f..000000000 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - -Title - - - -
-
-
-
-
- -
-
-
-
- - \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.java deleted file mode 100644 index bc607eb50..000000000 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.devgateway.toolkit.forms.wicket.page.user; - -import de.agilecoders.wicket.core.markup.html.bootstrap.form.BootstrapForm; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.model.ResourceModel; -import org.apache.wicket.model.StringResourceModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.apache.wicket.spring.injection.annot.SpringBean; -import org.apache.wicket.util.string.StringValue; -import org.devgateway.toolkit.forms.wicket.page.BasePage; -import org.devgateway.toolkit.persistence.dao.Person; -import org.devgateway.toolkit.persistence.repository.PersonRepository; -import org.wicketstuff.annotation.mount.MountPath; - -@MountPath(value = "/changedPasswordValidate") -public class ChangedPasswordValidatePage extends BasePage { - private static final long serialVersionUID = 109572901645014684L; - - public static final String PARAM_CHECK = "check"; - - @SpringBean - private PersonRepository personRepository; - - ChangedPasswordValidatePage(final PageParameters parameters) { - super(parameters); - - ValidatePassForm validatePassForm = new ValidatePassForm("validatePassForm"); - add(validatePassForm); - } - - class ValidatePassForm extends BootstrapForm { - private static final long serialVersionUID = 6478383928100394429L; - - ValidatePassForm(final String componentId) { - super(componentId); - } - - @Override - protected void onInitialize() { - super.onInitialize(); - - if (!getPageParameters().get(ChangedPasswordValidatePage.PARAM_CHECK).isEmpty()) { - StringValue secret = getPageParameters().get(ChangedPasswordValidatePage.PARAM_CHECK); - - Person person = personRepository.findBySecret(secret.toString()); - if (person != null && person.getSecret() != null - && person.getSecret().compareTo(secret.toString()) == 0) { - person.setEnabled(true); - person.setChangePassword(false); - personRepository.saveAndFlush(person); - - add(new Label("message", new ResourceModel("confirmMessage"))); - } else { - add(new Label("message", new ResourceModel("errorMessage"))); - } - } else { - add(new Label("message", new ResourceModel("errorMessage"))); - } - - IndicatingAjaxButton submit = new IndicatingAjaxButton( - "submit", new StringResourceModel("ok", ChangedPasswordValidatePage.this, null)) { - private static final long serialVersionUID = 1L; - - @Override - protected void onSubmit(final AjaxRequestTarget target, final Form form) { - setResponsePage(LoginPage.class); - } - }; - add(submit); - } - } -} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.properties deleted file mode 100644 index 27a6e86af..000000000 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ChangedPasswordValidatePage.properties +++ /dev/null @@ -1,4 +0,0 @@ -page.title=Validate your password -confirmMessage=Your account is now active, please click OK and login -errorMessage=The link you provided is wrong! -ok=OK diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.html index 7911142a4..07ae034a3 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.html @@ -2,44 +2,18 @@ - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- +
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.html index 354ae1a4b..69ce6903b 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/ForgotYourPasswordPage.html @@ -1,19 +1,21 @@ - + Title
-
-
+
+
-
- - + +
+ + +
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 9c8f0e1c7..50b6ca25b 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 @@ -28,6 +28,8 @@ public class ForgotYourPasswordPage extends BasePage { @SpringBean private SendEmailService sendEmailService; + public static final int RANDOM_PASSWORD_LENGTH = 16; + public ForgotYourPasswordPage(final PageParameters parameters) { super(parameters); @@ -77,7 +79,7 @@ protected void onSubmit(final AjaxRequestTarget target, final Form form) { if (person == null) { feedbackPanel.error("Email address not found"); } else { - String newPassword = RandomStringUtils.random(16, true, true); + String newPassword = RandomStringUtils.random(RANDOM_PASSWORD_LENGTH, true, true); person.setPassword(encoder.encode(newPassword)); person.setChangePassword(true); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.html index 3b4cd5602..d8c63bce7 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.html @@ -7,7 +7,7 @@
-
+
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 5510a00fe..dd6d46639 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 @@ -49,11 +49,6 @@ public class LoginPage extends BasePage { private static final int HIDE_NOTIFICATION_SECONDS = 15; - @Override - public Boolean fluidContainer() { - return false; - } - class LoginForm extends BootstrapForm { private static final long serialVersionUID = 2066636625524650473L; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaRepositoryDataProvider.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaRepositoryDataProvider.java index 72b878128..a9a7107ac 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaRepositoryDataProvider.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaRepositoryDataProvider.java @@ -72,7 +72,8 @@ protected Sort translateSort() { @Override public Iterator iterator(final long first, final long count) { int page = (int) ((double) first / WebConstants.PAGE_SIZE); - Page findAll = jpaRepository.findAll(filterState.getSpecification(), new PageRequest(page, WebConstants.PAGE_SIZE, translateSort())); + Page findAll = jpaRepository.findAll(filterState.getSpecification(), + new PageRequest(page, WebConstants.PAGE_SIZE, translateSort())); return findAll.iterator(); } @@ -97,7 +98,7 @@ public JpaFilterState getFilterState() { } @Override - public void setFilterState(JpaFilterState filterState) { + public void setFilterState(final JpaFilterState filterState) { this.filterState = filterState; } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css index 9a8585bbb..619c375c4 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css @@ -12,7 +12,7 @@ html { } .mainHeader.with-navbar-default .navbar-default { - /** + /* * The mainHeader navbar should not have rounded borders when it is not fixed. * * @see: Navbar.Position.DEFAULT @@ -22,7 +22,7 @@ html { } .mainHeader.with-navbar-fixed-top { - /** + /* * The mainHeader doesn't have any height when the navbar is fixed, and other elements are behind it. * * HACK: The default bootstrap navbar is 50px height and has a bottom margin of 20px for the default font size. @@ -36,8 +36,16 @@ html { } .mainContainer { - /** + /* * The main page content should have some height. */ min-height: 20em; } + +.mainFooter { + /* + * Add some spacing around the footer. + */ + margin-top: 2em; + margin-bottom: 2em; +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/DatatableStyleResourceReference.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/DatatableStyleResourceReference.java deleted file mode 100644 index bfa4b1a98..000000000 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/DatatableStyleResourceReference.java +++ /dev/null @@ -1,30 +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.forms.wicket.styles; - -import org.apache.wicket.request.resource.CssResourceReference; - -public class DatatableStyleResourceReference extends CssResourceReference { - - /** - * - */ - private static final long serialVersionUID = 1L; - public static final DatatableStyleResourceReference INSTANCE = new DatatableStyleResourceReference(); - - /** - * Construct. - */ - public DatatableStyleResourceReference() { - super(DatatableStyleResourceReference.class, "datatable.css"); - } -} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/datatable.css b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/datatable.css deleted file mode 100644 index ddce61a93..000000000 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/datatable.css +++ /dev/null @@ -1,26 +0,0 @@ -/* DATAVIEW - see AbstractListPage.html */ -table.dataview { - margin-bottom: 10px; - border-bottom: 1px solid #0079d6; - font-size: 1em; - font-family: arial; - } - -table.dataview caption { text-align: left; } -table.dataview tr { padding-top: 2px; padding-bottom: 2px; } -table.dataview tr.even { background-color: #ffebcd; } -table.dataview tr.odd { background-color: #fff; } -table.dataview tr td { padding-left: 8px; padding-right: 30px; } -table.dataview tr th { color: black; padding-top: 3px; padding-bottom: 3px; padding-left: 8px; padding-right: 30px; background-color: #c1e4ff; border-bottom: 1px solid #0079d6; border-top: 1px solid #0079d6; text-align: left; white-space: nowrap; vertical-align: middle;} - -table.dataview tr th { background-position: right; background-repeat:no-repeat; } -table.dataview tr th.wicket_orderDown { - background-color: #87cbff; background-image: url(/img/arrow_down.png); } -table.dataview tr th.wicket_orderUp { - background-color: #87cbff; background-image: url(/img/arrow_up.png); } -/*table.dataview tr th.wicket_orderNone {*/ - /*background-image: url(/img/arrow_off.png);*/ -/*}*/ -table.dataview tr th a { font-weight: normal; } -table.dataview #message { padding-left: 3px; } -table.dataview caption { padding-bottom: 2px; } diff --git a/forms/src/main/resources/logback.xml b/forms/src/main/resources/logback.xml index c2a4821fa..8b35ebcdb 100644 --- a/forms/src/main/resources/logback.xml +++ b/forms/src/main/resources/logback.xml @@ -10,8 +10,9 @@ - /srv/logs/application.%d{yyyy-MM-dd}.%i.log + ${LOG_DIR}/logger.%d{yyyy-MM-dd}.%i.log + 64MB diff --git a/forms/src/main/resources/public/assets/img/close-btns.png b/forms/src/main/resources/public/assets/img/close-btns.png deleted file mode 100644 index aa06f2a96..000000000 Binary files a/forms/src/main/resources/public/assets/img/close-btns.png and /dev/null differ diff --git a/forms/src/main/resources/public/assets/img/syncfusion-icons-white.png b/forms/src/main/resources/public/assets/img/syncfusion-icons-white.png deleted file mode 100644 index 625dcc098..000000000 Binary files a/forms/src/main/resources/public/assets/img/syncfusion-icons-white.png and /dev/null differ diff --git a/forms/src/main/resources/public/assets/img/syncfusion-icons.png b/forms/src/main/resources/public/assets/img/syncfusion-icons.png deleted file mode 100644 index 7ee687376..000000000 Binary files a/forms/src/main/resources/public/assets/img/syncfusion-icons.png and /dev/null differ diff --git a/forms/src/main/resources/public/assets/img/up.png b/forms/src/main/resources/public/assets/img/up.png deleted file mode 100644 index 1e9011c43..000000000 Binary files a/forms/src/main/resources/public/assets/img/up.png and /dev/null differ diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java index dcdd3e877..356da146d 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Address.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -16,7 +15,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#address * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "streetAddress", "locality", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java index 2aa452a29..cc9294f63 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amendment.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -18,7 +17,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#amendment * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "date", "changes", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java index 27f84ed2e..6ab2e9761 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Amount.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -18,7 +17,6 @@ * spring-mongodb-driver-not-supporting-hierarchical-structure-of-java-domain-objec * (hope this will be fixed in future versions of spring data mongodb) */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "amount", "currency" diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java index e4edf0b45..0ade4b55e 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Award.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; @@ -28,7 +27,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#award * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java index 17d49867b..65052d476 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Budget.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -22,7 +21,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#budget * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "source", "id", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java index 8076ee815..b1c641b42 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Change.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -11,7 +10,6 @@ /** * http://standard.open-contracting.org/latest/en/schema/reference/#changes */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "property", "former_value" diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java index 0f33553d5..480282ecc 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Classification.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -13,7 +12,6 @@ /** * Classification OCDS Entity http://standard.open-contracting.org/latest/en/schema/reference/#classification */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "scheme", "id", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java index 5332a3456..6a326009a 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ContactPoint.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -14,7 +13,6 @@ * * http://standard.open-contracting.org/latest/en/schema/reference/#contactpoint */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "name", "email", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java index a787d899a..d2919edf0 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Contract.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; @@ -27,7 +26,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#contract * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "awardID", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java index 9960c7c57..49d72f26f 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Document.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -20,7 +19,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#document * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "documentType", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Gazetteer.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Gazetteer.java index 2876814e3..db96bb585 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Gazetteer.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Gazetteer.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -11,7 +10,6 @@ import java.util.Set; import java.util.TreeSet; -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "scheme", "identifiers" diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java index ccaa14588..1b02cc780 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Identifier.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -11,7 +10,6 @@ /** * Identifier OCDS entity http://standard.open-contracting.org/latest/en/schema/reference/#identifier */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "scheme", "id", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java index e6a82943f..89ced1dc0 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Implementation.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -21,7 +20,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#implementation * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "transactions", "milestones", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java index f628f0567..d8bb1c600 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Item.java @@ -1,17 +1,15 @@ package org.devgateway.ocds.persistence.mongo; -import java.util.LinkedHashSet; -import java.util.Set; - +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.devgateway.ocds.persistence.mongo.excel.annotation.ExcelExport; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.LinkedHashSet; +import java.util.Set; /** * A good, service, or work to be contracted. @@ -19,7 +17,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#item * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "description", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Location.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Location.java index d08fdba79..16a738c9c 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Location.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Location.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -16,7 +15,6 @@ * A location can be described by either a geometry (point location, line or polygon), or a gazetteer entry, or both. * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "description", "geometry", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java index 600f09c98..ae2c6c342 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Milestone.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; @@ -20,7 +19,6 @@ /** * Milestone OCDS entity http://standard.open-contracting.org/latest/en/schema/reference/#milestone */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java index f7281e63e..b86bfa676 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Organization.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -23,7 +22,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#organization * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "identifier", "additionalIdentifiers", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java index 9703e3dad..e582caaa5 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Period.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -17,7 +16,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#period * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "startDate", "endDate" diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java index 557f39449..c3506ae56 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Planning.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -22,7 +21,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#planning * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "budget", "rationale", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Publisher.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Publisher.java index 87a88d811..44c3859ce 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Publisher.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Publisher.java @@ -1,17 +1,14 @@ package org.devgateway.ocds.persistence.mongo; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - /** * Information to uniquely identify the publisher of this package */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "name", "scheme", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java index b072e03c2..1b8fa4aec 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Record.java @@ -1,10 +1,8 @@ package org.devgateway.ocds.persistence.mongo; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -12,11 +10,10 @@ import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "ocid", "releases", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java index accd3c84f..738464a89 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/RecordPackage.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -24,7 +23,6 @@ * into the latest version of the information along with the history of any data changes. * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "uri", "publisher", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java index 2f4bdfeb6..467823062 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Release.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; @@ -30,7 +29,6 @@ * http://standard.open-contracting.org/latest/en/schema/release/ * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "ocid", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java index d3a153fa8..62e5233d9 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleasePackage.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -22,7 +21,6 @@ * within this release package. * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "uri", "publishedDate", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleaseReference.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleaseReference.java index 7cbc4a129..07908f22c 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleaseReference.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/ReleaseReference.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -17,7 +16,6 @@ * Information to uniquely identify the release. * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "url", "date", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java index 891f6b98c..908ba4678 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Tender.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonValue; @@ -30,7 +29,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#tender * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "title", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java index 1a2c9778d..36fccf488 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Transaction.java @@ -1,6 +1,5 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -25,7 +24,6 @@ * http://standard.open-contracting.org/latest/en/schema/reference/#transaction * */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "id", "source", diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java index 1b6f21995..f7f42aec9 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/Unit.java @@ -1,7 +1,6 @@ package org.devgateway.ocds.persistence.mongo; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; @@ -15,7 +14,6 @@ * * http://standard.open-contracting.org/latest/en/schema/reference/#unit */ -@JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "name", "value" diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFile.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFile.java index e21428a71..686d39944 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFile.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/excel/ReleaseExportFile.java @@ -2,7 +2,6 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.devgateway.ocds.persistence.mongo.Release; import java.util.ArrayList; @@ -29,9 +28,7 @@ public Workbook createWorkbook() { ExcelSheet releaseSheet = new OCDSObjectExcelSheet(this.workbook, Release.class); // don't do anything if the list of releases is empty, just display the error message - if (releases == null || releases.isEmpty()) { - // releaseSheet.emptySheet(); - } else { + if (releases != null && !releases.isEmpty()) { releaseSheet.writeSheet(new ArrayList<>(releases)); } diff --git a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFile.java b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFile.java index 4a84e113f..177d75ad5 100644 --- a/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFile.java +++ b/persistence-mongodb/src/main/java/org/devgateway/ocds/persistence/mongo/reader/XMLFile.java @@ -18,16 +18,16 @@ public interface XMLFile extends ImportService { * @throws IOException * @throws SAXException */ - public void process(final InputStream inputStream) throws IOException, SAXException; + void process(final InputStream inputStream) throws IOException, SAXException; - public void process(final File file) throws IOException, SAXException; + void process(final File file) throws IOException, SAXException; /** * Save a particular release into database. * * @param obj */ - public void saveRelease(Object obj); + void saveRelease(Object obj); /** * Returns a StringBuffer with import statistics diff --git a/persistence/.gitignore b/persistence/.gitignore index fca2ac261..a60d6c4e3 100644 --- a/persistence/.gitignore +++ b/persistence/.gitignore @@ -6,3 +6,4 @@ /.settings/ /derby.log /.checkstyle +/.factorypath 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 7e8c8d062..87bb71ba5 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 @@ -15,6 +15,10 @@ @Audited @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class AdminSettings extends AbstractAuditableEntity implements Serializable { + private static final long serialVersionUID = -1051140524022133178L; + + private Integer excelBatchSize; + private Boolean rebootServer = false; @Override @@ -22,6 +26,14 @@ public AbstractAuditableEntity getParent() { return null; } + public Integer getExcelBatchSize() { + return excelBatchSize; + } + + public void setExcelBatchSize(Integer excelBatchSize) { + this.excelBatchSize = excelBatchSize; + } + public Boolean getRebootServer() { return rebootServer; } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java index 0a24c730f..f84bd6c66 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java @@ -36,7 +36,7 @@ @EnableCaching public class CacheConfiguration { - @Autowired(required=false) + @Autowired(required = false) private MBeanServer mbeanServer; @Bean @@ -46,16 +46,17 @@ public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() { ehCacheManagerFactoryBean.setShared(true); return ehCacheManagerFactoryBean; } - + @Bean - public CacheManager cacheManager(EhCacheManagerFactoryBean factory) { + public CacheManager cacheManager(final EhCacheManagerFactoryBean factory) { return new EhCacheCacheManager(factory.getObject()); } - + @Bean(destroyMethod = "dispose", initMethod = "init") @Profile("!integration") - public ManagementService ehCacheManagementService(EhCacheManagerFactoryBean factory) { - ManagementService managementService = new ManagementService(factory.getObject(), mbeanServer, true, true, true, true); + public ManagementService ehCacheManagementService(final EhCacheManagerFactoryBean factory) { + ManagementService managementService = new ManagementService(factory.getObject(), mbeanServer, true, true, true, + true); return managementService; } 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 17054e0af..a1fe40013 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 @@ -10,7 +10,7 @@ * Development Gateway - initial API and implementation *******************************************************************************/ /** - * + * */ package org.devgateway.toolkit.persistence.spring; @@ -60,76 +60,76 @@ public class DatabaseConfiguration { private String springDatasourceTransactionIsolation; @Value("${dg-toolkit.derby.port}") - private int DERBY_PORT; + private int derbyPort; @Value("${dg-toolkit.datasource.jndi-name}") private String datasourceJndiName; - protected static Logger logger = Logger.getLogger(DatabaseConfiguration.class); + protected static Logger logger = Logger.getLogger(DatabaseConfiguration.class); - /** - * This bean creates the JNDI tree and registers the - * {@link javax.sql.DataSource} to this tree. This allows Pentaho Classic - * Engine to use a {@link javax.sql.DataSource} ,in our case backed by a - * connection pool instead of always opening up JDBC connections. Should - * significantly improve performance of all classic reports. In PRD use - * connection type=JNDI and name toolkitDS. To use it in PRD you need to add - * the configuration to the local PRD. Edit - * ~/.pentaho/simple-jndi/default.properties and add the following: - * toolkitDS/type=javax.sqlf.DataSource - * toolkitDS/driver=org.apache.derby.jdbc.ClientDriver toolkitDS/user=app - * toolkitDS/password=app - * toolkitDS/url=jdbc:derby://localhost//derby/toolkit - * - * @return - */ - @Bean - public SimpleNamingContextBuilder jndiBuilder() { - SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); + /** + * This bean creates the JNDI tree and registers the + * {@link javax.sql.DataSource} to this tree. This allows Pentaho Classic + * Engine to use a {@link javax.sql.DataSource} ,in our case backed by a + * connection pool instead of always opening up JDBC connections. Should + * significantly improve performance of all classic reports. In PRD use + * connection type=JNDI and name toolkitDS. To use it in PRD you need to add + * the configuration to the local PRD. Edit + * ~/.pentaho/simple-jndi/default.properties and add the following: + * toolkitDS/type=javax.sql.DataSource + * toolkitDS/driver=org.apache.derby.jdbc.ClientDriver toolkitDS/user=app + * toolkitDS/password=app + * toolkitDS/url=jdbc:derby://localhost//derby/toolkit + * + * @return + */ + @Bean + public SimpleNamingContextBuilder jndiBuilder() { + SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); builder.bind(datasourceJndiName, dataSource()); - try { - builder.activate(); - } catch (IllegalStateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NamingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return builder; - } + try { + builder.activate(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NamingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return builder; + } - /** - * Creates a {@link javax.sql.DataSource} based on Tomcat {@link DataSource} - * - * @return - */ - @Bean - @DependsOn(value = { "derbyServer"}) - public DataSource dataSource() { - HikariDataSource ds = new HikariDataSource(); + /** + * Creates a {@link javax.sql.DataSource} based on Tomcat {@link DataSource} + * + * @return + */ + @Bean + @DependsOn(value = { "derbyServer"}) + public DataSource dataSource() { + HikariDataSource ds = new HikariDataSource(); ds.setTransactionIsolation(springDatasourceTransactionIsolation); ds.setJdbcUrl(springDatasourceUrl); ds.setUsername(springDatasourceUsername); ds.setPassword(springDatasourcePassword); ds.setDriverClassName(springDatasourceDriverClassName); - return ds; - } + return ds; + } - /** - * Graciously starts a Derby Database Server when the application starts up - * - * @return - * @throws Exception - */ - @Bean(destroyMethod = "shutdown") - public NetworkServerControl derbyServer() throws Exception { - Properties p = System.getProperties(); - p.put("derby.storage.pageCacheSize", "30000"); - p.put("derby.language.maxMemoryPerTable", "20000"); - NetworkServerControl nsc = new NetworkServerControl(InetAddress.getByName("localhost"), DERBY_PORT); - nsc.start(new PrintWriter(java.lang.System.out, true)); - return nsc; - } + /** + * Graciously starts a Derby Database Server when the application starts up + * + * @return + * @throws Exception + */ + @Bean(destroyMethod = "shutdown") + public NetworkServerControl derbyServer() throws Exception { + Properties p = System.getProperties(); + p.put("derby.storage.pageCacheSize", "30000"); + p.put("derby.language.maxMemoryPerTable", "20000"); + NetworkServerControl nsc = new NetworkServerControl(InetAddress.getByName("localhost"), derbyPort); + nsc.start(new PrintWriter(java.lang.System.out, true)); + return nsc; + } } diff --git a/persistence/src/main/resources/ehcache.xml b/persistence/src/main/resources/ehcache.xml index c9207fcc2..f177ea273 100644 --- a/persistence/src/main/resources/ehcache.xml +++ b/persistence/src/main/resources/ehcache.xml @@ -13,36 +13,45 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 925511392..904351af5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,171 +10,186 @@ Development Gateway - initial API and implementation --> - 4.0.0 + 4.0.0 org.devgateway.ocds oce 0.0.1-SNAPSHOT - pom + pom OCExplorer OCDS General Implementation - - UTF-8 - 1.8 - 3.5.1 - 1.3.6.RELEASE - 10.12.1.1 - 4.3.11.Final - 3.2.2 - - - - persistence - web - ui - forms - persistence-mongodb - - - 2015 - - Development Gateway - http://developmentgateway.org - - - JIRA + + UTF-8 + 1.8 + 3.5.1 + 1.3.7.RELEASE + 10.12.1.1 + 4.3.11.Final + 3.2.2 + + + + persistence + web + ui + forms + persistence-mongodb + + + 2015 + + Development Gateway + http://developmentgateway.org + + + JIRA https://jira.dgfoundation.org/browse/OCE/ - - - Jenkins + + + Jenkins http://ocexplorer.dgstg.org/ci/ - + - + git@github.com:devgateway/oc-explorer.git - + scm:git:git@github.com:devgateway/oc-explorer.git git@github.com:devgateway/oc-explorer.git - 0.0.3-SNAPSHOT - + 0.0.3-SNAPSHOT + - - + + oc-explorer-releases OC Explorer releases http://artifactory.ampdev.net/artifactory/oc-explorer-releases/ - - - - - - - OCVN - OCVN - http://artifactory.ampdev.net/artifactory/ocvn/ - - true - - - - - - - - OCVN - OCVN - http://artifactory.ampdev.net/artifactory/ocvn/ - - true - - - - - - - - org.apache.derby - derby - ${derby.version} - - - - - - - - - org.mongodb - mongo-java-driver - ${mongo.version} - - - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.3 - - - attach-javadocs - - jar - - - -Xdoclint:none - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - - validate - validate - - checkstyle.xml - UTF-8 - true - false - - - check - - - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - @{project.version} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - - + + + + + + + OCVN + OCVN + http://artifactory.ampdev.net/artifactory/ocvn/ + + true + + + + + + + + OCVN + OCVN + http://artifactory.ampdev.net/artifactory/ocvn/ + + true + + + + + + + + org.apache.derby + derby + ${derby.version} + + + + + + + + + org.mongodb + mongo-java-driver + ${mongo.version} + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + attach-javadocs + + jar + + + -Xdoclint:none + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + validate + validate + + checkstyle.xml + checkstyle-suppressions.xml + UTF-8 + true + false + true + warning + + + check + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + @{project.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + classes + true + 64 + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + + diff --git a/reporting/src/main/java/org/devgateway/toolkit/reporting/spring/util/ReportsCacheService.java b/reporting/src/main/java/org/devgateway/toolkit/reporting/spring/util/ReportsCacheService.java new file mode 100644 index 000000000..f81d006a3 --- /dev/null +++ b/reporting/src/main/java/org/devgateway/toolkit/reporting/spring/util/ReportsCacheService.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.reporting.spring.util; + +import org.pentaho.reporting.engine.classic.core.cache.DataCacheFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** + * Component for accessing and clearing the pentaho caches + * + * @author mpostelnicu + * + */ +@Component +@Profile("reports") +public class ReportsCacheService { + + // @Autowired + // private DataSource dataSource; + + /** + * Flush mondrian and reports classic cache + */ + public void flushCache() { + flushReportsClassicCache(); + + // use this if you have Mondrian enabled + // flushMondrianCache(); + } + + public void flushReportsClassicCache() { + DataCacheFactory.getCache().getCacheManager().clearAll(); + } + + public void flushMondrianCache() { + // Util.PropertyList propertyList = new Util.PropertyList(); + // propertyList.put("Provider", "mondrian"); + + // ClassLoader classloader = this.getClass().getClassLoader(); + // URL mondrianCubeFileURL = + // classloader.getResource(MondrianConstants.CATALOG_FILE); + + // propertyList.put("Catalog", "res:" + MondrianConstants.CATALOG_FILE); + // + // Connection connection = + // mondrian.olap.DriverManager.getConnection(propertyList, null, + // dataSource); + // + // CacheControl cacheControl = connection.getCacheControl(null); + // for (Cube cube : connection.getSchema().getCubes()) { + // CellRegion createMeasuresRegion = + // cacheControl.createMeasuresRegion(cube); + // cacheControl.flush(createMeasuresRegion); + // } + // cacheControl.flushSchemaCache(); + + // connection.close(); + } + +} \ No newline at end of file diff --git a/ui/components/tender/index.jsx b/ui/components/tender/index.jsx deleted file mode 100644 index 2a17a4231..000000000 --- a/ui/components/tender/index.jsx +++ /dev/null @@ -1,132 +0,0 @@ -import React from "react"; -import Component from "../pure-render-component"; -import CostEffectiveness from "./cost-effectiveness"; -import FundingByBidType from "./funding-by-bid-type"; -import BiddingPeriod from "./bidding-period"; -import Cancelled from "./cancelled"; -import CancelledPercents from "./cancelled-percents"; -import AvgNrBids from "./avg-nr-bids"; -import PercentEbid from "./percent-ebid"; -import {toImmutable} from "nuclear-js"; -import Comparison from "../comparison"; -import translatable from "../translatable"; - -export default class Tender extends translatable(Component){ - render(){ - let {state, width, actions} = this.props; - let {compare, costEffectiveness, bidPeriod, bidType, cancelled, avgNrBids, showPercentsCancelled, - percentEbid} = state; - return ( -
- {compare ? - - : - - } - - {compare ? - - : - - } - - {compare ? - - : - - } - - {showPercentsCancelled ? - (compare ? - - : - - ) : (compare ? - - : - - ) - } - - {compare ? - - : - - } - - {compare ? - - : - - } -
- ); - } -} \ No newline at end of file diff --git a/ui/components/tender/percent-ebid.jsx b/ui/components/tender/percent-ebid.jsx deleted file mode 100644 index be1da5697..000000000 --- a/ui/components/tender/percent-ebid.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import Plot from "../plot"; -import {pluck} from "../../tools"; - -export default class BiddingPeriod extends Plot{ - getTitle() { - return "% of tenders using eBid" - } - - getData(){ - let {data} = this.props; - return [{ - x: data.map(pluck('year')), - y: data.map(pluck('percentageTendersUsingEbid')), - type: 'scatter', - fill: 'tonexty' - }]; - } - - getLayout(){ - return { - xaxis: { - title: "Years", - type: 'category', - titlefont: { - color: "#cc3c3b" - } - }, - yaxis: { - title: "%", - titlefont: { - color: "#cc3c3b" - }, - range: this.props.yAxisRange - } - } - } -} diff --git a/ui/flux/actions/constants.es6 b/ui/flux/actions/constants.es6 deleted file mode 100644 index 95b2241e7..000000000 --- a/ui/flux/actions/constants.es6 +++ /dev/null @@ -1,35 +0,0 @@ -import keyMirror from 'keymirror' - -export default keyMirror({ - TAB_CHANGED: null, - YEAR_TOGGLED: null, - CONTENT_WIDTH_CHANGED: null, - COST_EFFECTIVENESS_DATA_UPDATED: null, - BID_TYPE_DATA_UPDATED: null, - LOCATION_UPDATED: null, - BID_PERIOD_DATA_UPDATED: null, - OVERVIEW_DATA_UPDATED: null, - CANCELLED_DATA_UPDATED: null, - CANCELLED_PERCENTS_DATA_UPDATED: null, - FILTER_BOX_CHANGED: null, - FILTERS_DATA_UPDATED: null, - FILTER_TOGGLED: null, - FILTER_OPTIONS_TOGGLED: null, - PROCURING_ENTITY_QUERY_UPDATED: null, - PROCURING_ENTITIES_UPDATED: null, - TOP_AWARDS_DATA_UPDATED: null, - TOP_TENDERS_DATA_UPDATED: null, - COMPARISON_CRITERIA_UPDATED: null, - OVERVIEW_COMPARISON_DATA_UPDATED: null, - COST_EFFECTIVENESS_COMPARISON_DATA_UPDATED: null, - BID_PERIOD_COMPARISON_DATA_UPDATED: null, - BID_TYPE_COMPARISON_DATA_UPDATED: null, - CANCELLED_COMPARISON_DATA_UPDATED: null, - COMPARISON_CRITERIA_NAMES_UPDATED: null, - LOCALE_CHANGED: null, - FILTERS_APPLIED: null, - FILTERS_RESET: null, - AVERAGED_TENDERS_DATA_UPDATED: null, - CANCELLED_TYPE_TOGGLED: null, - PERCENT_EBID_DATA_UPDATED: null -}) \ No newline at end of file diff --git a/ui/flux/actions/index.es6 b/ui/flux/actions/index.es6 deleted file mode 100644 index 77bfa50b2..000000000 --- a/ui/flux/actions/index.es6 +++ /dev/null @@ -1,265 +0,0 @@ -import dispatcher from "../dispatcher"; -import constants from "./constants"; -import {fetchJson, pluck, years, identity} from "../../tools"; -import URI from "urijs"; -import {toImmutable} from "nuclear-js"; -import * as endpoints from "./endpoints"; - -let options2arr = options => Object.keys(options) - .filter(key => options[key].selected) - .map(identity); - -let addFilters = (filters, url) => null === filters ? url : - new URI(url).addSearch({ - bidTypeId: options2arr(filters.bidTypes.options), - bidSelectionMethod: options2arr(filters.bidSelectionMethods.options), - procuringEntityId: options2arr(filters.procuringEntities.options), - year: Object.keys(filters.years).filter(year => filters.years[year]) - }).toString(); - -//Given [[1,2,3], ['a','b','c']] will produce [[1, 'a'], [2, 'b'], [3, 'c']] -let transpose = ([head, ...tail]) => head.map((el, index) => [el].concat(tail.map(pluck(index)))); - -let response2obj = (field, arr) => arr.reduce((obj, elem) => { - obj[elem._id] = elem[field]; - return obj; -}, {}); - - -let transformOverviewData = ([bidplansResponse, tendersResponse, awardsResponse]) => { - let bidplans = response2obj('count', bidplansResponse); - let tenders = response2obj('count', tendersResponse); - let awards = response2obj('count', awardsResponse); - return Object.keys(tenders).map(year => ({ - year: year, - bidplan: bidplans[year], - tender: tenders[year], - award: awards[year] - })); -}; - -let transformCostEffectivenessData = ([tenderResponse, awardResponse]) => { - let tender = response2obj('totalTenderAmount', tenderResponse); - let award = response2obj('totalAwardAmount', awardResponse); - return Object.keys(tender).map(year => ({ - year: year, - tender: tender[year], - diff: tender[year] - award[year] - })) -}; - -let transformBidPeriodData = ([tenders, awards]) => { - let awardsHash = response2obj('averageAwardDays', awards); - return tenders.map(tender => ({ - year: tender._id, - tender: tender.averageTenderDays, - award: awardsHash[tender._id] || 0 - }) - ) -}; - -let transformCancelledData = raw => raw.map(({_id, totalCancelledTendersAmount}) => ({ - year: _id, - count: totalCancelledTendersAmount -})); - -export default { - changeTab(slug){ - dispatcher.dispatch(constants.TAB_CHANGED, slug); - }, - - toggleYear(year, selected){ - dispatcher.dispatch(constants.YEAR_TOGGLED, { - year: year, - selected: selected - }); - }, - - changeContentWidth(newWidth){ - dispatcher.dispatch(constants.CONTENT_WIDTH_CHANGED, newWidth); - }, - - loadServerSideYearFilteredData(filters = null){ - let load = url => fetchJson(addFilters(filters, url)); - load('/api/topTenLargestTenders').then(data => dispatcher.dispatch(constants.TOP_TENDERS_DATA_UPDATED, data)); - load('/api/topTenLargestAwards').then(data => dispatcher.dispatch(constants.TOP_AWARDS_DATA_UPDATED, data)); - }, - - loadData(filters = null){ - let load = url => fetchJson(addFilters(filters, url)); - this.loadServerSideYearFilteredData(filters); - Promise.all([ - load(endpoints.COUNT_BID_PLANS_BY_YEAR), - load(endpoints.COUNT_TENDERS_BY_YEAR), - load(endpoints.COUNT_AWARDS_BY_YEAR) - ]) - .then(transformOverviewData) - .then(data => dispatcher.dispatch(constants.OVERVIEW_DATA_UPDATED, data)); - - load(endpoints.PLANNED_FUNDING_BY_LOCATION).then(data => dispatcher.dispatch(constants.LOCATION_UPDATED, data)); - - Promise.all([ - load(endpoints.COST_EFFECTIVENESS_TENDER_AMOUNT), - load(endpoints.COST_EFFECTIVENESS_AWARD_AMOUNT) - ]) - .then(transformCostEffectivenessData) - .then(data => dispatcher.dispatch(constants.COST_EFFECTIVENESS_DATA_UPDATED, data)); - - Promise.all([ - load(endpoints.AVERAGE_TENDER_PERIOD), - load(endpoints.AVERAGE_AWARD_PERIOD) - ]).then(transformBidPeriodData).then(data => dispatcher.dispatch(constants.BID_PERIOD_DATA_UPDATED, data)); - - load(endpoints.TENDER_PRICE_BY_VN_TYPE_YEAR).then(data => dispatcher.dispatch(constants.BID_TYPE_DATA_UPDATED, data)); - - load(endpoints.TOTAL_CANCELLED_TENDERS_BY_YEAR) - .then(transformCancelledData) - .then(data => dispatcher.dispatch(constants.CANCELLED_DATA_UPDATED, data)); - - load('/api/averageNumberOfTenderers').then(data => dispatcher.dispatch(constants.AVERAGED_TENDERS_DATA_UPDATED, data)); - - load('/api/percentTendersCancelled').then(data => dispatcher.dispatch(constants.CANCELLED_PERCENTS_DATA_UPDATED, data)); - - load('/api/percentTendersUsingEBid').then(data => dispatcher.dispatch(constants.PERCENT_EBID_DATA_UPDATED, data)); - }, - - bootstrap(){ - this.loadData(); - Promise.all([ - fetchJson('/api/ocds/bidType/all'), - fetchJson('/api/ocds/bidSelectionMethod/all') - ]).then(([bidTypes, bidSelectionMethods]) => dispatcher.dispatch(constants.FILTERS_DATA_UPDATED, { - years: years().reduce((map, year) => map.set(year, true), toImmutable({})), - bidTypes: { - open: true, - options: bidTypes.reduce((accum, bidType) => { - let {id} = bidType; - accum[id] = { - id: id, - description: bidType.description, - selected: false - }; - return accum; - }, {}) - }, - bidSelectionMethods: { - open: true, - options: bidSelectionMethods - .filter(method => !!method._id) - .reduce((accum, {_id}) => { - accum[_id] = { - id: _id, - description: _id, - selected: false - }; - return accum; - }, {}) - }, - procuringEntities: { - open: true, - options: [] - } - })); - }, - - loadComparisonData(criteria, filters){ - if("none" == criteria) return; - let comparisonUrl = new URI(endpoints.COST_EFFECTIVENESS_TENDER_AMOUNT).addSearch({ - groupByCategory: criteria, - pageSize: 3 - }).toString(); - fetchJson(addFilters(filters, comparisonUrl)).then(comparisonData => { - dispatcher.dispatch(constants.COMPARISON_CRITERIA_NAMES_UPDATED, comparisonData); - let addComparisonFilters = url => { - let uri = new URI(addFilters(filters, url)); - let criteriaValues = comparisonData.map(pluck("bidTypeId" == criteria ? "0" : "_id")); - return criteriaValues - .map(criteriaValue => uri.clone().addSearch(criteria, criteriaValue).toString()) - .concat([ - uri.clone().addSearch(criteria, criteriaValues).addSearch('invert', 'true').toString() - ]); - }; - - let load = url => Promise.all(addComparisonFilters(url).map(fetchJson)); - - Promise.all([ - load(endpoints.COUNT_BID_PLANS_BY_YEAR), - load(endpoints.COUNT_TENDERS_BY_YEAR), - load(endpoints.COUNT_AWARDS_BY_YEAR) - ]) - .then(data => transpose(data).map(transformOverviewData)) - .then(data => dispatcher.dispatch(constants.OVERVIEW_COMPARISON_DATA_UPDATED, data)); - - Promise.all([ - load(endpoints.COST_EFFECTIVENESS_TENDER_AMOUNT), - load(endpoints.COST_EFFECTIVENESS_AWARD_AMOUNT) - ]) - .then(data => transpose(data).map(transformCostEffectivenessData)) - .then(data => dispatcher.dispatch(constants.COST_EFFECTIVENESS_COMPARISON_DATA_UPDATED, data)); - - Promise.all([ - load(endpoints.AVERAGE_TENDER_PERIOD), - load(endpoints.AVERAGE_AWARD_PERIOD) - ]) - .then(data => transpose(data).map(transformBidPeriodData)) - .then(data => dispatcher.dispatch(constants.BID_PERIOD_COMPARISON_DATA_UPDATED, data)); - - load(endpoints.TENDER_PRICE_BY_VN_TYPE_YEAR).then(data => - dispatcher.dispatch(constants.BID_TYPE_COMPARISON_DATA_UPDATED, data)); - - load(endpoints.TOTAL_CANCELLED_TENDERS_BY_YEAR) - .then(data => data.map(transformCancelledData)) - .then(data => dispatcher.dispatch(constants.CANCELLED_COMPARISON_DATA_UPDATED, data)); - }); - }, - - setFiltersBox(slug){ - dispatcher.dispatch(constants.FILTER_BOX_CHANGED, slug); - }, - - toggleFilter(slug, open){ - dispatcher.dispatch(constants.FILTER_TOGGLED, { - slug: slug, - open: open - }) - }, - - toggleFilterOption(slug, option, selected){ - dispatcher.dispatch(constants.FILTER_OPTIONS_TOGGLED, {slug, option, selected}); - }, - - updateProcuringEntityQuery(newQuery){ - dispatcher.dispatch(constants.PROCURING_ENTITY_QUERY_UPDATED, newQuery); - if (newQuery.length >= 3) { - fetchJson(new URI('/api/ocds/organization/procuringEntity/all').addSearch('text', newQuery).toString()) - .then(data => { - dispatcher.dispatch(constants.PROCURING_ENTITIES_UPDATED, data.reduce((accum, procuringEntity) => { - let {id} = procuringEntity; - accum[id] = procuringEntity; - return accum; - }, {})) - }); - } - }, - - updateComparisonCriteria(criteria){ - dispatcher.dispatch(constants.COMPARISON_CRITERIA_UPDATED, criteria); - }, - - setLocale(loc){ - localStorage.lang = loc; - dispatcher.dispatch(constants.LOCALE_CHANGED, loc); - }, - - applyFilters(){ - dispatcher.dispatch(constants.FILTERS_APPLIED); - }, - - resetFilters(){ - dispatcher.dispatch(constants.FILTERS_RESET); - }, - - toggleCancelledPercents(bool){ - dispatcher.dispatch(constants.CANCELLED_TYPE_TOGGLED, bool); - } -} \ No newline at end of file diff --git a/ui/flux/index.es6 b/ui/flux/index.es6 deleted file mode 100644 index cc6c90820..000000000 --- a/ui/flux/index.es6 +++ /dev/null @@ -1,216 +0,0 @@ -import dispatcher from "./dispatcher"; -import actions from "./actions"; -import globalStateStore from "./stores/global-state"; -import {toImmutable} from "nuclear-js"; -import {Map, List} from "immutable"; -import {identity, pluck} from '../tools'; - -dispatcher.registerStores({ - globalState: globalStateStore -}); - -let ensureArray = maybeArray => Array.isArray(maybeArray) ? maybeArray : []; - -let ensureList = maybeList => maybeList || List(); - -let obj2arr = obj => Object.keys(obj).map(key => obj[key]); - -let getSelectedYears = [ - ['globalState', 'filters', 'years'], - years => years ? years.filter(identity).keySeq().toArray() : [] -]; - -let yearOnly = year => ({ - year: year -}); - -let maxFieldExceptYear = datum => - Math.max.apply(Math, Object.keys(datum).filter(key => "year" != key).map(key => datum[key] || 0)); - -let getCriteriaNames = (compare, comparisonCriteriaNames, filters) => { - switch(compare){ - case "bidTypeId": - return ensureArray(comparisonCriteriaNames).map(name => - filters.getIn(['bidTypes', 'options']).find(bidType => bidType.get('id') == name[0]).get('description') - ); - default: - return ensureArray(comparisonCriteriaNames).map(pluck('_id')); - } -} - -let mkDataGetter = ({path, getFillerDatum = yearOnly, horizontal = false, getMaxField = maxFieldExceptYear}) => [ - ['globalState', 'compareBy'], - ['globalState', 'data', path], - ['globalState', 'comparisonData', path], - getSelectedYears, - ['globalState', 'comparisonCriteriaNames'], - ['globalState', 'filters'], - (compare, rawData, comparisonData, years, comparisonCriteriaNames, filters) => { - let parse = data => { - let dataByYear = []; - years.forEach(year => dataByYear[year] = getFillerDatum(year)); - data.forEach(datum => { - if(dataByYear[+datum.year]) dataByYear[+datum.year] = datum - }); - return obj2arr(dataByYear); - }; - if(compare){ - let arrOfData = ensureArray(comparisonData).map(parse); - let maxValue = Math.max.apply(Math, arrOfData.map(data => - Math.max.apply(Math, data.map(getMaxField)) - )); - return { - criteriaNames: getCriteriaNames(compare, comparisonCriteriaNames, filters), - [horizontal ? 'xAxisRange' : 'yAxisRange']: [0, maxValue], - data: arrOfData - } - } else { - return parse(ensureArray(rawData)); - } - } -]; - -let getOverviewData = mkDataGetter({ - path: "overview" -}); - -let getOverview = [ - ['globalState', 'compareBy'], - getOverviewData, - ['globalState', 'data', 'topTenders'], - ['globalState', 'data', 'topAwards'], - (compare, overviewData, topTenders, topAwards) => { - return { - compare: compare, - overview: overviewData, - topTenders: topTenders, - topAwards: topAwards - } - } -]; - -let getCostEffectiveness = mkDataGetter({ - path: "costEffectiveness", - getFillerDatum(year){ - return { - year: year, - tender: 0, - diff: 0 - } - }, - getMaxField({tender, diff}){ - return tender + diff; - } -}); - -let getBidPeriod = mkDataGetter({ - path: "bidPeriod", - horizontal: true, - getMaxField({tender, award}){ - return tender + award; - } -}); - -let getCancelled = mkDataGetter({ - path: "cancelled" -}); - -let getCancelledPercents = mkDataGetter({ - path: "cancelledPercents" -}); - -let getBidType = [ - ['globalState', 'compareBy'], - ['globalState', 'data', 'bidType'], - ['globalState', 'comparisonData', 'bidType'], - ['globalState', 'filters', 'years'], - ['globalState', 'comparisonCriteriaNames'], - ['globalState', 'filters'], - (compare, rawData, comparisonData, rawYears, comparisonCriteriaNames, filters) => { - let years = ensureList(rawYears); - let collectCats = arrOfData => arrOfData.reduce((cats, data) => data - .filter(bidType => years.get(bidType.get('year')), false) - .groupBy(bidType => bidType.get('procurementMethodDetails')) - .reduce((cats, bidTypes) => { - let name = bidTypes.getIn([0, 'procurementMethodDetails']) || 'unspecified'; - return cats.set(name, Map({ - _id: name, - totalTenderAmount: 0 - })) - }, cats) - , Map()); - - let parse = cats => data => data - .reduce((cats, datum) => { - let name = datum.get('procurementMethodDetails') || 'unspecified'; - let path = [name, 'totalTenderAmount']; - return cats.setIn(path, cats.getIn(path) + datum.get('totalTenderAmount')); - }, cats) - .toList().toJS() - - if (compare) { - let cats = collectCats(ensureArray(comparisonData)); - let arrOfData = ensureArray(comparisonData).map(parse(cats)); - let maxValue = Math.max.apply(Math, arrOfData.map(data => - Math.max.apply(Math, data.map(pluck('totalTenderAmount'))) - )); - return { - criteriaNames: getCriteriaNames(compare, comparisonCriteriaNames, filters), - yAxisRange: [0, maxValue], - data: arrOfData - } - } else { - let data = ensureList(rawData); - let cats = collectCats([data]); - return parse(cats)(data); - } - } -]; - -let getAvgTenders = mkDataGetter({ - path: "avgTenders" -}); - -let getPercentEbid = mkDataGetter({ - path: "percentEbid" -}); - -let getTender = [ - ['globalState', 'compareBy'], - getCostEffectiveness, - getBidPeriod, - getBidType, - getCancelled, - getCancelledPercents, - ['globalState', 'showPercentsCancelled'], - getAvgTenders, - getPercentEbid, - (compare, costEffectiveness, bidPeriod, bidType, cancelled, cancelledPercents, showPercentsCancelled, avgNrBids, - percentEbid) => { - return { - compare, - costEffectiveness, - bidPeriod, - bidType, - showPercentsCancelled, - cancelled: showPercentsCancelled ? cancelledPercents : cancelled, - avgNrBids, - percentEbid - } - } -]; - -let getGlobalState = [ - [], - state => state - .set('overview', dispatcher.evaluate(getOverview)) - .set('tender', dispatcher.evaluate(getTender)) -]; - -export default { - actions: actions, - onUpdate(cb, trigger = false){ - dispatcher.observe(getGlobalState, cb); - if(trigger) cb(dispatcher.evaluate(getGlobalState)); - } -} \ No newline at end of file diff --git a/ui/flux/stores/global-state.es6 b/ui/flux/stores/global-state.es6 deleted file mode 100644 index 18e974b4a..000000000 --- a/ui/flux/stores/global-state.es6 +++ /dev/null @@ -1,94 +0,0 @@ -import {Store, toImmutable} from "nuclear-js"; -import constants from "../actions/constants"; -import keyMirror from "keymirror"; -import {identity} from "../../tools"; -import actions from "../actions"; - -let mapIn = (obj, path, cb) => obj.updateIn(path, items => items.map(cb)); - -export let tabs = keyMirror({ - OVERVIEW: null, - PLANNING: null, - TENDER_AWARD: null -}); - -let store = Store({ - getInitialState(){ - return toImmutable({ - filtersBox: false, - compareBy: "", - tab: tabs.OVERVIEW, - contentWidth: 0, - data: {}, - comparisonData: {}, - procuringEntityQuery: "", - filters: {}, - locale: localStorage.lang || 'en', - showPercentsCancelled: false - }) - }, - - initialize(){ - let updateData = (path, pipe = identity) => (state, data) => state.setIn(['data', path], pipe(data)); - - this.on(constants.TAB_CHANGED, (state, tab) => state.set('tab', tab)); - this.on(constants.YEAR_TOGGLED, (state, {year, selected}) => { - let newState = state.setIn(['filters', 'years', year], selected); - actions.loadServerSideYearFilteredData(newState.get('filters').toJS()); - return newState; - }); - this.on(constants.CONTENT_WIDTH_CHANGED, (state, newWidth) => state.set('contentWidth', newWidth)); - this.on(constants.COST_EFFECTIVENESS_DATA_UPDATED, updateData('costEffectiveness')); - this.on(constants.BID_TYPE_DATA_UPDATED, updateData('bidType', toImmutable)); - this.on(constants.LOCATION_UPDATED, updateData('locations', toImmutable)); - this.on(constants.BID_PERIOD_DATA_UPDATED, updateData('bidPeriod')); - this.on(constants.OVERVIEW_DATA_UPDATED, updateData('overview')); - this.on(constants.CANCELLED_DATA_UPDATED, updateData('cancelled')); - this.on(constants.CANCELLED_PERCENTS_DATA_UPDATED, updateData('cancelledPercents')); - this.on(constants.TOP_TENDERS_DATA_UPDATED, updateData('topTenders')); - this.on(constants.TOP_AWARDS_DATA_UPDATED, updateData('topAwards')); - this.on(constants.AVERAGED_TENDERS_DATA_UPDATED, updateData('avgTenders')); - this.on(constants.PERCENT_EBID_DATA_UPDATED, updateData('percentEbid')); - this.on(constants.FILTER_BOX_CHANGED, (state, slug) => state.set('filtersBox', slug)); - this.on(constants.FILTERS_DATA_UPDATED, (state, data) => state.set('filters', toImmutable(data))); - this.on(constants.FILTER_TOGGLED, (state, {slug, open}) => state.setIn(['filters', slug, 'open'], open)); - this.on(constants.FILTER_OPTIONS_TOGGLED, (state, {slug, option, selected}) => - state.setIn(['filters', slug, 'options', option, 'selected'], selected) - ); - - this.on(constants.FILTERS_APPLIED, state => (actions.loadData(state.get('filters').toJS()), state)); - - this.on(constants.FILTERS_RESET, state => - mapIn(state, ['filters'], filter => - filter.has('options') ? - mapIn(filter, ['options'], option => option.set('selected', false)) : - filter - ) - ); - - this.on(constants.PROCURING_ENTITY_QUERY_UPDATED, (state, newQuery) => state.set('procuringEntityQuery', newQuery)); - this.on(constants.PROCURING_ENTITIES_UPDATED, (state, procuringEntities) => - state.setIn(['filters', 'procuringEntities', 'options'], toImmutable(procuringEntities))); - this.on(constants.COMPARISON_CRITERIA_UPDATED, (state, criteria) => { - let newState = state.set('compareBy', criteria); - actions.loadComparisonData(newState.get('compareBy'), newState.get('filters').toJS()); - return newState; - }); - this.on(constants.OVERVIEW_COMPARISON_DATA_UPDATED, (state, data) => state.setIn(['comparisonData', 'overview'], data)); - this.on(constants.COST_EFFECTIVENESS_COMPARISON_DATA_UPDATED, (state, data) => - state.setIn(['comparisonData', 'costEffectiveness'], data)); - this.on(constants.BID_PERIOD_COMPARISON_DATA_UPDATED, (state, data) => - state.setIn(['comparisonData', 'bidPeriod'], data)); - this.on(constants.BID_TYPE_COMPARISON_DATA_UPDATED, (state, data) => - state.setIn(['comparisonData', 'bidType'], data.map(toImmutable))); - this.on(constants.CANCELLED_COMPARISON_DATA_UPDATED, (state, data) => - state.setIn(['comparisonData', 'cancelled'], data)); - this.on(constants.COMPARISON_CRITERIA_NAMES_UPDATED, (state, names) => state.set('comparisonCriteriaNames', names)) - - this.on(constants.LOCALE_CHANGED, (state, loc) => state.set('locale', loc)); - - this.on(constants.CANCELLED_TYPE_TOGGLED, (state, percents) => state.set('showPercentsCancelled', percents)); - } -}); - -export default store; \ No newline at end of file diff --git a/ui/gulpfile.js b/ui/gulpfile.js index 3177f5559..2bab4805d 100644 --- a/ui/gulpfile.js +++ b/ui/gulpfile.js @@ -4,7 +4,7 @@ var webpack = require("webpack"); var webpackConfig = require('./webpack.config.js'); var gnf = require('gulp-npm-files'); -gulp.task("default", ['webpack', 'copy-deps', 'copy-html']); +gulp.task("default", ['webpack', 'copy-deps', 'copy-html', 'copy-assets']); gulp.task("webpack", function(cb){ webpack(webpackConfig, function(err, stats) { @@ -20,4 +20,8 @@ gulp.task("copy-deps", function(){ gulp.task("copy-html", function(){ gulp.src("index.html").pipe(gulp.dest('public/ui/')); +}); + +gulp.task("copy-assets", function(){ + gulp.src("assets/**/*").pipe(gulp.dest("./public/ui/assets")); }); \ No newline at end of file diff --git a/ui/index.html b/ui/index.html index a22656ed8..4fb8ad099 100644 --- a/ui/index.html +++ b/ui/index.html @@ -2,7 +2,7 @@ - OCVN + OCE diff --git a/ui/index.jsx b/ui/index.jsx index 1296e6f7c..8172a3519 100644 --- a/ui/index.jsx +++ b/ui/index.jsx @@ -1,22 +1,94 @@ -import App from "./components/app"; -import React from "react"; import ReactDOM from "react-dom"; -import flux from "./flux"; -import {debounce} from "./tools"; -var TRANSLATIONS = { - en: require('./languages/en_US.json'), - vn: require('./languages/vn_VN.json') -}; +import OCApp from "./oce"; +import OverviewTab from './oce/tabs/overview'; +import LocationTab from './oce/tabs/location'; +import CompetitivenessTab from './oce/tabs/competitiveness'; +import EfficiencyTab from './oce/tabs/efficiency'; +import EProcurementTab from './oce/tabs/e-procurement'; +import {fetchJson} from "./oce/tools"; +import {Map} from "immutable"; +import styles from "./style.less"; -flux.onUpdate(state => - ReactDOM.render( - , - document.getElementById('dg-container') - ) -, true); +function getBidTypeDescription(__, {id, description}){ + switch(+id){ + case 12: return __("Unspecified") + " #1"; + case 15: return __("Unspecified") + " #2"; + default: return description; + } +} -window.addEventListener("resize", debounce(function(){ - flux.actions.changeContentWidth(document.querySelector('.years-bar').offsetWidth) -})); +class OCEChild extends OCApp{ + constructor(props) { + super(props); + this.registerTab(OverviewTab); + this.registerTab(LocationTab); + this.registerTab(CompetitivenessTab); + this.registerTab(EfficiencyTab); + this.registerTab(EProcurementTab); + } -flux.actions.bootstrap(); \ No newline at end of file + fetchBidTypes(){ + fetchJson('/api/ocds/bidType/all').then(data => + this.setState({ + bidTypes: data.reduce((map, datum) => + map.set(datum.id, getBidTypeDescription(this.__.bind(this), datum)), Map()) + }) + ); + } + + render(){ + return
this.setState({menuBox: ""})}> +
+
+

+ {this.__('e-Procurement')} + {this.__('Toolkit')} +

+
+
+ {this.filters()} + {this.comparison()} + {this.exportBtn()} +
+
+ {this.languageSwitcher()} +
+
+ +
+
+ {this.content()} +
+
+
+ {this.yearsBar()} +
+
 
+
; + } +} + +ReactDOM.render(, document.getElementById('dg-container')); + +if("ocvn.developmentgateway.org" == location.hostname){ + (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= + function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; + e=o.createElement(i);r=o.getElementsByTagName(i)[0]; + e.src='//www.google-analytics.com/analytics.js'; + r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); + ga('create','UA-78202947-1');ga('send','pageview'); +} \ No newline at end of file diff --git a/ui/oce/comparison.jsx b/ui/oce/comparison.jsx index 28cb55eb1..9b7cb66cd 100644 --- a/ui/oce/comparison.jsx +++ b/ui/oce/comparison.jsx @@ -33,7 +33,7 @@ class Comparison extends translatable(PureRenderCompoent){ render(){ let {compareBy, comparisonData, comparisonCriteriaValues, filters, requestNewComparisonData, years, width - , translations} = this.props; + , translations, styling} = this.props; if(!comparisonCriteriaValues.length) return null; let Component = this.getComponent(); let decoratedFilters = this.constructor.decorateFilters(filters, compareBy, comparisonCriteriaValues); @@ -63,6 +63,7 @@ class Comparison extends translatable(PureRenderCompoent){ title={this.getTitle(index)} width={width / 2} translations={translations} + styling={styling} {...rangeProp} />
diff --git a/ui/oce/data-fetcher.es6 b/ui/oce/data-fetcher.es6 deleted file mode 100644 index 38b69ae52..000000000 --- a/ui/oce/data-fetcher.es6 +++ /dev/null @@ -1,51 +0,0 @@ -import translatable from "./translatable"; -import Component from "./pure-render-component"; -import {callFunc} from "./tools"; -import {fromJS} from "immutable"; -import URI from "urijs"; -const API_ROOT = '/api'; - -let fetchEP = url => fetch(url.clone().query(""), { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: url.query() -}).then(callFunc('json')); - -class DataFetcher extends translatable(Component){ - buildUrl(ep){ - let {filters} = this.props; - return new URI(API_ROOT + '/' + ep).addSearch(filters.toJS()); - } - - fetch(){ - let {endpoint, endpoints} = this.constructor; - let {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(!promise) return; - promise.then(this.transform).then(fromJS).then(data => requestNewData([], data)); - } - - transform(data){return data;} - - getData(){return this.props.data} - - componentDidMount(){ - this.fetch(); - } - - componentDidUpdate(prevProps){ - if(this.props.filters != prevProps.filters) this.fetch(); - } -} - -DataFetcher.propTypes = { - filters: React.PropTypes.object.isRequired, - data: React.PropTypes.object, - requestNewData: React.PropTypes.func.isRequired -}; - -export default DataFetcher; \ No newline at end of file diff --git a/ui/oce/filters/inputs/multiple-select.jsx b/ui/oce/filters/inputs/multiple-select.jsx index 2046cb275..09864054b 100644 --- a/ui/oce/filters/inputs/multiple-select.jsx +++ b/ui/oce/filters/inputs/multiple-select.jsx @@ -1,6 +1,6 @@ import translatable from "../../translatable"; import Component from "../../pure-render-component"; -import {fromJS} from "immutable"; +import {fromJS, Set} from "immutable"; import {fetchJson} from "../../tools"; class MultipleSelect extends translatable(Component){ @@ -28,6 +28,18 @@ class MultipleSelect extends translatable(Component){ if(ENDPOINT) fetchJson(`/api/${ENDPOINT}`).then(data => this.setState({options: fromJS(this.transform(data))})); } + selectAll(){ + this.props.onUpdateAll( + Set( + this.getOptions().map(this.getId).toArray() + ) + ); + } + + selectNone(){ + this.props.onUpdateAll(Set()); + } + render(){ let options = this.getOptions(); let {selected, onToggle} = this.props; @@ -36,6 +48,15 @@ class MultipleSelect extends translatable(Component){
{this.getTitle()} ({this.getSelectedCount()}/{totalOptions}) +
{options.map((option, key) => ( diff --git a/ui/oce/filters/tabs/index.jsx b/ui/oce/filters/tabs/index.jsx index 6b6f139d4..0e838f4b3 100644 --- a/ui/oce/filters/tabs/index.jsx +++ b/ui/oce/filters/tabs/index.jsx @@ -11,6 +11,7 @@ class Tab extends translatable(React.Component){ selected.delete(id) : selected.add(id)) } + onUpdateAll={onUpdate.bind(null, slug)} translations={translations} /> } diff --git a/ui/oce/frontend-year-filterable.es6 b/ui/oce/frontend-year-filterable.es6 index 490a96dc3..db861a6be 100644 --- a/ui/oce/frontend-year-filterable.es6 +++ b/ui/oce/frontend-year-filterable.es6 @@ -11,7 +11,9 @@ let frontendYearFilterable = 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 => { diff --git a/ui/oce/index.jsx b/ui/oce/index.jsx index e089e5910..f6a082d3e 100644 --- a/ui/oce/index.jsx +++ b/ui/oce/index.jsx @@ -1,7 +1,8 @@ import cn from "classnames"; import {fromJS, Map, Set} from "immutable"; -import {fetchJson, debounce, callFunc} from "./tools"; +import {fetchJson, debounce} from "./tools"; import URI from "urijs"; +import Filters from "./filters"; let range = (from, to) => from > to ? [] : [from].concat(range(from + 1, to)); const MIN_YEAR = 2010; @@ -9,7 +10,7 @@ const MAX_YEAR = 2020; const MENU_BOX_COMPARISON = "menu-box"; const MENU_BOX_FILTERS = 'filters'; -export default class OCApp extends React.Component{ +class OCApp extends React.Component{ constructor(props){ super(props); this.tabs = []; @@ -157,6 +158,7 @@ export default class OCApp extends React.Component{ bidTypes={bidTypes} width={width} translations={this.constructor.TRANSLATIONS[locale]} + styling={this.constructor.STYLING} />; } @@ -202,13 +204,7 @@ export default class OCApp extends React.Component{ downloadExcel(){ this.setState({exporting: true}); - let isSafari = navigator.userAgent.indexOf("Safari") && !navigator.userAgent.indexOf("Chrom");//excludes both Chrome and Chromium let url = new URI('/api/ocds/excelExport').addSearch(this.state.filters.toJS()).addSearch('year', this.state.selectedYears.toArray()); - if(isSafari){ - window.open(url, "_blank"); - this.setState({exporting: false}); - return; - } fetch(url.clone().query(""), { method: 'POST', headers: { @@ -216,6 +212,13 @@ export default class OCApp extends React.Component{ }, body: url.query() }).then(response => { + let {userAgent} = navigator; + let isSafari = -1 < userAgent.indexOf("Safari") && -1 == userAgent.indexOf("Chrom");//excludes both Chrome and Chromium + if(isSafari){ + location.href = url; + this.setState({exporting: false}); + return; + } let [_, filename] = response.headers.get('Content-Disposition').split("filename="); response.blob().then(blob => { var link = document.createElement('a'); @@ -249,3 +252,18 @@ export default class OCApp extends React.Component{
} } + +OCApp.TRANSLATIONS = { + us: {} +}; + +OCApp.Filters = Filters; + +OCApp.STYLING = { + charts: { + axisLabelColor: undefined, + traceColors: [] + } +}; + +export default OCApp; \ No newline at end of file diff --git a/ui/oce/tabs/competitiveness.jsx b/ui/oce/tabs/competitiveness.jsx index 4fdb7aea7..b29fdc6a2 100644 --- a/ui/oce/tabs/competitiveness.jsx +++ b/ui/oce/tabs/competitiveness.jsx @@ -1,6 +1,5 @@ import Tab from "./index"; import CostEffectiveness from "../visualizations/charts/cost-effectiveness"; -import BidSelectionMethod from "../visualizations/charts/bid-selection-method"; import AvgNrBids from "../visualizations/charts/avg-nr-bids"; class Competitiveness extends Tab{ @@ -8,5 +7,5 @@ class Competitiveness extends Tab{ } Competitiveness.icon = "competitive"; -Competitiveness.visualizations = [CostEffectiveness, BidSelectionMethod, AvgNrBids]; +Competitiveness.visualizations = [CostEffectiveness, AvgNrBids]; export default Competitiveness; \ No newline at end of file diff --git a/ui/oce/tabs/index.jsx b/ui/oce/tabs/index.jsx index 00e543001..0936ac885 100644 --- a/ui/oce/tabs/index.jsx +++ b/ui/oce/tabs/index.jsx @@ -1,9 +1,9 @@ -import DataFetcher from "../data-fetcher"; +import Visualization from "../visualization"; import {Set, List} from "immutable"; import {Map} from "immutable"; import DefaultComparison from "../comparison"; -class Tab extends DataFetcher{ +class Tab extends Visualization{ maybeWrap({dontWrap, getName}, index, rendered){ return dontWrap ? rendered :

{getName(this.__.bind(this))}

@@ -13,11 +13,11 @@ class Tab extends DataFetcher{ compare(Component, index){ let {compareBy, comparisonData, comparisonCriteriaValues, filters, requestNewComparisonData, years, bidTypes - , width, translations} = this.props; + , width, translations, styling} = this.props; let {compareWith: CustomComparison} = Component; let Comparison = CustomComparison || DefaultComparison; return } render(){ - let {filters, compareBy, requestNewData, data, years, width, translations} = this.props; + let {filters, compareBy, requestNewData, data, years, width, translations, styling} = this.props; return
- {this.constructor.visualizations.map((Component, index) => - compareBy && Component.comparable ? this.compare(Component, index) : - this.maybeWrap(Component, index, - requestNewData([index], data)} - data={data.get(index)} - years={years} - width={width} - translations={translations} - /> - ) - )} + {this.constructor.visualizations.map((Component, index) => + compareBy && Component.comparable ? this.compare(Component, index) : + this.maybeWrap(Component, index, + requestNewData([index], data)} + data={data.get(index)} + years={years} + width={width} + translations={translations} + styling={styling} + /> + ) + )}
} static computeYears(data){ if(!data) return Set(); return this.visualizations.reduce((years, visualization, index) => - visualization.computeYears ? - years.union(visualization.computeYears(data.get(index))) : - years - , Set()) + visualization.computeYears ? + years.union(visualization.computeYears(data.get(index))) : + years + , Set()) } static computeComparisonYears(data){ if(!data) return Set(); return this.visualizations.reduce((years, visualization, index) => - years.union( - data.get(index, List()).reduce((years, data, index) => - visualization.computeYears ? - years.union(visualization.computeYears(data)) : - years + years.union( + data.get(index, List()).reduce((years, data, index) => + visualization.computeYears ? + years.union(visualization.computeYears(data)) : + years + , Set()) + ) , Set()) - ) - , Set()) } } diff --git a/ui/oce/tabs/location/index.jsx b/ui/oce/tabs/location/index.jsx index a2402a38e..4df03024f 100644 --- a/ui/oce/tabs/location/index.jsx +++ b/ui/oce/tabs/location/index.jsx @@ -1,61 +1,70 @@ import Tab from "../index"; -import frontendYearFilterable from "../../frontend-year-filterable"; -import { Map, TileLayer } from 'react-leaflet'; -import {pluck} from "../../tools"; -import Cluster from "./cluster"; -import Location from "./location"; +import {Set} from "immutable"; +import TenderLocations from "../../visualizations/map/tender-locations"; +import cn from "classnames"; +import style from "./style.less"; -class LocationTab extends frontendYearFilterable(Tab){ - getData(){ - return super.getData() - .groupBy(location => location.getIn(['budget.projectLocation', '_id'])) - .map(locations => locations.reduce((reducedLocation, location) => { - return { - "_id": location.getIn(['budget.projectLocation', '_id']), - "name": location.getIn(['budget.projectLocation', 'description']), - "amount": reducedLocation.amount + location.get('totalPlannedAmount'), - "count": reducedLocation.count + location.get('recordsCount'), - "coords": location.getIn(['budget.projectLocation', 'geometry', 'coordinates']).toJS() - } - }, { - "amount": 0, - "count": 0 - })) - .toArray() +class LocationTab extends Tab{ + static getName(__){ + return __("Location") } - getMaxAmount(){ - return Math.max(0, ...this.getData().map(pluck('amount'))); + static computeYears(data){ + if(!data) return Set(); + return this.LAYERS.reduce((years, visualization, index) => + visualization.computeYears ? + years.union(visualization.computeYears(data.get(index))) : + years + , Set()) } - static getName(__){ - return __("Location") + constructor(props){ + super(props); + this.state = { + currentLayer: 0, + dropdownOpen: false + } + } + + maybeGetSwitcher(){ + let {LAYERS} = this.constructor; + if(this.constructor.LAYERS.length > 1){ + let {currentLayer, dropdownOpen} = this.state; + return
+
this.setState({dropdownOpen: !dropdownOpen})}> + + +
+
+ } } render(){ + let {currentLayer} = this.state; + let {data, requestNewData} = this.props; + let Map = this.constructor.LAYERS[currentLayer]; return
- - - - {this.getData().map(location => ( - - ))} - - + {this.maybeGetSwitcher()} + requestNewData([currentLayer], data)} + />
} } LocationTab.icon = "planning"; -LocationTab.endpoint = 'plannedFundingByLocation'; LocationTab.computeComparisonYears = null; +LocationTab.LAYERS = [TenderLocations]; export default LocationTab; diff --git a/ui/oce/tabs/location/style.less b/ui/oce/tabs/location/style.less new file mode 100644 index 000000000..fbb131d2b --- /dev/null +++ b/ui/oce/tabs/location/style.less @@ -0,0 +1,9 @@ +.content.map-content{ + position: relative; + .layer-switcher{ + position: absolute; + left: 50px; + top: 10px; + z-index: 1; + } +} \ No newline at end of file diff --git a/ui/oce/visualization.es6 b/ui/oce/visualization.es6 index 03a198293..b239cea8e 100644 --- a/ui/oce/visualization.es6 +++ b/ui/oce/visualization.es6 @@ -1,9 +1,53 @@ -import DataFetcher from "./data-fetcher"; +import translatable from "./translatable"; +import Component from "./pure-render-component"; +import {callFunc} from "./tools"; +import {fromJS} from "immutable"; +import URI from "urijs"; +const API_ROOT = '/api'; -class Visualization extends DataFetcher{ +let fetchEP = url => fetch(url.clone().query(""), { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: url.query() +}).then(callFunc('json')); +class Visualization extends translatable(Component){ + buildUrl(ep){ + let {filters} = this.props; + return new URI(API_ROOT + '/' + ep).addSearch(filters.toJS()); + } + + fetch(){ + let {endpoint, endpoints} = this.constructor; + let {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(!promise) return; + promise.then(this.transform).then(fromJS).then(data => requestNewData([], data)); + } + + transform(data){return data;} + + getData(){return this.props.data} + + componentDidMount(){ + this.fetch(); + } + + componentDidUpdate(prevProps){ + if(this.props.filters != prevProps.filters) this.fetch(); + } } Visualization.comparable = true; +Visualization.propTypes = { + filters: React.PropTypes.object.isRequired, + data: React.PropTypes.object, + requestNewData: React.PropTypes.func.isRequired +}; + export default Visualization; \ No newline at end of file diff --git a/ui/oce/visualizations/charts/avg-nr-bids.jsx b/ui/oce/visualizations/charts/avg-nr-bids.jsx index 1c064c6b1..c10320d5a 100644 --- a/ui/oce/visualizations/charts/avg-nr-bids.jsx +++ b/ui/oce/visualizations/charts/avg-nr-bids.jsx @@ -10,7 +10,10 @@ class AvgNrBids extends FrontendYearFilterableChart{ return [{ x: data.map(pluckImm('year')).toArray(), y: data.map(pluckImm('averageNoTenderers')).toArray(), - type: 'bar' + type: 'bar', + marker: { + color: this.props.styling.charts.traceColors[0] + } }]; } @@ -18,16 +21,10 @@ class AvgNrBids extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Number"), - type: "category", - titlefont: { - color: "#cc3c3b" - } + type: "category" }, yaxis: { - title: this.__("Amount"), - titlefont: { - color: "#cc3c3b" - } + title: this.__("Amount") } } } diff --git a/ui/oce/visualizations/charts/bid-period.jsx b/ui/oce/visualizations/charts/bid-period.jsx index 435529250..1e76c06f2 100644 --- a/ui/oce/visualizations/charts/bid-period.jsx +++ b/ui/oce/visualizations/charts/bid-period.jsx @@ -4,8 +4,8 @@ import {Map} from "immutable"; let ensureNonNegative = a => a < 0 ? 0 : a; -class BidPeriod extends FrontendYearFilterableChart{ - transform([tenders, awards]){ +class BidPeriod extends FrontendYearFilterableChart { + transform([tenders, awards]) { let awardsHash = response2obj('averageAwardDays', awards); return tenders.map(tender => ({ year: tender._id, @@ -14,40 +14,57 @@ class BidPeriod extends FrontendYearFilterableChart{ })) }; - getData(){ + getData() { let data = super.getData(); - if(!data) return []; + if (!data) return []; let years = data.map(pluckImm('year')).toArray(); return [{ x: data.map(pluckImm('tender')).map(ensureNonNegative).toArray(), y: years, name: this.__("Tender"), type: "bar", - orientation: 'h' + orientation: 'h', + marker: { + color: this.props.styling.charts.traceColors[0] + } }, { x: data.map(pluckImm('award')).map(ensureNonNegative).toArray(), y: years, name: this.__("Award"), type: "bar", - orientation: 'h' + orientation: 'h', + marker: { + color: this.props.styling.charts.traceColors[1] + } }]; } - getLayout(){ + getLayout() { + let annotations = []; + let data = super.getData(); + if(data){ + annotations = data.map((imm, index) => { + let sum = (ensureNonNegative(imm.get('tender')) + ensureNonNegative(imm.get('award'))).toFixed(2); + return { + y: index, + x: sum, + xanchor: 'left', + yanchor: 'middle', + text: this.__('Total:') + ' ' + sum, + showarrow: false + } + }).toArray(); + } + return { + annotations, barmode: "stack", xaxis: { - title: this.__("Days"), - titlefont: { - color: "#cc3c3b" - } + title: this.__("Days") }, yaxis: { title: this.__("Years"), - type: "category", - titlefont: { - color: "#cc3c3b" - } + type: "category" } } } diff --git a/ui/oce/visualizations/charts/bid-selection-method.jsx b/ui/oce/visualizations/charts/bid-selection-method.jsx deleted file mode 100644 index baa506c9b..000000000 --- a/ui/oce/visualizations/charts/bid-selection-method.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import FrontendYearFilterableChart from "./frontend-filterable"; -import {pluckImm, max} from "../../tools"; -import {Map, Set} from "immutable"; -import Comparison from "../../comparison"; -import Plotly from "plotly.js/lib/core"; - -class BidSelectionMethod extends FrontendYearFilterableChart{ - static toCats(data){ - if(!data) return Map(); - return data - .groupBy(pluckImm('procurementMethodDetails')) - .map((bidTypes, _id) => Map({ - _id, - totalTenderAmount: bidTypes.map(pluckImm('totalTenderAmount')).reduce((a, b) => a + b, 0) - })); - } - - getCats(data){ - let cats = this.props.cats || Map(); - return cats.merge(this.constructor.toCats(data)); - } - - getData(){ - let raw = super.getData(); - if(!raw) return []; - let data = this.getCats(raw); - - return [{ - x: data.map(imm => imm.get('_id') || this.__('Unspecified')).toArray(), - y: data.map(pluckImm('totalTenderAmount')).toArray(), - type: 'bar' - }]; - } - - getLayout(){ - return { - xaxis: { - title: this.__("Method"), - type: "category", - titlefont: { - color: "#cc3c3b" - } - }, - yaxis: { - title: this.__("Amount"), - titlefont: { - color: "#cc3c3b" - } - } - } - } -} - -BidSelectionMethod.endpoint = 'tenderPriceByVnTypeYear'; -BidSelectionMethod.getName = __ => __('Bid selection method'); -BidSelectionMethod.UPDATABLE_FIELDS = ['data', 'years', 'cats']; - -class BidSelectionMethodComparison extends Comparison{ - render(){ - let {compareBy, comparisonData, comparisonCriteriaValues, filters, requestNewComparisonData, years} = this.props; - if(!comparisonCriteriaValues.length) return null; - let Component = this.getComponent(); - let decoratedFilters = this.constructor.decorateFilters(filters, compareBy, comparisonCriteriaValues); - let rangeProp, cats; - - if(comparisonData.count() == comparisonCriteriaValues.length + 1){ - let uniformData = comparisonData.map(comparisonDatum => - Component.toCats(Component.filterDataByYears(comparisonDatum, years)) - ).reduce( - (a, b) => a.mergeWith( - (a, b) => a.get('totalTenderAmount') > b.get('totalTenderAmount') ? a : b - , b) - ); - - let maxValue = uniformData.map(pluckImm('totalTenderAmount')).reduce(max, 0); - - rangeProp = { - yAxisRange: [0, maxValue] - }; - - cats = uniformData.map(uniformDatum => uniformDatum.set('totalTenderAmount', undefined)) - } else { - cats = Map(); - rangeProp = {}; - } - - return this.wrap(decoratedFilters.map((comparisonFilters, index) =>
- requestNewComparisonData([index], data)} - data={comparisonData.get(index)} - years={years} - title={this.getTitle(index)} - cats={cats} - {...rangeProp} - /> -
- )); - } -} - - -BidSelectionMethod.compareWith = BidSelectionMethodComparison; - -export default BidSelectionMethod; \ No newline at end of file diff --git a/ui/oce/visualizations/charts/cancelled/amounts.jsx b/ui/oce/visualizations/charts/cancelled/amounts.jsx index c4dc7775e..8e3cdbcde 100644 --- a/ui/oce/visualizations/charts/cancelled/amounts.jsx +++ b/ui/oce/visualizations/charts/cancelled/amounts.jsx @@ -16,7 +16,10 @@ class CancelledFunding extends FrontendYearFilterableChart{ x: data.map(pluckImm('year')).toArray(), y: data.map(pluckImm('count')).toArray(), type: 'scatter', - fill: 'tonexty' + fill: 'tonexty', + marker: { + color: this.props.styling.charts.traceColors[0] + } }]; } @@ -24,17 +27,10 @@ class CancelledFunding extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Years"), - type: 'category', - titlefont: { - color: "#cc3c3b" - } + type: 'category' }, yaxis: { - title: this.__("Amount"), - titlefont: { - color: "#cc3c3b" - }, - range: this.props.yAxisRange + title: this.__("Amount") } } } diff --git a/ui/oce/visualizations/charts/cancelled/percents.jsx b/ui/oce/visualizations/charts/cancelled/percents.jsx index cafd65f67..dcef22fbc 100644 --- a/ui/oce/visualizations/charts/cancelled/percents.jsx +++ b/ui/oce/visualizations/charts/cancelled/percents.jsx @@ -9,7 +9,10 @@ class CancelledPercents extends FrontendYearFilterableChart{ x: data.map(pluckImm('year')).toArray(), y: data.map(pluckImm('percentCancelled')).toArray(), type: 'scatter', - fill: 'tonexty' + fill: 'tonexty', + marker: { + color: this.props.styling.charts.traceColors[0] + } }]; } @@ -17,17 +20,10 @@ class CancelledPercents extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Years"), - type: 'category', - titlefont: { - color: "#cc3c3b" - } + type: 'category' }, yaxis: { - title: this.__("Percent"), - titlefont: { - color: "#cc3c3b" - }, - range: this.props.yAxisRange + title: this.__("Percent") } } } diff --git a/ui/oce/visualizations/charts/cost-effectiveness.jsx b/ui/oce/visualizations/charts/cost-effectiveness.jsx index bfbf582b7..e23206fdd 100644 --- a/ui/oce/visualizations/charts/cost-effectiveness.jsx +++ b/ui/oce/visualizations/charts/cost-effectiveness.jsx @@ -22,12 +22,18 @@ class CostEffectiveness extends FrontendYearFilterableChart{ x: years, y: data.map(pluckImm('tender')).toArray(), name: this.__('Bid price'), - type: 'bar' + type: 'bar', + marker: { + color: this.props.styling.charts.traceColors[0] + } }, { x: years, y: data.map(pluckImm('diff')).toArray(), name: this.__('Difference'), - type: 'bar' + type: 'bar', + marker: { + color: this.props.styling.charts.traceColors[1] + } }]; } @@ -36,16 +42,10 @@ class CostEffectiveness extends FrontendYearFilterableChart{ barmode: "stack", xaxis: { title: this.__("Years"), - type: "category", - titlefont: { - color: "#cc3c3b" - } + type: "category" }, yaxis: { - title: this.__("Amount"), - titlefont: { - color: "#cc3c3b" - } + title: this.__("Amount") } } } diff --git a/ui/oce/visualizations/charts/frontend-filterable.es6 b/ui/oce/visualizations/charts/frontend-filterable.es6 index c7e1c7c10..7b02ab33b 100644 --- a/ui/oce/visualizations/charts/frontend-filterable.es6 +++ b/ui/oce/visualizations/charts/frontend-filterable.es6 @@ -1,8 +1,12 @@ import frontentYearFilterable from "../../frontend-year-filterable"; import Chart from "./index.jsx"; -import Plotly from "plotly.js/lib/core"; -class FrontendYearFilterableChart extends frontentYearFilterable(Chart){} +class FrontendYearFilterableChart extends frontentYearFilterable(Chart){ + hasNoData(){ + let data = super.getData(); + return data && data.isEmpty(); + } +} FrontendYearFilterableChart.UPDATABLE_FIELDS = ['data', 'years']; diff --git a/ui/oce/visualizations/charts/index.jsx b/ui/oce/visualizations/charts/index.jsx index fdcce65fa..ddcf53a02 100644 --- a/ui/oce/visualizations/charts/index.jsx +++ b/ui/oce/visualizations/charts/index.jsx @@ -2,6 +2,8 @@ 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') @@ -13,12 +15,21 @@ class Chart extends Visualization{ } getDecoratedLayout(){ - var {title, xAxisRange, yAxisRange} = this.props; + var {title, xAxisRange, yAxisRange, styling} = this.props; var layout = this.getLayout(); layout.width = this.props.width; 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 + }; + + layout.yaxis.titlefont = { + color: styling.charts.axisLabelColor + } + } return layout; } @@ -42,10 +53,18 @@ class Chart extends Visualization{ } } + hasNoData(){ + return 0 == this.getData().length; + } + render(){ - return -
- + let hasNoData = this.hasNoData(); + return
+ {hasNoData &&
{this.__('No data')}
} + +
+ +
} } @@ -55,4 +74,11 @@ Chart.getMaxField = data => data.flatten().filter((value, key) => value && "year 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 +}).isRequired; + export default Chart; \ No newline at end of file diff --git a/ui/oce/visualizations/charts/index.less b/ui/oce/visualizations/charts/index.less new file mode 100644 index 000000000..01a61a530 --- /dev/null +++ b/ui/oce/visualizations/charts/index.less @@ -0,0 +1,18 @@ +.chart-container{ + position: relative; + .no-data-msg{ + position: absolute; + top: 25%; + left: 50%; + width: 300px; + margin-left: -150px; + padding: 50px; + z-index: 1; + background: #efefef; + border: 1px solid #333; + text-align: center; + vertical-align: middle; + font-weight: bold; + font-size: 1.5em; + } +} \ No newline at end of file diff --git a/ui/oce/visualizations/charts/overview.jsx b/ui/oce/visualizations/charts/overview.jsx index 0b8205c8e..9da23fb05 100644 --- a/ui/oce/visualizations/charts/overview.jsx +++ b/ui/oce/visualizations/charts/overview.jsx @@ -23,11 +23,14 @@ class OverviewChart extends FrontendYearFilterableChart{ tender: this.__("Tender") }; let years = data.map(pluckImm('year')).toArray(); - return Object.keys(LINES).map(key => ({ + return Object.keys(LINES).map((key, index) => ({ x: years, y: data.map(pluckImm(key)).toArray(), type: 'scatter', - name: LINES[key] + name: LINES[key], + marker: { + color: this.props.styling.charts.traceColors[index] + } }) ); } @@ -36,16 +39,10 @@ class OverviewChart extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Years"), - type: "category", - titlefont: { - color: "#cc3c3b" - } + type: "category" }, yaxis: { - title: this.__("Count"), - titlefont: { - color: "#cc3c3b" - } + title: this.__("Count") } } } diff --git a/ui/oce/visualizations/charts/percent-e-bid.jsx b/ui/oce/visualizations/charts/percent-e-bid.jsx index d27157781..d56f55ecd 100644 --- a/ui/oce/visualizations/charts/percent-e-bid.jsx +++ b/ui/oce/visualizations/charts/percent-e-bid.jsx @@ -9,7 +9,10 @@ class PercentEbid extends FrontendYearFilterableChart{ x: data.map(pluckImm('year')).toArray(), y: data.map(pluckImm('percentageTendersUsingEbid')).toArray(), type: 'scatter', - fill: 'tonexty' + fill: 'tonexty', + marker: { + color: this.props.styling.charts.traceColors[0] + } }]; } @@ -17,17 +20,10 @@ class PercentEbid extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Years"), - type: 'category', - titlefont: { - color: "#cc3c3b" - } + type: 'category' }, yaxis: { - title: "%", - titlefont: { - color: "#cc3c3b" - }, - range: this.props.yAxisRange + title: "%" } } } diff --git a/ui/oce/visualizations/charts/percent-e-procurement.jsx b/ui/oce/visualizations/charts/percent-e-procurement.jsx index 5d3a2f64c..196b4a585 100644 --- a/ui/oce/visualizations/charts/percent-e-procurement.jsx +++ b/ui/oce/visualizations/charts/percent-e-procurement.jsx @@ -9,7 +9,10 @@ class PercentEProcurement extends FrontendYearFilterableChart{ x: data.map(pluckImm('year')).toArray(), y: data.map(pluckImm('percentEgp')).toArray(), type: 'scatter', - fill: 'tonexty' + fill: 'tonexty', + marker: { + color: this.props.styling.charts.traceColors[0] + } }]; } @@ -17,17 +20,10 @@ class PercentEProcurement extends FrontendYearFilterableChart{ return { xaxis: { title: this.__("Years"), - type: 'category', - titlefont: { - color: "#cc3c3b" - } + type: 'category' }, yaxis: { - title: "%", - titlefont: { - color: "#cc3c3b" - }, - range: this.props.yAxisRange + title: "%" } } } diff --git a/ui/oce/tabs/location/cluster.jsx b/ui/oce/visualizations/map/cluster.jsx similarity index 98% rename from ui/oce/tabs/location/cluster.jsx rename to ui/oce/visualizations/map/cluster.jsx index dc97c0bdf..778400298 100644 --- a/ui/oce/tabs/location/cluster.jsx +++ b/ui/oce/visualizations/map/cluster.jsx @@ -50,4 +50,4 @@ Cluster.propTypes = { maxAmount: React.PropTypes.number.isRequired }; -export default Cluster; \ No newline at end of file +export default Cluster; diff --git a/ui/oce/visualizations/map/index.jsx b/ui/oce/visualizations/map/index.jsx new file mode 100644 index 000000000..d820e6507 --- /dev/null +++ b/ui/oce/visualizations/map/index.jsx @@ -0,0 +1,36 @@ +import frontendYearFilterable from "../../frontend-year-filterable"; +import { Map, TileLayer } from 'react-leaflet'; +import {pluck} from "../../tools"; +import Cluster from "./cluster"; +import Location from "./location"; +import Visualization from "../../visualization"; + +class MapVisual extends frontendYearFilterable(Visualization){ + getMaxAmount(){ + return Math.max(0, ...this.getData().map(pluck('amount'))); + } + + render(){ + return + + + {this.getData().map(location => ( + + ))} + + + } +} + +MapVisual.propTypes = {}; +MapVisual.computeComparisonYears = null; + +export default MapVisual; diff --git a/ui/oce/tabs/location/location/index.jsx b/ui/oce/visualizations/map/location/index.jsx similarity index 100% rename from ui/oce/tabs/location/location/index.jsx rename to ui/oce/visualizations/map/location/index.jsx diff --git a/ui/oce/tabs/location/location/marker.jsx b/ui/oce/visualizations/map/location/marker.jsx similarity index 99% rename from ui/oce/tabs/location/location/marker.jsx rename to ui/oce/visualizations/map/location/marker.jsx index b43f35ec2..985d0ddf2 100644 --- a/ui/oce/tabs/location/location/marker.jsx +++ b/ui/oce/visualizations/map/location/marker.jsx @@ -7,4 +7,4 @@ export default class Location extends Marker{ this.leafletElement.options.data = this.props.data; } } -} \ No newline at end of file +} diff --git a/ui/oce/visualizations/map/tender-locations.jsx b/ui/oce/visualizations/map/tender-locations.jsx new file mode 100644 index 000000000..1d0ffe5e0 --- /dev/null +++ b/ui/oce/visualizations/map/tender-locations.jsx @@ -0,0 +1,41 @@ +import Map from "./index.jsx"; + +class TenderLocations extends Map{ + transform(data){ + return data.filter(location => { + if(!location['items.deliveryLocation'].geometry){ + console.warn("Invalid delivery location! Missing geometry!", location); + return false; + } + return true; + }); + } + + getData(){ + let data = super.getData(); + if(!data) return []; + return data + .groupBy(location => location.getIn(['items.deliveryLocation', '_id'])) + .map(locations => locations.reduce((reducedLocation, location) => { + return { + "_id": location.getIn(['items.deliveryLocation', '_id']), + "name": location.getIn(['items.deliveryLocation', 'description']), + "amount": reducedLocation.amount + location.get('totalTendersAmount'), + "count": reducedLocation.count + location.get('tendersCount'), + "coords": location.getIn(['items.deliveryLocation', 'geometry', 'coordinates']).toJS() + } + }, { + "amount": 0, + "count": 0 + })) + .toArray() + } + + static getLayerName(__){ + return __('Tender locations'); + } +} + +TenderLocations.endpoint = 'fundingByTenderDeliveryLocation'; + +export default TenderLocations; \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index c95c0ca04..01a8a783f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -56,7 +56,6 @@ "leaflet.markercluster": "https://github.com/Leaflet/Leaflet.markercluster.git#leaflet-0.7", "less": "^2.5.1", "less-loader": "^2.2.0", - "nuclear-js": "^1.1.1", "plotly.js": "^1.5.1", "react": "^15.1.0", "react-addons-test-utils": "^15.1.0", diff --git a/ui/pom.xml b/ui/pom.xml index 5d5d0b4fb..327ec7ea4 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -1,168 +1,169 @@ - 4.0.0 + 4.0.0 - ui - jar + ui + jar OCExplorer UI OCExplorer UI Module - - UTF-8 - 1.8 - org.devgateway.toolkit.ui.UIWebApplication - + + UTF-8 + 1.8 + org.devgateway.toolkit.ui.UIWebApplication + false + - + org.devgateway.ocds oce 0.0.1-SNAPSHOT - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - org.springframework.boot - spring-boot-starter-jetty - - - - - - - - - - true - src/main/java - - **/*.properties - - - - false - ${project.basedir} - - public/** - - - public/index.js - public/index.js.map - - - - - - - maven-clean-plugin - 2.6.1 - - - - public - - **/* - - false - - - - - - - - com.github.eirslett - frontend-maven-plugin - 0.0.29 - - - - install node and npm - - install-node-and-npm - - generate-resources - - v4.2.2 - 2.14.9 - - - - - npm install - - npm - - - - install - - - - - npm run build - - npm - - generate-resources - - run build - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - - repackage - - - - - - - - - - - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + + + + true + src/main/java + + **/*.properties + + + + + false + ${project.basedir} + + public/** + + + public/index.js + public/index.js.map + + + + + + + maven-clean-plugin + 2.6.1 + + + + public + + **/* + + false + + + + + + + com.github.eirslett + frontend-maven-plugin + 0.0.29 + + + + install node and npm + + install-node-and-npm + + generate-resources + + v4.2.2 + 2.14.9 + + + + + npm install + + npm + + + + install + + + + + npm run build + + npm + + generate-resources + + run build + + + + + + + + ${skip.frontend.build} + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + + repackage + + + + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + diff --git a/ui/style.less b/ui/style.less new file mode 100644 index 000000000..b1da8431d --- /dev/null +++ b/ui/style.less @@ -0,0 +1,362 @@ +@red: #cc3c3b; +@base-unit: 1em; + +.content{ + margin-top: 3em; + margin-bottom: 64px; +} + +.container-fluid{ + padding: 0; +} + +header.branding{ + background-color: @red; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + position: fixed; + top: 0; + right: 0; + left: 0; + height: 64px; + z-index: 1002; + + h1, small{ + color: white; + } + + h1{ + font-size: 24px; + margin-top: 10px; + small{ + text-transform: uppercase; + display: block; + font-size: 18px; + } + } + + .menu{ + color: white; + display: inline-block; + float: right; + padding-left: 15%; + text-transform: uppercase; + width: 100%; + @media only screen and (max-width: 1280px) { + margin-left: -140px; + } + &>div{ + cursor: pointer; + padding: 20px; + } + } + + .language-switcher{ + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + .flag { + box-shadow: 0px 1px 10px 1px #B53636; + -webkit-box-shadow: 0px 1px 10px 1px #B53636; + -moz-box-shadow: 0px 1px 10px 1px #B53636; + -o-box-shadow: 0px 1px 10px 1px #B53636; + margin-right: @base-unit / 2.5; + width: 2.2em; + &:hover { + box-shadow: 0px 1px 10px 1px #db5151; + -webkit-box-shadow: 0px 1px 10px 1px #db5151; + -moz-box-shadow: 0px 1px 10px 1px #db5151; + -o-box-shadow: 0px 1px 10px 1px #db5151; + } + } + } +} + +aside { + z-index: 1; + position: fixed!important; + top: 64px; + bottom: 0; + height: auto; + background-color: #e9e9e9; + border-right: 2px solid #c5c5c5; + + section.col-sm-12 { + padding: 15px 20px 15px 20px; + border-bottom: 1px solid #ccc; + } + + section.description{ + background: #ffffff; + line-height: 120%; + h3 { + color: @red; + font-size: 1em; + margin-top: 10px; + text-transform: uppercase; + } + } +} + +.years-bar { + position: fixed; + bottom: 34px; + border-bottom: 2px solid #c5c5c5; + z-index: 1; + padding-left: 0; + padding-right: 0; + display: table; + height: 30px; + a { + display: table-cell; + background-color: #e6e6e6; + color: #bbbbbb; + text-decoration: none; + padding: 5px 0 5px 20px; + border-right: 2px #f2f2f2 solid; + &:hover { + background-color: #707070; + } + .glyphicon { + visibility: hidden; + } + &:last-child { + border-right: none; + } + &.active { + color: white; + background-color: #808080; + .glyphicon { + visibility: visible; + } + } + } +} + +footer.main-footer{ + position: fixed; + height: 34px; + left: 0; + right: 0; + bottom: 0; + background: @red; + z-index: 1; +} + +@active-color: #808080; +@circle-grey: #666666; +@tab-light-grey: #f2f2f2; + +aside [role=navigation] a{ + border-bottom: 1px solid #c5c5c5; + color: #707070; + display: block; + cursor: pointer; + font-size: 1.05em; + line-height: 190%; + padding: 15px 0 15px 20px; + text-decoration: none; + text-transform: uppercase; + &:hover { + background-color: @active-color; + color: @tab-light-grey; + } + &.active{ + background-color: @active-color; + color: @tab-light-grey; + } + .circle{ + background-color: @circle-grey; + border-radius: 50%; + color: @tab-light-grey; + display: block; + float: left; + height: 1.6em; + margin-right: 0.4em; + text-align: center; + width: 1.6em; + } +} + +.nav-icon { + display:block; + height:1.28em; + margin: auto; + padding-top: 5px; + width: 1.28em; +} + +.content.map-content{ + padding-left: 0; + margin-top: 64px; + padding-right: 0; + overflow: hidden; + &>div{ + height: 95vh; + } +} + +.leaflet-control-zoom{ + position: fixed; +} + +@gray: #b1b1b1; +@active-color: #808080; + +.filters{ + background: #be3131; + border-left: 1px solid transparent; + border-right: 1px solid #a62323; + cursor: pointer; + display: block; + float: left; + height: 100%; + position: relative; + transition: background-color .4s ease-in-out; + text-align: center; + width: 33%; + + &:hover { + background: @active-color; + } + + .glyphicon { + &.glyphicon-menu-down { + font-size: 10px; + margin-left: 5px; + } + } + + &.comparison .box{ + padding: 0 20px 20px 20px + } + + .box{ + color: black; + min-height: 350px; + background: white; + border: 1px solid #c5c5c5; + border-top-color: white; + height: auto; + left: -50%; + opacity: 0; + overflow-y: auto; + padding: 20px; + position: absolute; + text-align: left; + top: 100%; + transform: scaleY(.00001) scaleX(.5) translateY(-150%); + transform-origin: 50% 0; + transition: transform .4s ease-in-out, opacity .4s ease-in-out; + width: 800px; + + section{ + header{ + color: white; + background-color: @gray; + font-weight: bold; + margin-left: -15px; + margin-right: -15px; + padding: 15px; + text-transform: uppercase; + a{ + color: white; + text-transform: none; + } + } + + &.buttons{ + margin-top: 10px; + } + + &.procuring-entities{ + header{ + margin-right: -35px; + } + } + } + + ul.nav li a{ + color: #7c7c7c; + } + + ul.nav li.active a{ + background-color: @red; + color: white; + } + } + + &.compare .box{ + min-height: 50px; + padding: 20px; + width: 500px; + } + + .arrow .glyphicon{ + transform: rotateZ(0); + transition: transform .4s ease-in-out; + } + + &.open{ + background-color: @active-color; + border-left: 1px solid white; + border-right: 1px solid white; + .box{ + opacity: 1; + transform: scaleY(1) scaleX(1) translateY(0); + } + } + + .filter-tab-content{ + max-height: 300px; + overflow-y: auto; + } +} + +.btn { + &.btn-primary { + background-color: #0B7F58; + border-color: #0B7F58; + &:hover { + background-color: darken(#0B7F58, 10%); + } + } +} + +.top-nav-icon { + display: inline-block; + margin-right: 0.3em; + margin-top: -0.3em; + position: relative; + width: 1.2em; +} + +.filters .field.type-ahead { + .search{ + margin-top: 10px; + border-radius: 0; + &:focus{ + border: solid 1px #ccc; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + transition: none; + -moz-transition: none; + -webkit-transition: none; + } + } + + .result-count{ + background-color: #a1a1a1; + color: #eee; + padding: 5px 10px; + font-size: 11px; + } + + .checkbox{ + width: 100%; + } +} \ No newline at end of file diff --git a/ui/webpack.config.js b/ui/webpack.config.js index 7dcd0a2b8..0ae354887 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -7,6 +7,11 @@ delete config.devtool; config.plugins = config.plugins.filter(function(plugin){ return !(plugin instanceof webpack.HotModuleReplacementPlugin); }).concat([ + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: JSON.stringify("production") + } + }), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({ sourceMap: false diff --git a/web/README.md b/web/README.md index 212f0a645..dde8ddfee 100644 --- a/web/README.md +++ b/web/README.md @@ -75,12 +75,6 @@ Shows the difference between the average tender amount and the average winning b `/api/costEffectivenessTenderAmount?bidTypeId=[bid1]&bidTypeId=[bid2]...&procuringEntityId=[proc1]&procuringEntityId=[proc2]....&bidSelectionMethod=[bidSel1]&bidSelectionMethod=[bidSel2]` -### Visualization 2 - Planned Locations - -A map that includes information on planned funding by location (with each location represented by a point, using a reference table). - -`/api/costEffectivenessTenderAmount?procuringEntityId=[proc1]&procuringEntityId=[proc2]` - ### Visualization 3 - Bidding Period diff --git a/web/pom.xml b/web/pom.xml index f39a8edf0..b159bda82 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -127,6 +127,13 @@ junit test + + + org.jminix + jminix + 1.2.0 + + de.flapdoodle.embed diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FundingByLocationController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FundingByLocationController.java index 8bdd9d15d..5a7fa5c00 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FundingByLocationController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FundingByLocationController.java @@ -26,13 +26,13 @@ import javax.validation.Valid; import org.devgateway.ocds.web.rest.controller.request.DefaultFilterPagingRequest; -import org.devgateway.toolkit.persistence.mongo.aggregate.CustomOperation; import org.devgateway.toolkit.persistence.mongo.aggregate.CustomProjectionOperation; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +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; @@ -52,46 +52,6 @@ @CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") @Cacheable public class FundingByLocationController extends GenericOCDSController { - - @ApiOperation(value = "Planned funding by location by year. Returns the total amount of planning.budget" - + " available per planning.budget.projectLocation, grouped by year. " - + "This will return full location information, including geocoding data.") - @RequestMapping(value = "/api/plannedFundingByLocation", - method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") - public List plannedFundingByLocation(@ModelAttribute @Valid final DefaultFilterPagingRequest filter) { - - DBObject vars = new BasicDBObject(); - vars.put("numberOfLocations", new BasicDBObject("$size", "$planning.budget.projectLocation")); - vars.put("planningBudget", "$planning.budget.amount.amount"); - DBObject in = new BasicDBObject("$divide", Arrays.asList("$$planningBudget", "$$numberOfLocations")); - - DBObject let = new BasicDBObject(); - let.put("vars", vars); - let.put("in", in); - - DBObject dividedTotal = new BasicDBObject("$let", let); - - DBObject project = new BasicDBObject(); - project.put("planning.budget.projectLocation", 1); - project.put("cntprj", new BasicDBObject("$literal", 1)); - project.put("planning.budget.amount.amount", 1); - project.put("dividedTotal", dividedTotal); - project.put("year", new BasicDBObject("$year", "$planning.bidPlanProjectDateApprove")); - - Aggregation agg = newAggregation( - match(where("planning").exists(true).and("planning.budget.projectLocation.0").exists(true) - .andOperator(getProcuringEntityIdCriteria(filter))), - new CustomOperation(new BasicDBObject("$project", project)), unwind("$planning.budget.projectLocation"), - group("year", "planning.budget.projectLocation").sum("$dividedTotal").as("totalPlannedAmount") - .sum("$cntprj").as("recordsCount"), - sort(Direction.DESC, "totalPlannedAmount"), skip(filter.getSkip()), limit(filter.getPageSize())); - - AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); - List tagCount = results.getMappedResults(); - return tagCount; - - } - @ApiOperation(value = "Total estimated funding (tender.value) grouped by " + "tender.items.deliveryLocation and also grouped by year." @@ -120,6 +80,49 @@ public List fundingByTenderDeliveryLocation( List tagCount = results.getMappedResults(); return tagCount; } + + + @ApiOperation("Calculates percentage of releases with tender with at least one specified delivery location," + + " that is the array tender.items.deliveryLocation has to have items." + + "Filters out stub tenders, therefore tender.tenderPeriod.startDate has to exist.") + @RequestMapping(value = "/api/qualityFundingByTenderDeliveryLocation", method = { RequestMethod.POST, + RequestMethod.GET }, produces = "application/json") + public List qualityFundingByTenderDeliveryLocation( + @ModelAttribute @Valid final DefaultFilterPagingRequest filter) { + + DBObject project = new BasicDBObject(); + project.putAll(filterProjectMap); + project.put(Fields.UNDERSCORE_ID, "$tender._id"); + project.put("tenderItemsDeliveryLocation", new BasicDBObject("$cond", + Arrays.asList(new BasicDBObject("$gt", + Arrays.asList("$tender.items.deliveryLocation", null)), 1, 0))); + + + DBObject project1 = new BasicDBObject(); + project1.put(Fields.UNDERSCORE_ID, 0); + project1.put("totalTendersWithStartDate", 1); + project1.put("totalTendersWithStartDateAndLocation", 1); + project1.put("percentTendersWithStartDateAndLocation", + new BasicDBObject("$multiply", + Arrays.asList(new BasicDBObject("$divide", + Arrays.asList("$totalTendersWithStartDateAndLocation", "$totalTendersWithStartDate")), + 100))); + + Aggregation agg = newAggregation( + match(where("tender.tenderPeriod.startDate").exists(true) + .andOperator(getDefaultFilterCriteria(filter))), + unwind("$tender.items"), new CustomProjectionOperation(project), + group(Fields.UNDERSCORE_ID_REF).max("tenderItemsDeliveryLocation").as("hasTenderItemsDeliverLocation"), + group().count().as("totalTendersWithStartDate").sum("hasTenderItemsDeliverLocation") + .as("totalTendersWithStartDateAndLocation"), + new CustomProjectionOperation(project1), skip(filter.getSkip()), + limit(filter.getPageSize())); + + AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); + List tagCount = results.getMappedResults(); + return tagCount; + } + } \ No newline at end of file diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelExportController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelExportController.java new file mode 100644 index 000000000..6dd393384 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelExportController.java @@ -0,0 +1,101 @@ +package org.devgateway.ocds.web.rest.controller.export; + +import io.swagger.annotations.ApiOperation; +import org.apache.commons.io.FileCleaningTracker; +import org.apache.commons.io.IOUtils; +import org.devgateway.ocds.persistence.mongo.Release; +import org.devgateway.ocds.web.rest.controller.GenericOCDSController; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.devgateway.ocds.web.util.SettingsUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author idobre + * @since 7/23/16 + */ +@RestController +public class ExcelExportController extends GenericOCDSController { + protected final Logger logger = LoggerFactory.getLogger(ExcelExportController.class); + + @Autowired + private SettingsUtils settingsUtils; + + @Autowired + private ExcelGenerator excelGenerator; + + @Autowired + private FileCleaningTracker fileCleaningTracker; + + @ApiOperation(value = "Export releases in Excel format.") + @RequestMapping(value = "/api/ocds/excelExport", method = {RequestMethod.GET, RequestMethod.POST}) + public void excelExport(@ModelAttribute @Valid final YearFilterPagingRequest filter, + HttpServletResponse response) throws IOException { + + // set the default page size from admin settings + filter.setPageSize(settingsUtils.getExcelBatchSize()); + + long numberOfReleases = mongoTemplate + .count(query(getYearFilterCriteria("tender.tenderPeriod.startDate", filter) + .andOperator(getDefaultFilterCriteria(filter))), Release.class); + + // if we need to export just one file then we don't create an archive + if (numberOfReleases <= settingsUtils.getExcelBatchSize()) { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=" + "excel-export.xlsx"); + + response.getOutputStream().write(excelGenerator.getExcelDownload(filter)); + } else { + File file = File.createTempFile("createZip", ".zip"); + logger.info("Created temp file: " + file.getAbsolutePath()); + + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); + + ZipOutputStream zout = new ZipOutputStream(bos); + zout.setMethod(ZipOutputStream.DEFLATED); + zout.setLevel(Deflater.BEST_COMPRESSION); + + int numberOfPages = (int) Math.ceil((double) numberOfReleases / filter.getPageSize()); + for (int i = 0; i < numberOfPages; i++) { + filter.setPageNumber(i); + + ZipEntry ze = new ZipEntry("excel-export-page " + (i + 1) + ".xlsx"); + + zout.putNextEntry(ze); + byte[] bytes = excelGenerator.getExcelDownload(filter); + zout.write(bytes, 0, bytes.length); + + zout.closeEntry(); + } + zout.close(); + fileCleaningTracker.track(file, file); + IOUtils.closeQuietly(bos); + + response.setContentType("application/zip"); + response.setHeader("Content-Disposition", "attachment; filename=" + "excel-export.zip"); + + InputStream is = new FileInputStream(file); + IOUtils.copy(is, response.getOutputStream()); + response.flushBuffer(); + } + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelGenerator.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelGenerator.java new file mode 100644 index 000000000..b0184271a --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/export/ExcelGenerator.java @@ -0,0 +1,58 @@ +package org.devgateway.ocds.web.rest.controller.export; + +import org.apache.poi.ss.usermodel.Workbook; +import org.devgateway.ocds.persistence.mongo.Release; +import org.devgateway.ocds.persistence.mongo.excel.ExcelFile; +import org.devgateway.ocds.persistence.mongo.excel.ReleaseExportFile; +import org.devgateway.ocds.web.rest.controller.GenericOCDSController; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author idobre + * @since 7/29/16 + */ +@Service +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "excelExport") +public class ExcelGenerator extends GenericOCDSController { + protected final Logger logger = LoggerFactory.getLogger(ExcelGenerator.class); + + /** + * Method that returns a byte array with excel export. + * + * @param filter + * @return + * @throws IOException + */ + @Cacheable + public byte[] getExcelDownload(final YearFilterPagingRequest filter) throws IOException { + PageRequest pageRequest = new PageRequest(filter.getPageNumber(), filter.getPageSize(), + Sort.Direction.ASC, "id"); + + List releases = mongoTemplate + .find(query(getYearFilterCriteria("tender.tenderPeriod.startDate", filter) + .andOperator(getDefaultFilterCriteria(filter))) + .with(pageRequest), Release.class); + + ExcelFile releaseExcelFile = new ReleaseExportFile(releases); + Workbook workbook = releaseExcelFile.createWorkbook(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + workbook.write(baos); + byte[] bytes = baos.toByteArray(); + + return bytes; + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/util/SettingsUtils.java b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java similarity index 69% rename from forms/src/main/java/org/devgateway/toolkit/forms/util/SettingsUtils.java rename to web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java index 3464429fd..f03e05300 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/util/SettingsUtils.java +++ b/web/src/main/java/org/devgateway/ocds/web/util/SettingsUtils.java @@ -1,4 +1,4 @@ -package org.devgateway.toolkit.forms.util; +package org.devgateway.ocds.web.util; import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.devgateway.toolkit.persistence.repository.AdminSettingsRepository; @@ -17,6 +17,8 @@ public class SettingsUtils { protected static Logger logger = LoggerFactory.getLogger(SettingsUtils.class); + private static final Integer EXCELBATCHSIZEDEFAULT = 10000; + @Autowired private AdminSettingsRepository adminSettingsRepository; @@ -28,4 +30,12 @@ private AdminSettings getSettings() { return list.get(0); } } + + public Integer getExcelBatchSize() { + AdminSettings adminSettings = getSettings(); + if (adminSettings.getExcelBatchSize() == null) { + return EXCELBATCHSIZEDEFAULT; + } + return adminSettings.getExcelBatchSize(); + } } diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java b/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java new file mode 100644 index 000000000..8bf588e75 --- /dev/null +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java @@ -0,0 +1,31 @@ +package org.devgateway.toolkit.web.spring; + +import org.jminix.console.application.MiniConsoleApplication; +import org.jminix.console.servlet.SpringMiniConsoleServlet; +import org.jminix.server.WebSpringServerConnectionProvider; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JMXConfiguration { + + @Bean + public WebSpringServerConnectionProvider jMiniXConnectionProvider() { + return new WebSpringServerConnectionProvider(); + } + + @Bean + public MiniConsoleApplication miniConsoleApplication() { + MiniConsoleApplication mca = new MiniConsoleApplication(); + mca.setServerConnectionProvider(jMiniXConnectionProvider()); + return mca; + } + + @Bean + public ServletRegistrationBean jminiXServletRegistration(final MiniConsoleApplication miniConsoleApplication) { + ServletRegistrationBean registration = new ServletRegistrationBean(new SpringMiniConsoleServlet()); + registration.addUrlMappings("/jminix/*"); + return registration; + } +} 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 b4f6015be..4a9d56bd0 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,8 @@ import java.text.SimpleDateFormat; import java.util.TimeZone; +import org.apache.commons.io.FileCleaningTracker; +import org.bson.types.ObjectId; import org.devgateway.ocds.web.cache.generators.GenericPagingRequestKeyGenerator; import org.devgateway.ocds.web.rest.serializers.GeoJsonPointSerializer; import org.springframework.cache.interceptor.KeyGenerator; @@ -26,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @@ -44,6 +47,7 @@ public Jackson2ObjectMapperBuilder objectMapperBuilder() { dateFormatGmt.setTimeZone(TimeZone.getTimeZone("GMT")); builder.serializationInclusion(Include.NON_EMPTY).dateFormat(dateFormatGmt); builder.serializerByType(GeoJsonPoint.class, new GeoJsonPointSerializer()); + builder.serializerByType(ObjectId.class, new ToStringSerializer()); return builder; } @@ -53,4 +57,8 @@ public KeyGenerator genericPagingRequestKeyGenerator(ObjectMapper objectMapper) return new GenericPagingRequestKeyGenerator(objectMapper); } + @Bean(destroyMethod = "exitWhenFinished") + public FileCleaningTracker fileCleaningTracker() { + return new FileCleaningTracker(); + } } diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java index 80f0aee55..1d3749189 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java @@ -13,8 +13,10 @@ import org.devgateway.toolkit.persistence.spring.CustomJPAUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -26,7 +28,7 @@ import org.springframework.security.web.context.SecurityContextPersistenceFilter; /** - * + * * @author mpostelnicu This configures the spring security for the Web project. * An * @@ -36,10 +38,14 @@ @Order(2) // this loads the security config after the forms security (if you use // them overlayed, it must pick that one first) @EnableWebSecurity +@PropertySource("classpath:allowedApiEndpoints.properties") public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired protected CustomJPAUserDetailsService customJPAUserDetailsService; + + @Value("${allowedApiEndpoints}") + private String[] allowedApiEndpoints; @Bean public HttpSessionSecurityContextRepository httpSessionSecurityContextRepository() { @@ -54,29 +60,10 @@ public SecurityContextPersistenceFilter securityContextPersistenceFilter() { return securityContextPersistenceFilter; } - protected String[] allowedApiEndpoints() { - return new String[] { "/api/tenderPriceByOcdsTypeYear/**", "/api/tenderPriceByVnTypeYear/**", - "/api/tenderBidPeriodPercentiles/**", "/api/ocds/release/budgetProjectId/**", - "/api/ocds/release/planningBidNo/**", "/api/plannedFundingByLocation/**", - "/api/costEffectivenessAwardAmount/**", "/api/costEffectivenessTenderAmount/**", - "/api/ocds/organization/all**", "/api/ocds/organization/id/**", "/api/ocds/release/all/**", - "/api/countBidPlansByYear/**", "/api/countTendersByYear/**", "/api/countAwardsByYear/**", - "/api/totalCancelledTendersByYear**", "/api/averageTenderPeriod**", - "/api/ocds/bidSelectionMethod/all**", "/api/topTenLargestAwards**", "/api/topTenLargestTenders**", - "/api/averageAwardPeriod**", "/api/ocds/release/ocid/**", "/api/ocds/bidType/all**", - "/api/ocds/contrMethod/all/**", "/api/ocds/package/budgetProjectId/**", - "/api/ocds/package/planningBidNo/**", "/api/ocds/package/all/**", "/api/ocds/package/ocid/**", - "/api/ocds/location/all/**", "/api/ocds/location/search/**", "/api/averageNumberOfTenderers/**", - "/api/percentTendersCancelled/**", "/api/percentTendersUsingEBid/**", - "/api/qualityAverageTenderPeriod/**", "/api/qualityAverageAwardPeriod/**", - "/api/fundingByTenderDeliveryLocation/**", "/api/percentTendersAwardedWithTwoOrMoreTenderers/**", - "/api/percentTendersWithTwoOrMoreTenderers/**" }; - } - @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/", "/home", "/v2/api-docs/**", "/swagger-ui.html**", "/webjars/**", "/images/**", - "/configuration/**", "/swagger-resources/**", "/dashboard").antMatchers(allowedApiEndpoints()); + "/configuration/**", "/swagger-resources/**", "/dashboard").antMatchers(allowedApiEndpoints); } diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties new file mode 100644 index 000000000..eb348332a --- /dev/null +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -0,0 +1,35 @@ +allowedApiEndpoints=/api/tenderPriceByOcdsTypeYear/**,\ +/api/tenderPriceByVnTypeYear/**,\ +/api/tenderBidPeriodPercentiles/**,\ +/api/ocds/release/budgetProjectId/**,\ +/api/ocds/release/planningBidNo/**,\ +/api/costEffectivenessAwardAmount/**,\ +/api/costEffectivenessTenderAmount/**,\ +/api/ocds/organization/all**,\ +/api/ocds/organization/id/**,\ +/api/ocds/release/all/**,\ +/api/countBidPlansByYear/**,\ +/api/countTendersByYear/**,\ +/api/countAwardsByYear/**,\ +/api/totalCancelledTendersByYear**,\ +/api/averageTenderPeriod**,\ +/api/ocds/bidSelectionMethod/all**,\ +/api/topTenLargestAwards**,\ +/api/topTenLargestTenders**,\ +/api/averageAwardPeriod**,\ +/api/ocds/release/ocid/**,\ +/api/ocds/bidType/all**,\ +/api/ocds/package/budgetProjectId/**,\ +/api/ocds/package/planningBidNo/**,\ +/api/ocds/package/all/**,\ +/api/ocds/package/ocid/**,\ +/api/averageNumberOfTenderers/**,\ +/api/percentTendersCancelled/**,\ +/api/percentTendersUsingEBid/**,\ +/api/qualityAverageTenderPeriod/**,\ +/api/qualityAverageAwardPeriod/**,\ +/api/fundingByTenderDeliveryLocation/**,\ +/api/percentTendersAwardedWithTwoOrMoreTenderers/**,\ +/api/percentTendersWithTwoOrMoreTenderers/**,\ +/api/ocds/excelExport/**,\ +/api/qualityFundingByTenderDeliveryLocation/** \ No newline at end of file diff --git a/web/src/test/resources/json/full-release.json b/web/src/test/resources/json/full-release.json index 832bec5b1..8c3a4d32a 100644 --- a/web/src/test/resources/json/full-release.json +++ b/web/src/test/resources/json/full-release.json @@ -58,6 +58,14 @@ "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", @@ -105,6 +113,12 @@ "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", @@ -208,6 +222,12 @@ "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", @@ -243,6 +263,12 @@ "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",