UI deep-linking for targets (#1230)

* added target deeplinking through url param
* populate search irregardless of target existence
* adapted view state paramater evaluation flow
* fixed review comments

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>
This commit is contained in:
Bondar Bogdan
2022-02-22 09:24:30 +01:00
committed by GitHub
parent 949cd8cd8b
commit cc60725c08
7 changed files with 130 additions and 31 deletions

View File

@@ -226,14 +226,19 @@ public abstract class AbstractHawkbitUI extends UI implements DetachListener {
private class ManagementViewProvider implements ViewProvider {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_PARAMETER_SEPARATOR = "/";
@Override
public String getViewName(final String viewAndParameters) {
return viewProvider.getViewName(getStartView(viewAndParameters));
final int paramsDelimeterIndex = viewAndParameters.indexOf(DEFAULT_PARAMETER_SEPARATOR);
final String viewName = paramsDelimeterIndex != -1 ? viewAndParameters.substring(0, paramsDelimeterIndex)
: viewAndParameters;
return viewProvider.getViewName(getStartView(viewName));
}
@Override
public View getView(final String viewName) {
return viewProvider.getView(getStartView(viewName));
return viewProvider.getView(viewName);
}
private String getStartView(final String viewName) {

View File

@@ -8,13 +8,20 @@
*/
package org.eclipse.hawkbit.ui.common;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.util.StringUtils;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewBeforeLeaveEvent;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
@@ -27,6 +34,10 @@ import com.vaadin.ui.VerticalLayout;
public abstract class AbstractEventListenersAwareView extends VerticalLayout implements View, ViewNameAware {
private static final long serialVersionUID = 1L;
// directly taken from Vaadin Navigator for consistency
private static final String DEFAULT_STATE_PARAMETER_SEPARATOR = "&";
private static final String DEFAULT_STATE_PARAMETER_KEY_VALUE_SEPARATOR = "=";
private final transient List<EventListenersAwareLayout> eventAwareLayouts = new ArrayList<>();
private boolean initial;
@@ -71,10 +82,24 @@ public abstract class AbstractEventListenersAwareView extends VerticalLayout imp
if (initial) {
restoreState();
initial = false;
return;
} else {
updateLayoutsOnViewEnter();
}
updateLayoutsOnViewEnter();
if (StringUtils.hasText(event.getParameters())) {
handleStateParams(parseStateParameters(event.getParameters()));
}
}
private static Map<String, String> parseStateParameters(final String urlParams) {
return Arrays.stream(urlParams.split(DEFAULT_STATE_PARAMETER_SEPARATOR)).map(paramPair -> {
final String[] keyValue = paramPair.split(DEFAULT_STATE_PARAMETER_KEY_VALUE_SEPARATOR, 2);
if (keyValue.length == 2) {
return new AbstractMap.SimpleEntry<>(keyValue[0], keyValue[1]);
}
return null;
}).filter(Objects::nonNull)
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
}
/**
@@ -94,8 +119,17 @@ public abstract class AbstractEventListenersAwareView extends VerticalLayout imp
}
/**
* Called on on view enter for added event aware layouts to update their
* state.
* Handles state url parameters of added event aware layouts.
*
* @param stateParams
* map of view state url parameters
*/
protected void handleStateParams(final Map<String, String> stateParams) {
eventAwareLayouts.forEach(layout -> layout.handleStateParameters(stateParams));
}
/**
* Called on view enter for added event aware layouts to update their state.
*
*/
protected void updateLayoutsOnViewEnter() {

View File

@@ -8,6 +8,8 @@
*/
package org.eclipse.hawkbit.ui.common;
import java.util.Map;
/**
* Interface for event aware event listeners
*
@@ -26,6 +28,15 @@ public interface EventListenersAwareLayout {
default void onViewEnter() {
}
/**
* Set components state based on url parameters
*
* @param stateParameters
* map of state url parameters
*/
default void handleStateParameters(final Map<String, String> stateParameters) {
}
/**
* Subscribe event listeners
*/

View File

@@ -54,7 +54,7 @@ public class SearchHeaderSupport implements HeaderSupport {
* @param searchResetIconId
* Value supplier for search box
* @param searchStateSupplier
* Search state supplier
* Search state supplier
* @param searchByCallback
* Callback for search event
*/
@@ -70,8 +70,6 @@ public class SearchHeaderSupport implements HeaderSupport {
this.searchField = createSearchField();
this.searchResetIcon = createSearchResetIcon();
this.isSearchInputActive = false;
}
private TextField createSearchField() {
@@ -108,7 +106,6 @@ public class SearchHeaderSupport implements HeaderSupport {
// Clicked on search icon
openSearchTextField();
}
isSearchInputActive = !isSearchInputActive;
}
private void openSearchTextField() {
@@ -118,6 +115,8 @@ public class SearchHeaderSupport implements HeaderSupport {
searchField.setVisible(true);
searchField.focus();
isSearchInputActive = true;
}
private void closeSearchTextField() {
@@ -129,6 +128,22 @@ public class SearchHeaderSupport implements HeaderSupport {
searchField.setVisible(false);
searchByCallback.accept(null);
isSearchInputActive = false;
}
/**
* Update the search and trigger the callback to refresh the grid.
*
* @param searchQuery
* search query
*/
public void setAndTriggerSearch(final String searchQuery) {
if (!StringUtils.isEmpty(searchQuery)) {
openSearchTextField();
searchField.setValue(searchQuery);
searchByCallback.accept(searchQuery);
}
}
@Override
@@ -138,7 +153,6 @@ public class SearchHeaderSupport implements HeaderSupport {
if (!StringUtils.isEmpty(onLoadSearchBoxValue)) {
openSearchTextField();
searchField.setValue(onLoadSearchBoxValue);
isSearchInputActive = true;
}
}
@@ -162,7 +176,6 @@ public class SearchHeaderSupport implements HeaderSupport {
public void resetSearch() {
if (isSearchInputActive) {
closeSearchTextField();
isSearchInputActive = false;
}
}

View File

@@ -197,12 +197,24 @@ public class TargetGrid extends AbstractGrid<ProxyTarget, TargetManagementFilter
* @param entityId
* Entity id
*
* @return Target
* @return ProxyTarget
*/
public Optional<ProxyTarget> mapIdToProxyEntity(final long entityId) {
return targetManagement.get(entityId).map(targetToProxyTargetMapper::map);
}
/**
* Map target controller id to proxy target entity
*
* @param controllerId
* controller id
*
* @return ProxyTarget
*/
public Optional<ProxyTarget> mapControllerIdToProxyEntity(final String controllerId) {
return targetManagement.getByControllerID(controllerId).map(targetToProxyTargetMapper::map);
}
private Long getSelectedEntityIdFromUiState() {
return targetGridLayoutUiState.getSelectedEntityId();
}
@@ -290,7 +302,7 @@ public class TargetGrid extends AbstractGrid<ProxyTarget, TargetManagementFilter
/**
* Update filter on filter tab selection
*/
public void resetAllFilters(){
public void resetAllFilters() {
getFilter().ifPresent(filter -> {
filter.setDistributionId(null);
filter.setNoTagClicked(false);

View File

@@ -241,14 +241,25 @@ public class TargetGridHeader extends AbstractEntityGridHeader {
}
}
/**
* Update search programmatically.
*
* @param searchQuery
* search query
*/
public void updateSearch(final String searchQuery) {
getSearchHeaderSupport().setAndTriggerSearch(searchQuery);
}
/**
* Enable search icon in the search header
*/
public void enableSearchIcon() {
getSearchHeaderSupport().enableSearch();
}
/**
* Disable search icon in the search header
* Disable search icon in the search header.
*/
public void disabledSearchIcon() {
getSearchHeaderSupport().disableSearch();

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.management.targettable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.hawkbit.repository.DeploymentManagement;
@@ -57,6 +58,8 @@ import org.eclipse.hawkbit.ui.management.targettag.filter.TargetTagFilterLayoutU
public class TargetGridLayout extends AbstractGridComponentLayout {
private static final long serialVersionUID = 1L;
private static final String TARGET_STATE_PARAM = "target";
private final TargetGridHeader targetGridHeader;
private final TargetGrid targetGrid;
private final TargetDetailsHeader targetDetailsHeader;
@@ -107,23 +110,23 @@ public class TargetGridLayout extends AbstractGridComponentLayout {
* DistributionGridLayoutUiState
*/
public TargetGridLayout(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement,
final TargetTypeManagement targetTypeManagement,
final DeploymentManagement deploymentManagement, final UiProperties uiProperties,
final TargetTagManagement targetTagManagement, final DistributionSetManagement distributionSetManagement,
final Executor uiExecutor, final TenantConfigurationManagement configManagement,
final TargetManagementStateDataSupplier targetManagementStateDataSupplier,
final SystemSecurityContext systemSecurityContext,
final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState,
final TargetGridLayoutUiState targetGridLayoutUiState,
final TargetBulkUploadUiState targetBulkUploadUiState,
final DistributionGridLayoutUiState distributionGridLayoutUiState) {
final TargetWindowBuilder targetWindowBuilder = new TargetWindowBuilder(uiDependencies, targetManagement, targetTypeManagement,
EventView.DEPLOYMENT);
final TargetTypeManagement targetTypeManagement, final DeploymentManagement deploymentManagement,
final UiProperties uiProperties, final TargetTagManagement targetTagManagement,
final DistributionSetManagement distributionSetManagement, final Executor uiExecutor,
final TenantConfigurationManagement configManagement,
final TargetManagementStateDataSupplier targetManagementStateDataSupplier,
final SystemSecurityContext systemSecurityContext,
final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState,
final TargetGridLayoutUiState targetGridLayoutUiState,
final TargetBulkUploadUiState targetBulkUploadUiState,
final DistributionGridLayoutUiState distributionGridLayoutUiState) {
final TargetWindowBuilder targetWindowBuilder = new TargetWindowBuilder(uiDependencies, targetManagement,
targetTypeManagement, EventView.DEPLOYMENT);
final TargetMetaDataWindowBuilder targetMetaDataWindowBuilder = new TargetMetaDataWindowBuilder(uiDependencies,
targetManagement);
final BulkUploadWindowBuilder bulkUploadWindowBuilder = new BulkUploadWindowBuilder(uiDependencies,
uiProperties, uiExecutor, targetManagement, deploymentManagement, targetTypeManagement, targetTagManagement,
distributionSetManagement, targetBulkUploadUiState);
uiProperties, uiExecutor, targetManagement, deploymentManagement, targetTypeManagement,
targetTagManagement, distributionSetManagement, targetBulkUploadUiState);
this.targetGridHeader = new TargetGridHeader(uiDependencies, targetWindowBuilder, bulkUploadWindowBuilder,
targetTagFilterLayoutUiState, targetGridLayoutUiState, targetBulkUploadUiState);
@@ -211,10 +214,10 @@ public class TargetGridLayout extends AbstractGridComponentLayout {
if (isCustomFilterTabSelected) {
targetGridHeader.disabledSearchIcon();
}
if(isTargetTypeFilterTabSelected){
if (isTargetTypeFilterTabSelected) {
targetGridHeader.enableSearchIcon();
}
if (isSimpleTypeFilterTabSelected){
if (isSimpleTypeFilterTabSelected) {
targetGridHeader.enableSearchIcon();
}
@@ -246,6 +249,16 @@ public class TargetGridLayout extends AbstractGridComponentLayout {
countMessageLabel.updatePinningDetails();
}
@Override
public void handleStateParameters(final Map<String, String> stateParameters) {
if (stateParameters.containsKey(TARGET_STATE_PARAM)) {
final String stateTargetParam = stateParameters.get(TARGET_STATE_PARAM);
targetGridHeader.updateSearch(stateTargetParam);
targetGrid.mapControllerIdToProxyEntity(stateTargetParam)
.ifPresent(t -> targetGrid.getSelectionSupport().select(t));
}
}
@Override
public void onViewEnter() {
targetGridHeader.checkBulkUpload();