diff --git a/.gitignore b/.gitignore index d4174b465..a72b8eebf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,11 @@ npm-debug.log .idea rebel.xml /logs/ +/forms/nbactions.xml +/nb-configuration.xml +/forms/nb-configuration.xml +/persistence-mongodb/nb-configuration.xml +/forms/.rebel.xml.bak +/persistence/nb-configuration.xml +/web/nb-configuration.xml +/ui/nb-configuration.xml \ No newline at end of file diff --git a/forms/pom.xml b/forms/pom.xml index 57f3e7eb9..e1376f5ae 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -29,9 +29,9 @@ UTF-8 1.8 - 7.4.0 - 0.10.8 - 1.9 + 7.5.0 + 0.10.11 + 1.10 0.5.5 v20160822 @@ -231,7 +231,7 @@ org.apache.poi poi - ${poi.version} + ${pentaho.poi.version} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.html b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.html new file mode 100644 index 000000000..7a2782cdd --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.html @@ -0,0 +1,15 @@ + + + + +EditUserDashboardPage + + + +
+
+
+
+
+ + diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.java new file mode 100644 index 000000000..741b7c61d --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.edit; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; +import org.devgateway.ocds.forms.wicket.providers.LabelPersistableJpaRepositoryTextChoiceProvider; +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.ocds.persistence.repository.UserDashboardRepository; +import org.devgateway.toolkit.forms.security.SecurityConstants; +import org.devgateway.toolkit.forms.wicket.components.form.Select2MultiChoiceBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextAreaFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; +import org.devgateway.toolkit.persistence.dao.Person; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath("/editUserDashboard") +public class EditUserDashboardPage extends AbstractEditPage { + + private static final long serialVersionUID = -6069250112046118104L; + + @Override + protected UserDashboard newInstance() { + return new UserDashboard(); + } + + @SpringBean + private UserDashboardRepository userDashboardRepository; + + @SpringBean + private PersonRepository personRepository; + + public EditUserDashboardPage(final PageParameters parameters) { + super(parameters); + this.jpaRepository = userDashboardRepository; + this.listPageClass = ListAllDashboardsPage.class; + + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + TextFieldBootstrapFormComponent name = new TextFieldBootstrapFormComponent<>("name"); + name.required(); + editForm.add(name); + + TextAreaFieldBootstrapFormComponent formUrlEncodedBody = + new TextAreaFieldBootstrapFormComponent<>("formUrlEncodedBody"); + formUrlEncodedBody.required(); + formUrlEncodedBody.getField().setEnabled(false); + editForm.add(formUrlEncodedBody); + + Select2MultiChoiceBootstrapFormComponent defaultDashboardUsers = + new Select2MultiChoiceBootstrapFormComponent<>("defaultDashboardUsers", + new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); + defaultDashboardUsers.setEnabled(false); + editForm.add(defaultDashboardUsers); + + Select2MultiChoiceBootstrapFormComponent users = + new Select2MultiChoiceBootstrapFormComponent<>("users", + new LabelPersistableJpaRepositoryTextChoiceProvider<>(personRepository)); + editForm.add(users); + + + } +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.properties new file mode 100644 index 000000000..2522f3134 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/edit/EditUserDashboardPage.properties @@ -0,0 +1,20 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=Edit User Dashboard +name.label=Name +formUrlEncodedBody.label=Filter body +formUrlEncodedBody.help=This is a list of saved filters from the UI and cannot be edited here. +defaultDashboardUsers.label=Default Dashboard For Users +defaultDashboardUsers.help=This is a user setting and can be changed from the User form +users.label=Assigned Users +delete_error_message=Cannot delete this dashboard. It is assigned as the default dashboard for at least one user.\ + Please assign a different default dashboard for the assigned users or select no default dashboard, and then try the delete again. \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage$DashboardsActionPanel.html b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage$DashboardsActionPanel.html new file mode 100644 index 000000000..3d00cc949 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage$DashboardsActionPanel.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.java new file mode 100644 index 000000000..b6a2bffe3 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.list; + +import de.agilecoders.wicket.core.markup.html.bootstrap.button.BootstrapExternalLink; +import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconType; +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.page.edit.EditUserDashboardPage; +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.ocds.persistence.repository.UserDashboardRepository; +import org.devgateway.toolkit.forms.security.SecurityConstants; +import org.devgateway.toolkit.forms.wicket.page.lists.AbstractListPage; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@MountPath(value = "/listAllDashboards") +public class ListAllDashboardsPage extends AbstractListPage { + + /** + * + */ + private static final long serialVersionUID = -324298525712620234L; + @SpringBean + protected UserDashboardRepository userDashboardRepository; + + public class DashboardsActionPanel extends ActionPanel { + + /** + * @param id + * @param model + */ + public DashboardsActionPanel(String id, IModel model) { + super(id, model); + + UserDashboard entity = (UserDashboard) this.getDefaultModelObject(); + + BootstrapExternalLink viewLink = new BootstrapExternalLink("view", Model.of("ui/index.html?dashboardId=" + + entity.getId()), Buttons.Type.Danger) { + }; + viewLink.setLabel(new StringResourceModel("view", ListAllDashboardsPage.this, null)); + viewLink.setIconType(FontAwesomeIconType.eye).setSize(Buttons.Size.Small); + add(viewLink); + + } + } + + public ListAllDashboardsPage(final PageParameters pageParameters) { + super(pageParameters); + this.jpaRepository = userDashboardRepository; + this.editPageClass = EditUserDashboardPage.class; + columns.add(new PropertyColumn( + new Model((new StringResourceModel("name", ListAllDashboardsPage.this, null)).getString()), + "name", "name")); + columns.add(new PropertyColumn(new Model( + (new StringResourceModel("defaultDashboardUsers", ListAllDashboardsPage.this, null)).getString()), + "defaultDashboardUsers", "defaultDashboardUsers")); + + columns.add(new PropertyColumn(new Model( + (new StringResourceModel("users", ListAllDashboardsPage.this, null)).getString()), + "users", "users")); + + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + editPageLink.setVisibilityAllowed(false); + } + + @Override + public ActionPanel getActionPanel(String id, IModel model) { + return new DashboardsActionPanel(id, model); + } +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.properties new file mode 100644 index 000000000..c961f460c --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListAllDashboardsPage.properties @@ -0,0 +1,16 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=All Dashboards +name=Name +defaultDashboardUsers=Default Dashboard For Users +users=Users +view=View \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.java new file mode 100644 index 000000000..b7b7f36ea --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2016 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.forms.wicket.page.list; + +import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.spring.injection.annot.SpringBean; +import org.devgateway.ocds.forms.wicket.providers.PersonDashboardJpaRepositoryProvider; +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.toolkit.forms.security.SecurityConstants; +import org.devgateway.toolkit.forms.wicket.providers.SortableJpaRepositoryDataProvider; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.wicketstuff.annotation.mount.MountPath; + +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_PROCURING_ENTITY) +@MountPath(value = "/listMyDashboards") +public class ListMyDashboardsPage extends ListAllDashboardsPage { + + /** + * + */ + private static final long serialVersionUID = 8105049572554654046L; + + @SpringBean + private PersonRepository personRepository; + + + @Override + public SortableJpaRepositoryDataProvider getProvider() { + return new PersonDashboardJpaRepositoryProvider(userDashboardRepository, personRepository); + } + + public ListMyDashboardsPage(final PageParameters pageParameters) { + super(pageParameters); + } + +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.properties b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.properties new file mode 100644 index 000000000..092626609 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/page/list/ListMyDashboardsPage.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +page.title=My Dashboards \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/LabelPersistableJpaRepositoryTextChoiceProvider.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/LabelPersistableJpaRepositoryTextChoiceProvider.java new file mode 100644 index 000000000..2e83e860b --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/LabelPersistableJpaRepositoryTextChoiceProvider.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +/** + * + */ +package org.devgateway.ocds.forms.wicket.providers; + +import java.util.Collection; + +import org.apache.wicket.model.IModel; +import org.devgateway.toolkit.forms.wicket.providers.AbstractJpaRepositoryTextChoiceProvider; +import org.devgateway.toolkit.persistence.dao.GenericPersistable; +import org.devgateway.toolkit.persistence.dao.Labelable; +import org.devgateway.toolkit.persistence.repository.category.TextSearchableRepository; + +/** + * @author mpostelnicu + * + */ +public class LabelPersistableJpaRepositoryTextChoiceProvider + extends AbstractJpaRepositoryTextChoiceProvider { + + /** + * + */ + private static final long serialVersionUID = -9109118476966448737L; + + public LabelPersistableJpaRepositoryTextChoiceProvider( + final TextSearchableRepository textSearchableRepository) { + super(textSearchableRepository); + } + + public LabelPersistableJpaRepositoryTextChoiceProvider( + final TextSearchableRepository textSearchableRepository, + final IModel> restrictedToItemsModel) { + super(textSearchableRepository, restrictedToItemsModel); + } + + public LabelPersistableJpaRepositoryTextChoiceProvider( + final TextSearchableRepository textSearchableRepository, final Class clazz, + final Boolean addNewElements) { + super(textSearchableRepository, clazz, addNewElements); + } + + @Override + public String getDisplayValue(final T choice) { + return choice.getLabel(); + } +} diff --git a/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/PersonDashboardJpaRepositoryProvider.java b/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/PersonDashboardJpaRepositoryProvider.java new file mode 100644 index 000000000..86416e401 --- /dev/null +++ b/forms/src/main/java/org/devgateway/ocds/forms/wicket/providers/PersonDashboardJpaRepositoryProvider.java @@ -0,0 +1,54 @@ +/** + * + */ +package org.devgateway.ocds.forms.wicket.providers; + +import java.util.Iterator; + +import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.ocds.persistence.repository.UserDashboardRepository; +import org.devgateway.toolkit.forms.WebConstants; +import org.devgateway.toolkit.forms.security.SecurityUtil; +import org.devgateway.toolkit.forms.wicket.providers.SortableJpaRepositoryDataProvider; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +/** + * @author mpost + * + */ +public class PersonDashboardJpaRepositoryProvider extends SortableJpaRepositoryDataProvider { + + private static final long serialVersionUID = -490237568464403107L; + + private UserDashboardRepository userDashboardRepository; + + private PersonRepository personRepository; + + public PersonDashboardJpaRepositoryProvider(UserDashboardRepository jpaRepository, + PersonRepository personRepository) { + super(jpaRepository); + this.personRepository = personRepository; + userDashboardRepository = (UserDashboardRepository) jpaRepository; + } + + /** + * @see SortableDataProvider#iterator(long, long) + */ + @Override + public Iterator iterator(final long first, final long count) { + int page = (int) ((double) first / WebConstants.PAGE_SIZE); + Page findAll = + userDashboardRepository.findDashboardsForPersonId(SecurityUtil.getCurrentAuthenticatedPerson().getId(), + new PageRequest(page, WebConstants.PAGE_SIZE, translateSort())); + return findAll.iterator(); + } + + @Override + public long size() { + return personRepository.getOne(SecurityUtil.getCurrentAuthenticatedPerson().getId()).getDashboards().size(); + } + +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java index 4196152a2..9a6f99a76 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java @@ -28,5 +28,6 @@ private Roles() { public static final String ROLE_ADMIN = "ROLE_ADMIN"; public static final String ROLE_USER = "ROLE_USER"; + public static final String ROLE_PROCURING_ENTITY = "ROLE_PROCURING_ENTITY"; } } 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 45695b565..5bcc5129f 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 @@ -62,14 +62,14 @@ public final void flushMarkupCache() { /** * Add the content of a report (PDF, Excel, RTF) to cache - * + * * @param outputType * @param reportName * @param parameters * @param buffer */ public void addReportToCache(final String outputType, final String reportName, final String parameters, - final byte[] buffer) { + final byte[] buffer) { CacheManager cm = CacheManager.getInstance(); // get the reports cache "reportsCache", declared in ehcache.xml @@ -80,7 +80,7 @@ public void addReportToCache(final String outputType, final String reportName, f /** * Fetch the content of a report from cache - * + * * @param outputType * @param reportName * @param parameters @@ -139,7 +139,20 @@ public void clearReportsCache() { if (cache != null) { cache.removeAll(); } + } + + /** + * Remove from cache all reports api content + */ + public void clearReportsApiCache() { + CacheManager cm = CacheManager.getInstance(); + // get the reports cache "reportsApiCache", declared in ehcache.xml + Cache cache = cm.getCache("reportsApiCache"); + + if (cache != null) { + cache.removeAll(); + } } private String createCacheKey(final String outputType, final String reportName, final String parameters) { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.html new file mode 100644 index 000000000..52c3ed42a --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.html @@ -0,0 +1,30 @@ + + + ListSectionPanel + + + +
+
+

[[title]]

+
+ +
+ + + + + + +
+ +
+
+
+ +
+ +
+
+ + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.java new file mode 100644 index 000000000..9a7e5640a --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/ListViewSectionPanel.java @@ -0,0 +1,142 @@ +package org.devgateway.toolkit.forms.wicket.components; + +import org.apache.wicket.ajax.AjaxRequestTarget; +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.form.Form; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.ResourceModel; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapAddButton; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapDeleteButton; +import org.devgateway.toolkit.persistence.dao.AbstractAuditableEntity; + +import java.util.List; + +/** + * @author idobre + * @since 10/5/16 + * + * Class that displays a list of type with the possibility of adding/removing elements. + * + * @param The current list data type + * @param The parent field data type + */ + +public abstract class ListViewSectionPanel + extends CompoundSectionPanel> { + private WebMarkupContainer listWrapper; + + protected ListView listView; + + public ListViewSectionPanel(final String id) { + super(id); + } + + /** + * Removes a child based on its index + * + * @param index + * @return + */ + private BootstrapDeleteButton getRemoveChildButton(final int index) { + BootstrapDeleteButton removeButton = new BootstrapDeleteButton("remove") { + private static final long serialVersionUID = 1L; + + @Override + protected void onSubmit(final AjaxRequestTarget target, final Form form) { + ListViewSectionPanel.this.getModelObject().remove(index); + listView.removeAll(); + target.add(listWrapper); + } + }; + + removeButton.setOutputMarkupPlaceholderTag(true); + return removeButton; + } + + /** + * Returns the new child button + * + * @return + */ + protected BootstrapAddButton getAddNewChildButton() { + BootstrapAddButton newButton = new BootstrapAddButton("newButton", new ResourceModel("newButton")) { + private static final long serialVersionUID = 1L; + + @Override + protected void onSubmit(final AjaxRequestTarget target, final Form form) { + @SuppressWarnings("unchecked") + T newChild = createNewChild((IModel) ListViewSectionPanel.this.getParent().getDefaultModel()); + ListViewSectionPanel.this.getModel().getObject().add(newChild); + + listView.removeAll(); + target.add(listWrapper); + } + + }; + + newButton.setOutputMarkupPlaceholderTag(true); + return newButton; + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + setOutputMarkupId(true); + setOutputMarkupPlaceholderTag(true); + + listWrapper = new TransparentWebMarkupContainer("listWrapper"); + listWrapper.setOutputMarkupId(true); + add(listWrapper); + + listWrapper.add(new Label("panelTitle", title)); + + listView = new ListView("list", getModel()) { + private static final long serialVersionUID = 1L; + + @Override + protected void populateItem(final ListItem item) { + // we wrap the item model on a compound model so we can use the field ids as property models + final CompoundPropertyModel compoundPropertyModel = new CompoundPropertyModel<>(item.getModel()); + + // we set back the model as the compound model, thus ensures the rest of the items added will benefit + item.setModel(compoundPropertyModel); + + // we add the rest of the items in the listItem + populateCompoundListItem(item); + + // we add the remove button + final BootstrapDeleteButton removeButton = getRemoveChildButton(item.getIndex()); + item.add(removeButton); + } + }; + + listView.setReuseItems(true); + listView.setOutputMarkupId(true); + listWrapper.add(listView); + + final BootstrapAddButton addButton = getAddNewChildButton(); + add(addButton); + } + + /** + * Use the constructor for new children and return the entity after setting + * its parent + * + * @param parentModel the model of the parent + * @return + */ + public abstract T createNewChild(IModel parentModel); + + /** + * Populates the list item elements + * + * @param item + */ + public abstract void populateCompoundListItem(ListItem item); +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java index 693690f12..e05a7088e 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/GenericBootstrapFormComponent.java @@ -159,6 +159,8 @@ public GenericBootstrapFormComponent(final String id, final IModel label field.add(sizeBehavior); border.add(field); + field.setLabel(labelModel); + tooltipLabel = new TooltipLabel("tooltipLabel", id); border.add(tooltipLabel); } 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 c508c9443..d3e6fbb3d 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java @@ -47,6 +47,8 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.resource.JQueryResourceReference; import org.apache.wicket.util.string.StringValue; +import org.devgateway.ocds.forms.wicket.page.list.ListAllDashboardsPage; +import org.devgateway.ocds.forms.wicket.page.list.ListMyDashboardsPage; import org.devgateway.toolkit.forms.WebConstants; import org.devgateway.toolkit.forms.security.SecurityConstants; import org.devgateway.toolkit.forms.security.SecurityUtil; @@ -251,6 +253,17 @@ protected NavbarButton newHomeMenu() { return homeMenu; } + + protected NavbarButton newMyDashboardsMenu() { + // home + NavbarButton menu = new NavbarButton<>(ListMyDashboardsPage.class, + this.getPageParameters(), new ResourceModel("mydashboards")); + menu.setIconType(GlyphIconType.filter); + MetaDataRoleAuthorizationStrategy.authorize(menu, Component.RENDER, + SecurityConstants.Roles.ROLE_PROCURING_ENTITY); + return menu; + } + protected NavbarDropDownButton newAdminMenu() { // admin menu @@ -311,6 +324,10 @@ protected void onComponentTag(final ComponentTag tag) { list.add(new MenuBookmarkablePageLink(EditAdminSettingsPage.class, new StringResourceModel("navbar.adminSettings", BasePage.this, null)) .setIconType(FontAwesomeIconType.briefcase)); + + list.add(new MenuBookmarkablePageLink(ListAllDashboardsPage.class, + new StringResourceModel("navbar.allDashboard", BasePage.this, null)) + .setIconType(FontAwesomeIconType.filter)); list.add(uiBrowserLink); @@ -342,7 +359,9 @@ protected Navbar newNavbar(final String markupId) { navbar.setPosition(Navbar.Position.TOP); navbar.setInverted(true); - navbar.addComponents(NavbarComponents.transform(Navbar.ComponentPosition.RIGHT, newHomeMenu(), newAdminMenu(), + navbar.addComponents(NavbarComponents.transform(Navbar.ComponentPosition.RIGHT, newHomeMenu(), + newMyDashboardsMenu(), + newAdminMenu(), newAccountMenu(), newLogoutMenu())); navbar.addComponents(NavbarComponents.transform(Navbar.ComponentPosition.LEFT, newLanguageMenu())); 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 b5caa1823..affdef1c6 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 @@ -22,3 +22,5 @@ navbar.adminSettings=Settings home=Home navbar.lang=Language navbar.jminix=JMX Console +navbar.allDashboard=All Saved Dashboards +mydashboards=My Dashboards \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java index be32b0bd3..ea817b21d 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditTestFormPage.java @@ -31,8 +31,8 @@ import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.forms.wicket.page.lists.ListTestFormPage; +import org.devgateway.toolkit.forms.wicket.providers.GenericChoiceProvider; import org.devgateway.toolkit.forms.wicket.providers.GenericPersistableJpaRepositoryTextChoiceProvider; -import org.devgateway.toolkit.forms.wicket.providers.ListChoiceProvider; import org.devgateway.toolkit.persistence.dao.TestForm; import org.devgateway.toolkit.persistence.dao.categories.Group; import org.devgateway.toolkit.persistence.dao.categories.Role; @@ -130,7 +130,7 @@ protected void onInitialize() { editForm.add(fileInput); Select2ChoiceBootstrapFormComponent preloadedEntitySelect = new Select2ChoiceBootstrapFormComponent<>( - "preloadedEntitySelect", new ListChoiceProvider<>(groupRepository.findAll())); + "preloadedEntitySelect", new GenericChoiceProvider<>(groupRepository.findAll())); preloadedEntitySelect.required(); editForm.add(preloadedEntitySelect); } 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 1f2e1d6e0..1516f54d7 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 @@ -15,7 +15,7 @@ - })); } } @@ -133,4 +133,4 @@ class BidsByItemComparison extends Comparison{ BidsByItem.compareWith = BidsByItemComparison; -export default BidsByItem; \ No newline at end of file +export default BidsByItem;; \ 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 b37fa6e49..4cb8ff73f 100644 --- a/ui/oce/visualizations/charts/cancelled/amounts.jsx +++ b/ui/oce/visualizations/charts/cancelled/amounts.jsx @@ -43,11 +43,11 @@ class CancelledFunding extends FrontendYearFilterableChart{ getLayout(){ return { xaxis: { - title: this.__("Year"), + title: this.t('charts:cancelledAmounts:xAxisName'), type: 'category' }, yaxis: { - title: this.__("Amount (in VND)") + title: this.t('charts:cancelledAmounts:yAxisName') } } } diff --git a/ui/oce/visualizations/charts/cancelled/index.jsx b/ui/oce/visualizations/charts/cancelled/index.jsx index 43b151ad2..173fd77f7 100644 --- a/ui/oce/visualizations/charts/cancelled/index.jsx +++ b/ui/oce/visualizations/charts/cancelled/index.jsx @@ -20,7 +20,7 @@ class Cancelled extends translatable(React.Component){ let Chart = percents ? Percents : Amounts; return

- {percents ? this.__('Cancelled funding (%)') : this.__('Cancelled funding')} + {percents ? this.t('charts:cancelledPercents:title') : this.t('charts:cancelledAmounts:title')}  

@@ -56,19 +56,17 @@ class LocationWrapper extends translatable(Component){ class Tab extends translatable(Component){} export class OverviewTab extends Tab{ - static getName(__){ - return __('Overview'); - } + static getName(t){return t('maps:tenderLocations:tabs:overview:title')} render(){ let {data} = this.props; let {count, amount} = data; return

- {this.__('Number of Tenders:')} {count} + {this.t('maps:tenderLocations:tabs:overview:nrOfTenders')} {count}

- {this.__('Total Funding for the location:')} {amount.toLocaleString()} + {this.t('maps:tenderLocations:tabs:overview:totalFundingByLocation')} {amount.toLocaleString()}

} @@ -104,7 +102,7 @@ export class ChartTab extends Tab{ ep: this.constructor.Chart.excelEP, filters: decoratedFilters, years, - __: this.__.bind(this) + t: this.t.bind(this) }); return
- {this.__('Number')} - {this.__('Date')} - {this.__('Supplier')} - {this.__('Value')} + {this.t('tables:top10awards:number')} + {this.t('tables:top10awards:date')} + {this.t('tables:top10awards:supplier')} + {this.t('tables:top10awards:value')} @@ -36,7 +36,7 @@ class Awards extends Table{ } } -Awards.getName = __ => __('Top 10 largest awards'); +Awards.getName = t => t('tables:top10awards:title'); Awards.endpoint = 'topTenLargestAwards'; export default Awards; \ No newline at end of file diff --git a/ui/oce/visualizations/tables/frequent-tenderers.jsx b/ui/oce/visualizations/tables/frequent-tenderers.jsx new file mode 100644 index 000000000..4b137713c --- /dev/null +++ b/ui/oce/visualizations/tables/frequent-tenderers.jsx @@ -0,0 +1,57 @@ +import Table from "./index"; +import orgNamesFetching from "../../orgnames-fetching"; +import {pluckImm} from "../../tools"; + +class FrequentTenderers extends orgNamesFetching(Table){ + constructor(...args){ + super(...args); + this.state = this.state || {}; + this.state.showAll = false; + } + + row(entry, index){ + return + {this.getOrgName(entry.getIn(['id', 'tendererId1']))} + {this.getOrgName(entry.getIn(['id', 'tendererId2']))} + {entry.get('value')} + + } + + maybeSlice(flag, list){ + return flag ? list.slice(0, 10) : list; + } + + getOrgsWithoutNamesIds(){ + if(!this.props.data) return []; + return this.props.data.map(pluckImm('id')).flatten().filter(id => !this.state.orgNames[id]).toJS(); + } + + render(){ + if(!this.props.data) return null; + const {showAll} = this.state; + return + + + + + + + + + {this.maybeSlice(!showAll, this.props.data).map(this.row.bind(this))} + {!showAll && this.props.data.count() > 10 && + + } + +
{this.t('tables:frequentTenderers:supplier')} #1{this.t('tables:frequentTenderers:supplier')} #2{this.t('tables:frequentTenderers:nrITB')}
+ +
+ } +} + +FrequentTenderers.getName = t => t('tables:frequentTenderers:title'); +FrequentTenderers.endpoint = 'frequentTenderers'; + +export default FrequentTenderers; \ No newline at end of file diff --git a/ui/oce/visualizations/tables/tenders.jsx b/ui/oce/visualizations/tables/tenders.jsx index 7cacac68d..c2a54586e 100644 --- a/ui/oce/visualizations/tables/tenders.jsx +++ b/ui/oce/visualizations/tables/tenders.jsx @@ -21,11 +21,11 @@ class Tenders extends Table{ - - - - - + + + + + @@ -36,7 +36,7 @@ class Tenders extends Table{ } } -Tenders.getName = __ => __('Top 10 largest tenders'); +Tenders.getName = t => t('tables:top10tenders:title'); Tenders.endpoint = 'topTenLargestTenders'; export default Tenders; \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index 713d888d7..9d34785a5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -46,14 +46,15 @@ "classnames": "^2.2.3", "css-loader": "^0.17.0", "d3": "^3.5.13", - "exports-loader": "^0.6.2", "eslint": "^3.7.0", "eslint-config-airbnb": "^12.0.0", "eslint-import-resolver-webpack": "^0.6.0", "eslint-plugin-import": "^1.16.0", + "exports-loader": "^0.6.2", "gulp": "^3.9.0", "gulp-npm-files": "^0.1.2", "gulp-util": "^3.0.7", + "immutable": "^3.8.1", "imports-loader": "^0.6.5", "jest-cli": "^13.0.0", "json-loader": "^0.5.4", @@ -64,12 +65,15 @@ "plotly.js": "^1.5.1", "react": "^15.3.1", "react-addons-test-utils": "^15.1.0", + "react-bootstrap": "^0.30.6", "react-dom": "^15.3.1", "react-hot-loader": "^1.3.0", "react-immutable-proptypes": "^1.2.0", "react-input-range": "^0.9.2", "react-leaflet": "^0.10.0", "style-loader": "^0.12.3", + "transit-immutable-js": "^0.6.0", + "transit-js": "^0.8.846", "urijs": "^1.17.1", "webpack": "^1.12.0", "webpack-dev-server": "^1.10.1", diff --git a/ui/pom.xml b/ui/pom.xml index 5643b3bc7..ed5ee69b7 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -83,7 +83,7 @@ com.github.eirslett frontend-maven-plugin - 0.0.29 + 1.3 @@ -93,8 +93,8 @@ generate-resources - v4.2.2 - 2.14.9 + v6.9.1 + 3.10.8 diff --git a/ui/style.less b/ui/style.less index b1da8431d..84dabf5d3 100644 --- a/ui/style.less +++ b/ui/style.less @@ -53,12 +53,26 @@ header.branding{ } } - .language-switcher{ + .header-icons.user-tools{ + font-size: 1em; + .glyphicon { + color: #f0f0f0; + } + a{ + color: white; + text-transform: uppercase; + &, :hover{ + text-decoration: none; + } + } + } + + .header-icons{ cursor: pointer; display: flex; align-items: center; justify-content: center; - .flag { + .icon { box-shadow: 0px 1px 10px 1px #B53636; -webkit-box-shadow: 0px 1px 10px 1px #B53636; -moz-box-shadow: 0px 1px 10px 1px #B53636; @@ -101,15 +115,18 @@ aside { } } -.years-bar { +@yearsBarBottom: 34px; +@yearsBarHeight: 30px; + +.years-bar, .months-bar { position: fixed; - bottom: 34px; + bottom: @yearsBarBottom; border-bottom: 2px solid #c5c5c5; z-index: 1; padding-left: 0; padding-right: 0; display: table; - height: 30px; + height: @yearsBarHeight; a { display: table-cell; background-color: #e6e6e6; @@ -136,6 +153,10 @@ aside { } } +.months-bar{ + bottom: @yearsBarBottom + @yearsBarHeight + 1; +} + footer.main-footer{ position: fixed; height: 34px; @@ -194,7 +215,7 @@ aside [role=navigation] a{ margin-top: 64px; padding-right: 0; overflow: hidden; - &>div{ + .leaflet-container{ height: 95vh; } } @@ -359,4 +380,14 @@ aside [role=navigation] a{ .checkbox{ width: 100%; } +} + +.dashboard-name{ + width: 100px; + display: inline-block; +} + +.dashboard-hint{ + text-transform: none; + margin-top: 10px; } \ No newline at end of file diff --git a/ui/webpack.dev.config.js b/ui/webpack.dev.config.js index 31b056cf5..b3e3aab5e 100644 --- a/ui/webpack.dev.config.js +++ b/ui/webpack.dev.config.js @@ -17,8 +17,7 @@ module.exports = { test: /\.(jsx|es6)$/, loaders: [ 'react-hot', - 'babel-loader?babelrc=false,presets[]=react,presets[]=es2015,cacheDirectory', - 'eslint-loader' + 'babel-loader?babelrc=false,presets[]=react,presets[]=es2015,cacheDirectory' ], exclude: /node_modules/ }, diff --git a/web/pom.xml b/web/pom.xml index 3b943d105..3e08b4a68 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -65,6 +65,12 @@ spring-boot-starter-actuator + + org.springframework.security + spring-security-test + test + + cz.jirutka.validator validator-collection diff --git a/web/src/main/java/org/devgateway/ocds/web/cache/generators/GenericPagingRequestKeyGenerator.java b/web/src/main/java/org/devgateway/ocds/web/cache/generators/GenericPagingRequestKeyGenerator.java index f2595960f..c96026517 100644 --- a/web/src/main/java/org/devgateway/ocds/web/cache/generators/GenericPagingRequestKeyGenerator.java +++ b/web/src/main/java/org/devgateway/ocds/web/cache/generators/GenericPagingRequestKeyGenerator.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * @author mihai {@link KeyGenerator} for {@link RequestMapping}S that use + * @author mpostelnicu {@link KeyGenerator} for {@link RequestMapping}S that use * {@link GenericPagingRequest} This will use the default * Jackson's {@link ObjectMapper} to produce JSON from the input bean * plus takes into account the target ( diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CostEffectivenessVisualsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CostEffectivenessVisualsController.java index ef06d4bcd..69b993c61 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/CostEffectivenessVisualsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/CostEffectivenessVisualsController.java @@ -165,13 +165,15 @@ public List costEffectivenessTenderAmount( Aggregation agg = Aggregation.newAggregation( match(where("tender.status").is(Tender.Status.active.toString()).and("tender.tenderPeriod.startDate") - .exists(true).andOperator(getYearDefaultFilterCriteria(filter, "tender.tenderPeriod.startDate"))), + .exists(true) + .andOperator(getYearDefaultFilterCriteria(filter, "tender.tenderPeriod.startDate"))), getMatchDefaultFilterOperation(filter), unwind("$awards"), new CustomProjectionOperation(project), new CustomGroupingOperation(group1), getTopXFilterOperation(filter, "$year").sum("tenderWithAwardsValue").as(Keys.TOTAL_TENDER_AMOUNT) - .count() - .as(Keys.TOTAL_TENDERS).sum("tenderWithAwards").as(Keys.TOTAL_TENDER_WITH_AWARDS), - new CustomProjectionOperation(project2), sort(Direction.ASC, Fields.UNDERSCORE_ID), + .count().as(Keys.TOTAL_TENDERS).sum("tenderWithAwards").as(Keys.TOTAL_TENDER_WITH_AWARDS), + new CustomProjectionOperation(project2), + filter.getGroupByCategory() != null ? sort(Direction.DESC, Keys.TOTAL_TENDER_AMOUNT) + : sort(Direction.ASC, Fields.UNDERSCORE_ID), skip(filter.getSkip()), limit(filter.getPageSize())); AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java new file mode 100644 index 000000000..c56cbc3ca --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.ocds.web.rest.controller; + +import static org.springframework.data.mongodb.core.query.Criteria.where; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import javax.validation.Valid; + +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.mongodb.core.query.Query; +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 io.swagger.annotations.ApiOperation; + +/** + * + * @author mpostelnicu + * + */ +@RestController +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") +@Cacheable +public class FrequentTenderersController extends GenericOCDSController { + + private class TendererPair { + private String tendererId1; + private String tendererId2; + + public String getTendererId1() { + return tendererId1; + } + + public void setTendererId1(String tenderer1) { + this.tendererId1 = tenderer1; + } + + public String getTendererId2() { + return tendererId2; + } + + public void setTendererId2(String tenderer2) { + this.tendererId2 = tenderer2; + } + + } + + private class ValueObject { + private TendererPair id; + private Integer value; + + public TendererPair getId() { + return id; + } + + public void setId(TendererPair id) { + this.id = id; + } + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + } + + @ApiOperation(value = "Detect frequent pairs of tenderers that apply together to bids." + + "We are only showing pairs if they applied to more than one bid together." + + "We are sorting the results after the number of occurences, descending." + + "You can use all the filters that are available along with pagination options.") + @RequestMapping(value = "/api/frequentTenderers", method = { RequestMethod.POST, RequestMethod.GET }, + produces = "application/json") + public List frequentTenderers(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + return StreamSupport + .stream(mongoTemplate.mapReduce( + new Query(where("tender.tenderers.1").exists(true) + .andOperator(getYearDefaultFilterCriteria(filter, "tender.tenderPeriod.startDate"))), + "release", "classpath:frequent-tenderers-map.js", "classpath:frequent-tenderers-reduce.js", + ValueObject.class).spliterator(), false) + .filter(vo -> vo.getValue() > 1).sorted((p1, p2) -> p2.getValue().compareTo(p1.getValue())) + .skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize()) + .collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java index f53a290b4..fe4f3b1c8 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/GenericOCDSController.java @@ -74,6 +74,11 @@ protected Date getEndDate(final int year) { return end; } + protected String ref(String field) { + return "$" + field; + } + + /** * Appends the procuring bid type id for this filter, this will fitler based * on tender.items.classification._id @@ -85,6 +90,10 @@ protected Criteria getBidTypeIdFilterCriteria(final DefaultFilterPagingRequest f return createFilterCriteria("tender.items.classification._id", filter.getBidTypeId(), filter); } + protected Criteria getNotBidTypeIdFilterCriteria(final DefaultFilterPagingRequest filter) { + return createNotFilterCriteria("tender.items.classification._id", filter.getNotBidTypeId(), filter); + } + /** * Appends the tender.items.deliveryLocation._id @@ -112,15 +121,9 @@ private Criteria getByTenderAmountIntervalCriteria(final DefaultFilterPagingRequ } Criteria criteria = where("tender.value.amount"); if (filter.getMinTenderValue() != null) { - if (filter.getInvert()) { - criteria = criteria.not(); - } criteria = criteria.gte(filter.getMinTenderValue().doubleValue()); } if (filter.getMaxTenderValue() != null) { - if (filter.getInvert()) { - criteria = criteria.not(); - } criteria = criteria.lte(filter.getMaxTenderValue().doubleValue()); } return criteria; @@ -141,15 +144,9 @@ private Criteria getByAwardAmountIntervalCriteria(final DefaultFilterPagingReque } Criteria criteria = where("awards.value.amount"); if (filter.getMinAwardValue() != null) { - if (filter.getInvert()) { - criteria = criteria.not(); - } criteria = criteria.gte(filter.getMinAwardValue().doubleValue()); } if (filter.getMaxAwardValue() != null) { - if (filter.getInvert()) { - criteria = criteria.not(); - } criteria = criteria.lte(filter.getMaxAwardValue().doubleValue()); } return criteria; @@ -160,8 +157,15 @@ private Criteria createFilterCriteria(final String filterName, final List if (filterValues == null) { return new Criteria(); } - return filter.getInvert() ? where(filterName).not().in(filterValues.toArray()) - : where(filterName).in(filterValues.toArray()); + return where(filterName).in(filterValues.toArray()); + } + + private Criteria createNotFilterCriteria(final String filterName, final List filterValues, + final DefaultFilterPagingRequest filter) { + if (filterValues == null) { + return new Criteria(); + } + return where(filterName).not().in(filterValues.toArray()); } /** @@ -175,6 +179,11 @@ protected Criteria getProcuringEntityIdCriteria(final DefaultFilterPagingRequest return createFilterCriteria("tender.procuringEntity._id", filter.getProcuringEntityId(), filter); } + protected Criteria getNotProcuringEntityIdCriteria(final DefaultFilterPagingRequest filter) { + return createNotFilterCriteria("tender.procuringEntity._id", filter.getNotProcuringEntityId(), filter); + } + + /** * Appends the supplier entity id for this filter, this will fitler based @@ -215,22 +224,33 @@ protected Criteria getYearFilterCriteria(final YearFilterPagingRequest filter, f } } - return filter.getInvert() ? criteria.norOperator(yearCriteria) : criteria.orOperator(yearCriteria); + return criteria.orOperator(yearCriteria); } protected Criteria getDefaultFilterCriteria(final DefaultFilterPagingRequest filter) { - return new Criteria().andOperator(getBidTypeIdFilterCriteria(filter), getProcuringEntityIdCriteria(filter), + return new Criteria().andOperator( + getBidTypeIdFilterCriteria(filter), + getNotBidTypeIdFilterCriteria(filter), + getProcuringEntityIdCriteria(filter), + getNotProcuringEntityIdCriteria(filter), getSupplierIdCriteria(filter), - getByTenderDeliveryLocationIdentifier(filter), getByTenderAmountIntervalCriteria(filter), + getByTenderDeliveryLocationIdentifier(filter), + getByTenderAmountIntervalCriteria(filter), getByAwardAmountIntervalCriteria(filter)); } protected Criteria getYearDefaultFilterCriteria(final YearFilterPagingRequest filter, final String dateProperty) { - return new Criteria().andOperator(getBidTypeIdFilterCriteria(filter), getProcuringEntityIdCriteria(filter), + return new Criteria().andOperator( + getBidTypeIdFilterCriteria(filter), + getNotBidTypeIdFilterCriteria(filter), + getProcuringEntityIdCriteria(filter), + getNotProcuringEntityIdCriteria(filter), getSupplierIdCriteria(filter), - getByTenderDeliveryLocationIdentifier(filter), getByTenderAmountIntervalCriteria(filter), - getByAwardAmountIntervalCriteria(filter), getYearFilterCriteria(filter, dateProperty)); + getByTenderDeliveryLocationIdentifier(filter), + getByTenderAmountIntervalCriteria(filter), + getByAwardAmountIntervalCriteria(filter), + getYearFilterCriteria(filter, dateProperty)); } protected MatchOperation getMatchDefaultFilterOperation(final DefaultFilterPagingRequest filter) { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java index 4dfe216d6..007e02359 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/OcdsController.java @@ -11,11 +11,13 @@ *******************************************************************************/ package org.devgateway.ocds.web.rest.controller; +import com.fasterxml.jackson.annotation.JsonView; import io.swagger.annotations.ApiOperation; import org.devgateway.ocds.persistence.mongo.Publisher; import org.devgateway.ocds.persistence.mongo.Release; import org.devgateway.ocds.persistence.mongo.ReleasePackage; import org.devgateway.ocds.persistence.mongo.repository.ReleaseRepository; +import org.devgateway.ocds.persistence.mongo.spring.json.Views; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -51,6 +53,7 @@ public class OcdsController extends GenericOCDSController { @RequestMapping(value = "/api/ocds/release/budgetProjectId/{projectId:^[a-zA-Z0-9]*$}", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public Release ocdsByProjectId(@PathVariable final String projectId) { Release release = releaseRepository.findByBudgetProjectId(projectId); @@ -61,6 +64,7 @@ public Release ocdsByProjectId(@PathVariable final String projectId) { @RequestMapping(value = "/api/ocds/release/ocid/{ocid}", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public Release ocdsByOcid(@PathVariable final String ocid) { Release release = releaseRepository.findByOcid(ocid); @@ -71,6 +75,7 @@ public Release ocdsByOcid(@PathVariable final String ocid) { + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") @RequestMapping(value = "/api/ocds/package/ocid/{ocid}", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public ReleasePackage ocdsPackageByOcid(@PathVariable final String ocid) { Release release = releaseRepository.findByOcid(ocid); @@ -100,6 +105,7 @@ public ReleasePackage createReleasePackage(final Release release) { @RequestMapping(value = "/api/ocds/package/budgetProjectId/{projectId:^[a-zA-Z0-9]*$}", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public ReleasePackage packagedReleaseByProjectId(@PathVariable final String projectId) { Release release = ocdsByProjectId(projectId); @@ -114,6 +120,7 @@ public ReleasePackage packagedReleaseByProjectId(@PathVariable final String proj @ApiOperation(value = "Resturns all available releases, filtered by the given criteria.") @RequestMapping(value = "/api/ocds/release/all", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { Pageable pageRequest = new PageRequest(releaseRequest.getPageNumber(), releaseRequest.getPageSize(), @@ -130,6 +137,7 @@ public List ocdsReleases(@ModelAttribute @Valid final YearFilterPagingR + "This will contain the OCDS package information (metadata about publisher) plus the release itself.") @RequestMapping(value = "/api/ocds/package/all", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @JsonView(Views.Public.class) public List ocdsPackages(@ModelAttribute @Valid final YearFilterPagingRequest releaseRequest) { List ocdsReleases = ocdsReleases(releaseRequest); List releasePackages = new ArrayList<>(ocdsReleases.size()); diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/TendersAwardsYears.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/TendersAwardsYears.java index 760dfef07..e037abe96 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/TendersAwardsYears.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/TendersAwardsYears.java @@ -28,7 +28,7 @@ import static org.springframework.data.mongodb.core.query.Criteria.where; /** - * @author mihai + * @author mpostelnicu * */ diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestController.java new file mode 100644 index 000000000..3faae1d78 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestController.java @@ -0,0 +1,120 @@ +package org.devgateway.ocds.web.rest.controller; + +import javax.validation.Valid; + +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.ocds.persistence.repository.UserDashboardRepository; +import org.devgateway.toolkit.persistence.dao.Person; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; +import org.springframework.data.rest.webmvc.RepositoryRestController; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceAssembler; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +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.ResponseBody; + +@RepositoryRestController +public class UserDashboardRestController { + + private UserDashboardRepository repository; + + @Autowired + private PersonRepository personRepository; + + @Autowired + private PagedResourcesAssembler resourcesAssembler; + + + @Autowired + public UserDashboardRestController(UserDashboardRepository repo) { + repository = repo; + } + + @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET }, + value = "/userDashboards/search/getDefaultDashboardForCurrentUser") + @PreAuthorize("hasRole('ROLE_PROCURING_ENTITY')") + @ResponseBody + public ResponseEntity + getDefaultDashboardForCurrentUser(PersistentEntityResourceAssembler persistentEntityResourceAssembler) { + UserDashboard dashboard = repository.getDefaultDashboardForPersonId(getCurrentAuthenticatedPerson().getId()); + if (dashboard == null) { + return ResponseEntity.ok().build(); + } + Resource resource = persistentEntityResourceAssembler.toResource(dashboard); + return ResponseEntity.ok(resource); + } + + @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET }, + value = "/userDashboards/getCurrentAuthenticatedUserDetails") + @ResponseBody + public ResponseEntity + getCurrentAuthenticatedUserDetails(PersistentEntityResourceAssembler persistentEntityResourceAssembler) { + + Person currentAuthenticatedPersonToken = getCurrentAuthenticatedPerson(); + Person currentAuthenticatedPerson; + if (currentAuthenticatedPersonToken != null) { + currentAuthenticatedPerson = personRepository.getOne(currentAuthenticatedPersonToken.getId()); + } else { + return ResponseEntity.ok().build(); + } + + return ResponseEntity.ok(currentAuthenticatedPerson); + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET }, + value = "/userDashboards/search/getDashboardsForCurrentUser") + @PreAuthorize("hasRole('ROLE_PROCURING_ENTITY')") + @ResponseBody + public PagedResources> getDashboardsForCurrentUser(Pageable page, + PersistentEntityResourceAssembler persistentEntityResourceAssembler) { + return resourcesAssembler.toResource( + repository.findDashboardsForPersonId(getCurrentAuthenticatedPerson().getId(), page), + (ResourceAssembler) persistentEntityResourceAssembler); + } + + @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET }, + value = "/userDashboards/saveDashboardForCurrentUser") + @PreAuthorize("hasRole('ROLE_PROCURING_ENTITY')") + public ResponseEntity saveDashboardForCurrentUser(@ModelAttribute @Valid UserDashboard userDashboard) { + Person person = personRepository.getOne(getCurrentAuthenticatedPerson().getId()); + userDashboard.getUsers().add(person); + person.getDashboards().add(userDashboard); + repository.save(userDashboard); + return ResponseEntity.ok().build(); + } + + @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET }, value = "/userDashboards/saveDashboard") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public ResponseEntity saveDashboard(@ModelAttribute @Valid UserDashboard userDashboard) { + repository.save(userDashboard); + return ResponseEntity.ok().build(); + } + + private static Person getCurrentAuthenticatedPerson() { + if (SecurityContextHolder.getContext().getAuthentication() == null) { + return null; + } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return null; + } + final Object principal = authentication.getPrincipal(); + if (principal instanceof Person) { + return (Person) principal; + } + return null; + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagController.java new file mode 100644 index 000000000..ee0f4b53e --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagController.java @@ -0,0 +1,17 @@ +package org.devgateway.ocds.web.rest.controller.flags; + +import org.devgateway.ocds.web.rest.controller.GenericOCDSController; + +/** + * Created by mpostelnicu on 12/2/2016. + */ +public abstract class AbstractFlagController extends GenericOCDSController { + + protected abstract String getFlagProperty(); + + + protected String getYearProperty() { + return "tender.tenderPeriod.startDate"; + } + +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java new file mode 100644 index 000000000..ead0e3c42 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagReleaseSearchController.java @@ -0,0 +1,29 @@ +package org.devgateway.ocds.web.rest.controller.flags; + +import com.fasterxml.jackson.annotation.JsonView; +import org.devgateway.ocds.persistence.mongo.FlaggedRelease; +import org.devgateway.ocds.persistence.mongo.spring.json.Views; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.web.bind.annotation.ModelAttribute; + +import javax.validation.Valid; +import java.util.List; + +/** + * Created by mpostelnicu on 12/2/2016. + */ +public abstract class AbstractFlagReleaseSearchController extends AbstractFlagController { + + + @JsonView(Views.Internal.class) + protected List releaseFlagSearch(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + Query query = new Query(Criteria.where(getFlagProperty()).is(true). + andOperator(getYearDefaultFilterCriteria(filter, getYearProperty()))) + .with(new PageRequest(filter.getPageNumber(), filter.getPageSize())); + List results = mongoTemplate.find(query, FlaggedRelease.class); + return results; + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagStatsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagStatsController.java new file mode 100644 index 000000000..46e709863 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/AbstractFlagStatsController.java @@ -0,0 +1,95 @@ +package org.devgateway.ocds.web.rest.controller.flags; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.devgateway.toolkit.persistence.mongo.aggregate.CustomGroupingOperation; +import org.devgateway.toolkit.persistence.mongo.aggregate.CustomProjectionOperation; +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 javax.validation.Valid; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.match; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation; + +/** + * Created by mpostelnicu on 12/2/2016. + */ +public abstract class AbstractFlagStatsController extends AbstractFlagController { + + public static final class GenericKeys { + public static final String TOTAL = "total"; + public static final String TOTAL_TRUE = "totalTrue"; + public static final String TOTAL_FALSE = "totalFalse"; + public static final String TOTAL_PRECOND_MET = "totalPrecondMet"; + public static final String PERCENT_TRUE_PRECOND_MET = "percentTruePrecondMet"; + public static final String PERCENT_FALSE_PRECOND_MET = "percentFalsePrecondMet"; + public static final String PERCENT_PRECOND_MET = "percentPrecondMet"; + } + + + protected DBObject getProjectPrepare(final YearFilterPagingRequest year) { + DBObject projectPrepare = new BasicDBObject(); + projectPrepare.put(getFlagProperty(), 1); + return projectPrepare; + } + + + protected DBObject getGroup(final YearFilterPagingRequest filter) { + DBObject group = new BasicDBObject(); + group.put(Fields.UNDERSCORE_ID, null); + group.put(GenericKeys.TOTAL, new BasicDBObject("$sum", 1)); + group.put(GenericKeys.TOTAL_TRUE, new BasicDBObject("$sum", new BasicDBObject("$cond", + Arrays.asList(new BasicDBObject("$eq", Arrays.asList(ref(getFlagProperty()), true)), 1, 0)))); + group.put(GenericKeys.TOTAL_FALSE, new BasicDBObject("$sum", new BasicDBObject("$cond", + Arrays.asList(new BasicDBObject("$eq", Arrays.asList(ref(getFlagProperty()), false)), 1, 0)))); + group.put(GenericKeys.TOTAL_PRECOND_MET, new BasicDBObject("$sum", new BasicDBObject("$cond", + Arrays.asList(new BasicDBObject("$gt", Arrays.asList(ref(getFlagProperty()), null)), 1, 0)))); + return group; + } + + protected DBObject getProjectPercentage(final YearFilterPagingRequest filter) { + DBObject project2 = new BasicDBObject(); + project2.put(Fields.UNDERSCORE_ID, 0); + project2.put(GenericKeys.TOTAL, 1); + project2.put(GenericKeys.TOTAL_TRUE, 1); + project2.put(GenericKeys.TOTAL_FALSE, 1); + project2.put(GenericKeys.TOTAL_PRECOND_MET, 1); + project2.put(GenericKeys.PERCENT_TRUE_PRECOND_MET, new BasicDBObject("$multiply", + Arrays.asList(new BasicDBObject("$divide", Arrays.asList(ref(GenericKeys.TOTAL_TRUE), + ref(GenericKeys.TOTAL_PRECOND_MET))), 100))); + project2.put(GenericKeys.PERCENT_FALSE_PRECOND_MET, new BasicDBObject("$multiply", + Arrays.asList(new BasicDBObject("$divide", Arrays.asList(ref(GenericKeys.TOTAL_FALSE), + ref(GenericKeys.TOTAL_PRECOND_MET))), 100))); + project2.put(GenericKeys.PERCENT_PRECOND_MET, new BasicDBObject("$multiply", + Arrays.asList(new BasicDBObject("$divide", Arrays.asList(ref(GenericKeys.TOTAL_PRECOND_MET), + ref(GenericKeys.TOTAL))), 100))); + return project2; + } + + protected List flagStats(@ModelAttribute @Valid final YearFilterPagingRequest filter) { + + + DBObject projectPrepare = getProjectPrepare(filter); + + DBObject group = getGroup(filter); + + DBObject projectPercentage = getProjectPercentage(filter); + + Aggregation agg = newAggregation( + match(getYearDefaultFilterCriteria(filter, getYearProperty())), + new CustomProjectionOperation(projectPrepare), + new CustomGroupingOperation(group), + new CustomProjectionOperation(projectPercentage) + ); + + AggregationResults results = mongoTemplate.aggregate(agg, "release", DBObject.class); + List list = results.getMappedResults(); + return list; + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java new file mode 100644 index 000000000..5019ca796 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038ReleaseSearchController.java @@ -0,0 +1,36 @@ +package org.devgateway.ocds.web.rest.controller.flags; + +import io.swagger.annotations.ApiOperation; +import org.devgateway.ocds.persistence.mongo.FlaggedRelease; +import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +/** + * Created by mpostelnicu on 12/2/2016. + */ +@RestController +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") +@Cacheable +public class FlagI038ReleaseSearchController extends AbstractFlagReleaseSearchController { + @Override + protected String getFlagProperty() { + return FlagsConstants.I038_VALUE; + } + + @Override + @ApiOperation(value = "Search releases by flag i038") + @RequestMapping(value = "/api/flags/i038/releases", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + protected List releaseFlagSearch(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.releaseFlagSearch(filter); + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038StatsController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038StatsController.java new file mode 100644 index 000000000..c3548e071 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/flags/FlagI038StatsController.java @@ -0,0 +1,37 @@ +package org.devgateway.ocds.web.rest.controller.flags; + +import com.mongodb.DBObject; +import io.swagger.annotations.ApiOperation; +import org.devgateway.ocds.persistence.mongo.flags.FlagsConstants; +import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.List; + +/** + * Created by mpostelnicu on 12/2/2016. + */ +@RestController +@CacheConfig(keyGenerator = "genericPagingRequestKeyGenerator", cacheNames = "genericPagingRequestJson") +@Cacheable +public class FlagI038StatsController extends AbstractFlagStatsController { + + @Override + protected String getFlagProperty() { + return FlagsConstants.I038_VALUE; + } + + @Override + @ApiOperation(value = "Stats for flag i038") + @RequestMapping(value = "/api/flags/i038/stats", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + protected List flagStats(@ModelAttribute @Valid YearFilterPagingRequest filter) { + return super.flagStats(filter); + } +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java index d85872ea9..45f42edf2 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java @@ -10,7 +10,7 @@ import io.swagger.annotations.ApiModelProperty; /** - * @author mihai Filtering bean applied to all endpoints + * @author mpostelnicu Filtering bean applied to all endpoints */ public class DefaultFilterPagingRequest extends GenericPagingRequest { @@ -18,12 +18,22 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { @ApiModelProperty(value = "This corresponds to the tender.items.classification._id") private List bidTypeId; + @EachPattern(regexp = "^[a-zA-Z0-9]*$") + @ApiModelProperty( + value = "This corresponds the negated bidTypeId filter, matches elements that are NOT in the list of Ids") + private List notBidTypeId; + @EachPattern(regexp = "^[a-zA-Z0-9]*$") @ApiModelProperty(value = "This is the id of the organization/procuring entity. " + "Corresponds to the OCDS Organization.identifier") private List procuringEntityId; - //@EachPattern(regexp = "^[\\p{L}0-9]*$") + @EachPattern(regexp = "^[a-zA-Z0-9]*$") + @ApiModelProperty(value = "This corresponds the negated procuringEntityId filter," + + " matches elements that are NOT in the list of Ids") + private List notProcuringEntityId; + + // @EachPattern(regexp = "^[\\p{L}0-9]*$") @ApiModelProperty(value = "This is the id of the organization/supplier entity. " + "Corresponds to the OCDS Organization.identifier") private List supplierId; @@ -47,20 +57,6 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { + "Use /api/awardValueInterval to get the maximum allowed.") private BigDecimal maxAwardValue; - /** - * This parameter will invert (negate) all existing filtering parameters. So - * A IN B turns into A NOT IN B. A IN B AND AN IN C turns into A NOT IN B - * AND A NOT IN C. So this is NOT exactly a logical *not*, the correct way - * would be !(A && B) = !A || !B. Which is not what we do here, but we - * actually dont use multiple parameters anywhere, so it should not matter - * now - */ - @ApiModelProperty(value = "This parameter will invert (negate) all existing filtering parameters." - + "So A IN B turns into A NOT IN B. A IN B AND AN IN C turns into A NOT IN B" - + " AND A NOT IN C. So this is NOT exactly a logical *not*, the correct way " - + " would be !(A && B) = !A || !B.") - private Boolean invert = false; - public DefaultFilterPagingRequest() { super(); } @@ -81,16 +77,6 @@ public void setProcuringEntityId(final List procuringEntityId) { this.procuringEntityId = procuringEntityId; } - - - public Boolean getInvert() { - return invert; - } - - public void setInvert(final Boolean invert) { - this.invert = invert; - } - public List getTenderLoc() { return tenderLoc; } @@ -139,6 +125,20 @@ public void setSupplierId(final List supplierId) { this.supplierId = supplierId; } + public List getNotBidTypeId() { + return notBidTypeId; + } + + public void setNotBidTypeId(List notBidTypeId) { + this.notBidTypeId = notBidTypeId; + } + + public List getNotProcuringEntityId() { + return notProcuringEntityId; + } + public void setNotProcuringEntityId(List notProcuringEntityId) { + this.notProcuringEntityId = notProcuringEntityId; + } } diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/GenericPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/GenericPagingRequest.java index 785879d46..3b49a91d8 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/GenericPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/GenericPagingRequest.java @@ -10,7 +10,7 @@ import io.swagger.annotations.ApiModelProperty; /** - * @author mihai + * @author mpostelnicu * */ public class GenericPagingRequest { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationIdWrapper.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationIdWrapper.java new file mode 100644 index 000000000..e86a1e6be --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationIdWrapper.java @@ -0,0 +1,22 @@ +package org.devgateway.ocds.web.rest.controller.request; + +import java.util.List; + +import cz.jirutka.validator.collection.constraints.EachPattern; +import io.swagger.annotations.ApiModelProperty; + +public class OrganizationIdWrapper { + + @EachPattern(regexp = "^[a-zA-Z0-9]*$") + @ApiModelProperty(value = "List of organization identifiers") + private List id; + + public List getId() { + return id; + } + + public void setId(List ids) { + this.id = ids; + } + +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationSearchRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationSearchRequest.java index b4811a89b..35ce238cd 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationSearchRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/OrganizationSearchRequest.java @@ -5,7 +5,7 @@ import io.swagger.annotations.ApiModelProperty; /** - * @author mihai + * @author mpostelnicu * */ public class OrganizationSearchRequest extends GenericPagingRequest { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/TextSearchRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/TextSearchRequest.java index bb9dc79e7..92baa6dfe 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/TextSearchRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/TextSearchRequest.java @@ -6,7 +6,7 @@ import javax.validation.constraints.Size; /** - * @author mihai + * @author mpostelnicu * */ public class TextSearchRequest extends GenericPagingRequest { diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BuyerSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BuyerSearchController.java index ce0cc4593..3f92ddf1b 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BuyerSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/BuyerSearchController.java @@ -27,7 +27,7 @@ public class BuyerSearchController extends AbstractOrganizationSearchController method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") @ApiOperation(value = "Finds buyer entity by the given id") public Organization byId(@PathVariable final String id) { - return organizationRepository.findByIdAndTypes(id, Organization.OrganizationType.buyer); + return organizationRepository.findByAllIdsAndType(id, Organization.OrganizationType.buyer); } /** diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/OrganizationSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/OrganizationSearchController.java index 21e12d5f0..2346a17e2 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/OrganizationSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/OrganizationSearchController.java @@ -5,7 +5,9 @@ import javax.validation.Valid; import org.devgateway.ocds.persistence.mongo.Organization; +import org.devgateway.ocds.web.rest.controller.request.OrganizationIdWrapper; import org.devgateway.ocds.web.rest.controller.request.OrganizationSearchRequest; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,6 +36,13 @@ public class OrganizationSearchController extends AbstractOrganizationSearchCont public Organization byId(@PathVariable final String id) { return organizationRepository.findOne(id); } + + @RequestMapping(value = "/api/ocds/organization/ids", + method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") + @ApiOperation(value = "Finds organization entities by the given list of ids, comma separated") + public List byIdCollection(@ModelAttribute @Valid OrganizationIdWrapper orgIdWrapper) { + return organizationRepository.findByIdCollection(orgIdWrapper.getId()); + } @RequestMapping(value = "/api/ocds/organization/all", method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/ProcuringEntitySearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/ProcuringEntitySearchController.java index 007683e99..da2082279 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/ProcuringEntitySearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/ProcuringEntitySearchController.java @@ -27,7 +27,7 @@ public class ProcuringEntitySearchController extends AbstractOrganizationSearchC method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") @ApiOperation(value = "Finds procuringEntities by the given id") public Organization byId(@PathVariable final String id) { - return organizationRepository.findByIdAndTypes(id, Organization.OrganizationType.procuringEntity); + return organizationRepository.findByAllIdsAndType(id, Organization.OrganizationType.procuringEntity); } /** diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/SupplierSearchController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/SupplierSearchController.java index a610cdd89..24492129f 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/SupplierSearchController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/SupplierSearchController.java @@ -26,7 +26,7 @@ public class SupplierSearchController extends AbstractOrganizationSearchControll method = { RequestMethod.POST, RequestMethod.GET }, produces = "application/json") @ApiOperation(value = "Finds supplier by the given id") public Organization byId(@PathVariable final String id) { - return organizationRepository.findByIdAndTypes(id, Organization.OrganizationType.supplier); + return organizationRepository.findByAllIdsAndType(id, Organization.OrganizationType.supplier); } /** diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/TendersAwardsValueIntervals.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/TendersAwardsValueIntervals.java index e24763782..64446bfd0 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/TendersAwardsValueIntervals.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/selector/TendersAwardsValueIntervals.java @@ -30,7 +30,7 @@ import io.swagger.annotations.ApiOperation; /** - * @author mihai + * @author mpostelnicu * */ @RestController diff --git a/web/src/main/java/org/devgateway/toolkit/web/application.properties b/web/src/main/java/org/devgateway/toolkit/web/application.properties index 715f08e3c..0a6805e7a 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/application.properties +++ b/web/src/main/java/org/devgateway/toolkit/web/application.properties @@ -22,4 +22,4 @@ spring.profiles.active=default # Hierarchy is specified as a string. Space separates rules and > symbol has the meaning of 'includes'. # Example: role1 > role2 > role3 role2 > role4 # Here role1 includes role2 role3 and role4 (indirectly). And role2 includes role4. -roleHierarchy=ROLE_ADMIN > ROLE_USER +roleHierarchy=ROLE_ADMIN > ROLE_PROCURING_ENTITY > ROLE_USER diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/AsyncControllerLookupService.java b/web/src/main/java/org/devgateway/toolkit/web/spring/AsyncControllerLookupService.java index 3fac74ae8..138ecd339 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/AsyncControllerLookupService.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/AsyncControllerLookupService.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Service; /** - * @author mihai Service designed to run a + * @author mpostelnicu Service designed to run a * {@link AsyncBeanParamControllerMethodCallable} using a {@link Object} * as its parameter. and return the results in an {@link Async} way. */ diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/CustomRestMvcConfiguration.java b/web/src/main/java/org/devgateway/toolkit/web/spring/CustomRestMvcConfiguration.java new file mode 100644 index 000000000..1da9c5642 --- /dev/null +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/CustomRestMvcConfiguration.java @@ -0,0 +1,32 @@ +package org.devgateway.toolkit.web.spring; + +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; +import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; +import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter; + +/** + * We only allow to expose repositories that are annotated + * + * @author mpostelnicu + * http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_which_repositories_get_exposed_by_defaults + */ +@Configuration +public class CustomRestMvcConfiguration { + + @Bean + public RepositoryRestConfigurer repositoryRestConfigurer() { + + return new RepositoryRestConfigurerAdapter() { + + @Override + public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config) { + config.setRepositoryDetectionStrategy(RepositoryDetectionStrategies.ANNOTATED); + config.exposeIdsFor(UserDashboard.class); + } + }; + } +} \ No newline at end of file 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 ba737085b..fd48cbd70 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/MvcConfig.java @@ -11,9 +11,9 @@ *******************************************************************************/ package org.devgateway.toolkit.web.spring; -import java.text.SimpleDateFormat; -import java.util.TimeZone; - +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.apache.commons.io.FileCleaningTracker; import org.bson.types.ObjectId; import org.devgateway.ocds.web.cache.generators.GenericExcelChartKeyGenerator; @@ -27,9 +27,8 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import java.text.SimpleDateFormat; +import java.util.TimeZone; @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @@ -49,6 +48,7 @@ public Jackson2ObjectMapperBuilder objectMapperBuilder() { builder.serializationInclusion(Include.NON_EMPTY).dateFormat(dateFormatGmt); builder.serializerByType(GeoJsonPoint.class, new GeoJsonPointSerializer()); builder.serializerByType(ObjectId.class, new ToStringSerializer()); + builder.defaultViewInclusion(true); return builder; } 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 b803caa15..0a5130227 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 @@ -22,6 +22,7 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -43,6 +44,7 @@ @Order(2) // this loads the security config after the forms security (if you use // them overlayed, it must pick that one first) @EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) @PropertySource("classpath:allowedApiEndpoints.properties") public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @@ -75,11 +77,14 @@ public void configure(final WebSecurity web) throws Exception { } + @Override protected void configure(final HttpSecurity http) throws Exception { http.authorizeRequests() .expressionHandler(webExpressionHandler()) // inject role hierarchy - .anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and() + .anyRequest().authenticated().and().formLogin(). + loginPage("/login"). + permitAll().and().requestCache().and() .logout().permitAll().and().sessionManagement().and().csrf().disable(); http.addFilter(securityContextPersistenceFilter()); } diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/util/AsyncBeanParamControllerMethodCallable.java b/web/src/main/java/org/devgateway/toolkit/web/spring/util/AsyncBeanParamControllerMethodCallable.java index 54a5d3107..fcc7adbfe 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/util/AsyncBeanParamControllerMethodCallable.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/util/AsyncBeanParamControllerMethodCallable.java @@ -4,7 +4,7 @@ /** * @see AsyncControllerLookupService - * @author mihai + * @author mpostelnicu * * @param * @param diff --git a/web/src/main/resources/allowedApiEndpoints.properties b/web/src/main/resources/allowedApiEndpoints.properties index c9e03ae8d..2ab0cc540 100644 --- a/web/src/main/resources/allowedApiEndpoints.properties +++ b/web/src/main/resources/allowedApiEndpoints.properties @@ -56,4 +56,7 @@ allowedApiEndpoints=/api/tenderPriceByProcurementMethod**,\ /api/numberOfTendersByItemClassification**,\ /api/totalCancelledTendersByYearByRationale**,\ /api/costEffectivenessTenderAwardAmount**,\ -/api/tendersAwardsYears** \ No newline at end of file +/api/tendersAwardsYears**,\ +/rest/userDashboards/getCurrentAuthenticatedUserDetails,\ +/api/frequentTenderers**,\ +/api/ocds/organization/ids** \ No newline at end of file diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java index 40d238f1a..db37c3381 100644 --- a/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/OrganizationEndpointsTest.java @@ -62,6 +62,7 @@ public void importTestData() throws IOException, InterruptedException { organization.setContactPoint(contactPoint); final Identifier identifier = new Identifier(); + identifier.setId(ORG_ID); organization.getAdditionalIdentifiers().add(identifier); organization.getTypes().add(Organization.OrganizationType.procuringEntity); organization.getTypes().add(Organization.OrganizationType.buyer); diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java new file mode 100644 index 000000000..8d41da4b1 --- /dev/null +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/ReleaseFlaggingServiceTest.java @@ -0,0 +1,45 @@ +/** + * + */ +package org.devgateway.ocds.web.rest.controller; + +import org.devgateway.ocds.persistence.mongo.FlaggedRelease; +import org.devgateway.ocds.persistence.mongo.repository.FlaggedReleaseRepository; +import org.devgateway.ocds.persistence.mongo.spring.ReleaseFlaggingService; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author mpostelnicu + * + */ +public class ReleaseFlaggingServiceTest extends AbstractEndPointControllerTest { + + @Autowired + private ReleaseFlaggingService releaseFlaggingService; + + + @Autowired + private FlaggedReleaseRepository flaggedReleaseRepository;; + + + public static void logMessage(String message) { + logger.info(message); + } + + @Test + public void testProcessAndSaveFlagsForAllReleases() { + releaseFlaggingService.processAndSaveFlagsForAllReleases(ReleaseFlaggingServiceTest::logMessage); + + FlaggedRelease release1 = flaggedReleaseRepository.findByOcid("ocds-endpoint-001"); + Assert.assertNotNull(release1); + Assert.assertEquals(false, release1.getFlags().getI038().getValue()); + + FlaggedRelease release2 = flaggedReleaseRepository.findByOcid("ocds-endpoint-002"); + Assert.assertNotNull(release2); + Assert.assertEquals(null, release2.getFlags().getI038().getValue()); + + } + +} diff --git a/web/src/test/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestControllerTest.java b/web/src/test/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestControllerTest.java new file mode 100644 index 000000000..4433ae0a9 --- /dev/null +++ b/web/src/test/java/org/devgateway/ocds/web/rest/controller/UserDashboardRestControllerTest.java @@ -0,0 +1,48 @@ +package org.devgateway.ocds.web.rest.controller; + +import org.devgateway.ocds.persistence.dao.UserDashboard; +import org.devgateway.toolkit.web.AbstractSpringDataRestControllerTest; +import org.devgateway.toolkit.web.TestUserDetailsConfiguration; +import org.devgateway.toolkit.web.spring.CustomRestMvcConfiguration; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = { CustomRestMvcConfiguration.class, TestUserDetailsConfiguration.class }) +public class UserDashboardRestControllerTest extends AbstractSpringDataRestControllerTest { + + @Autowired + private UserDashboardRestController userDashboardRestController; + + @Test + @WithUserDetails(value = "admin", userDetailsServiceBeanName = "testUserDetailsAdminProcuringEntity") + public void saveDashboardForCurrentUser() { + + UserDashboard ud = new UserDashboard(); + ud.setName("some name"); + ud.setFormUrlEncodedBody("some body"); + ResponseEntity responseEntity = userDashboardRestController.saveDashboardForCurrentUser(ud); + Assert.assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @WithUserDetails(value = "admin", userDetailsServiceBeanName = "testUserDetailsAdminProcuringEntity") + public void getDefaultDashboardForCurrentUserTest() { + ResponseEntity responseEntity = + userDashboardRestController.getDefaultDashboardForCurrentUser(persistentEntityResourceAssembler); + Assert.assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @WithUserDetails(value = "admin", userDetailsServiceBeanName = "testUserDetailsAdminProcuringEntity") + public void getDefaultDashboardsForCurrentUserTest() { + PagedResources> responseEntity = + userDashboardRestController.getDashboardsForCurrentUser(pageRequest, persistentEntityResourceAssembler); + } +} diff --git a/web/src/test/java/org/devgateway/toolkit/web/AbstractSpringDataRestControllerTest.java b/web/src/test/java/org/devgateway/toolkit/web/AbstractSpringDataRestControllerTest.java new file mode 100644 index 000000000..f63bb4d4b --- /dev/null +++ b/web/src/test/java/org/devgateway/toolkit/web/AbstractSpringDataRestControllerTest.java @@ -0,0 +1,69 @@ +/** + * + */ +package org.devgateway.toolkit.web; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * @author mpostelnicuThis helps us test Spring Data Rest + * repositories with our infrastructure. + */ +@Transactional +public abstract class AbstractSpringDataRestControllerTest extends AbstractWebTest { + + protected PersistentEntityResourceAssembler persistentEntityResourceAssembler; + + protected PageRequest pageRequest; + + @Autowired + protected WebApplicationContext context; + + protected MockMvc mockMvc; + + /** + * http://stackoverflow.com/a/36960968 + * This is needed if you do + * {@link PagedResourcesAssembler#toResource(org.springframework.data.domain.Page)} + * in your controller + */ + private void mockHttpServletRequestForResouceAssemblerSupport() { + String localHost = "http://localhost"; + HttpServletRequest httpServletRequestMock = mock(HttpServletRequest.class); + when(httpServletRequestMock.getRequestURL()).thenReturn(new StringBuffer(localHost)); + when(httpServletRequestMock.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + when(httpServletRequestMock.getRequestURI()).thenReturn(localHost); + when(httpServletRequestMock.getContextPath()).thenReturn(StringUtils.EMPTY); + when(httpServletRequestMock.getServletPath()).thenReturn(StringUtils.EMPTY); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(httpServletRequestMock); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + } + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + this.persistentEntityResourceAssembler = mock(PersistentEntityResourceAssembler.class); + this.pageRequest = mock(PageRequest.class); + + mockHttpServletRequestForResouceAssemblerSupport(); + } + +} diff --git a/web/src/test/java/org/devgateway/toolkit/web/AsyncControllerLookupServiceTest.java b/web/src/test/java/org/devgateway/toolkit/web/AsyncControllerLookupServiceTest.java index ec67fe2e0..65ef8399c 100644 --- a/web/src/test/java/org/devgateway/toolkit/web/AsyncControllerLookupServiceTest.java +++ b/web/src/test/java/org/devgateway/toolkit/web/AsyncControllerLookupServiceTest.java @@ -15,7 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * @author mihai + * @author mpostelnicu * */ public class AsyncControllerLookupServiceTest extends AbstractWebTest { diff --git a/web/src/test/java/org/devgateway/toolkit/web/TestUserDetailsConfiguration.java b/web/src/test/java/org/devgateway/toolkit/web/TestUserDetailsConfiguration.java new file mode 100644 index 000000000..42439e2d2 --- /dev/null +++ b/web/src/test/java/org/devgateway/toolkit/web/TestUserDetailsConfiguration.java @@ -0,0 +1,47 @@ +package org.devgateway.toolkit.web; + +import java.util.Arrays; + +import org.devgateway.toolkit.persistence.dao.Person; +import org.devgateway.toolkit.persistence.repository.PersonRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * Stub {@link UserDetailsService} used for testing purposes. Not to be used on + * anything else! + * + * @author mpostelnicu + * + */ +@Profile("integration") +@Configuration +public class TestUserDetailsConfiguration { + + @Autowired + private PersonRepository personRepository; + + @Bean("testUserDetailsAdminProcuringEntity") + public UserDetailsService testUserDetailsAdminProcuringEntity() { + + return new UserDetailsService() { + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Person person = new Person(); + person.setUsername(username); + person.setPassword("idontcare"); + person.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("ROLE_PROCURING_ENTITY"), + new SimpleGrantedAuthority("ROLE_ADMIN"))); + return personRepository.save(person); + } + }; + } + +} \ No newline at end of file diff --git a/web/src/test/resources/test.properties b/web/src/test/resources/test.properties index b37905f98..07e7e002c 100644 --- a/web/src/test/resources/test.properties +++ b/web/src/test/resources/test.properties @@ -1,5 +1,5 @@ spring.data.mongodb.port=27018 -spring.mongodb.embedded.version=3.2.9 +spring.mongodb.embedded.version=3.2.10 # liquibase properties liquibase.enabled=false
{this.__("Number")}{this.__("Start date")}{this.__("End date")}{this.__("Procuring entity")}{this.__("Estimated value")}{this.t('tables:top10tenders:number')}{this.t('tables:top10tenders:startDate')}{this.t('tables:top10tenders:endDate')}{this.t('tables:top10tenders:procuringEntity')}{this.t('tables:top10tenders:estimatedValue')}