feat: add target filter query view (#2892)

* feat: add target filter query view

* fix: add missing file header, also add query column and details panel in TargetFilterQueryView

---------

Co-authored-by: MICKAEL MAUGER <mickael.mauger@siemens.com>
This commit is contained in:
Mickaël Mauger
2026-02-20 10:33:01 +01:00
committed by GitHub
parent 2b682bf62f
commit 87eefe6e9c
5 changed files with 450 additions and 35 deletions

View File

@@ -45,6 +45,7 @@ import org.eclipse.hawkbit.ui.view.ConfigView;
import org.eclipse.hawkbit.ui.view.DistributionSetView;
import org.eclipse.hawkbit.ui.view.RolloutView;
import org.eclipse.hawkbit.ui.view.SoftwareModuleView;
import org.eclipse.hawkbit.ui.view.TargetFilterQueryView;
import org.eclipse.hawkbit.ui.view.TargetView;
/**
@@ -117,6 +118,9 @@ public final class MainLayout extends AppLayout {
if (accessChecker.hasAccess(TargetView.class)) {
nav.addItem(new SideNavItem("Targets", TargetView.class, VaadinIcon.FILTER.create()));
}
if (accessChecker.hasAccess(TargetFilterQueryView.class)) {
nav.addItem(new SideNavItem("Target Filter Queries", TargetFilterQueryView.class, VaadinIcon.FILTER.create()));
}
if (accessChecker.hasAccess(RolloutView.class)) {
nav.addItem(new SideNavItem("Rollouts", RolloutView.class, VaadinIcon.COGS.create()));
}

View File

@@ -337,7 +337,7 @@ public final class RolloutView extends TableView<MgmtRolloutResponseBody, Long>
description.setMinLength(2);
description.setWidthFull();
actionType = Utils.actionTypeControls(forceTime);
actionType = Utils.actionTypeControls(MgmtActionType.FORCED, forceTime);
startType = new Select<>();
startType.setValue(StartType.MANUAL);

View File

@@ -0,0 +1,405 @@
/**
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.ui.view;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.dom.Style;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import jakarta.annotation.security.RolesAllowed;
import lombok.Getter;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtDistributionSetAutoAssignment;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.ui.HawkbitMgmtClient;
import org.eclipse.hawkbit.ui.MainLayout;
import org.eclipse.hawkbit.ui.view.util.Filter;
import org.eclipse.hawkbit.ui.view.util.SelectionGrid;
import org.eclipse.hawkbit.ui.view.util.TableView;
import org.eclipse.hawkbit.ui.view.util.Utils;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@PageTitle("Target Filter Queries")
@Route(value = "target_filter_queries", layout = MainLayout.class)
@RolesAllowed({ "TARGET_READ" })
@Uses(Icon.class)
public class TargetFilterQueryView extends TableView<TargetFilterQueryView.TargetFilterQueryGridItem, Long> {
public TargetFilterQueryView(final HawkbitMgmtClient hawkbitClient) {
super(
new TargetFilterQueryFilter(),
null,
new SelectionGrid.EntityRepresentation<>(TargetFilterQueryGridItem.class, TargetFilterQueryGridItem::getId) {
@Override
protected void addColumns(final Grid<TargetFilterQueryGridItem> grid) {
grid.addColumn(MgmtTargetFilterQuery::getId).setHeader(Constants.ID).setAutoWidth(true).setKey("id").setSortable(true);
grid.addColumn(MgmtTargetFilterQuery::getName).setHeader(Constants.NAME).setAutoWidth(true).setKey("name").setSortable(true).setResizable(true);
grid.addColumn(new ComponentRenderer<>(QueryCell::new)).setHeader("Query").setAutoWidth(true).setKey("query").setResizable(true);
grid.addColumn(Utils.localDateTimeRenderer(MgmtTargetFilterQuery::getLastModifiedAt)).setHeader(Constants.LAST_MODIFIED_AT).setKey("lastModifiedAt")
.setSortable(true).setAutoWidth(true).setResizable(true);
grid.addColumn(new ComponentRenderer<>(DistributionSetCell::new)).setHeader(Constants.DISTRIBUTION_SET).setAutoWidth(true).setResizable(true);
grid.addComponentColumn(rollout -> new Actions(rollout, grid, hawkbitClient)).setHeader(
Constants.ACTIONS).setAutoWidth(true);
}
},
(query, filter) -> Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi()
.getFilters(filter, query.getOffset(), query.getPageSize(), Utils.getSortParam(query.getSortOrders(), Constants.NAME_ASC), "compact")
.getBody())
.stream()
.map(PagedList::getContent)
.flatMap(List::stream)
.map(m -> TargetFilterQueryGridItem.from(hawkbitClient, m)),
null,
selectionGrid -> {
selectionGrid.getSelectedItems()
.forEach(toDelete -> hawkbitClient.getTargetFilterQueryRestApi().deleteFilter(toDelete.getId()));
return CompletableFuture.completedFuture(null);
},
filterQuery -> {
final TargetFilterQueryDetailedView detailedView = new TargetFilterQueryDetailedView();
detailedView.setItem(filterQuery);
return detailedView;
}
);
}
private static class TargetFilterQueryFilter implements Filter.Rsql {
private final TextField name = Utils.textField(Constants.NAME);
private TargetFilterQueryFilter() {
name.setPlaceholder("<name filter>");
}
@Override
public List<Component> components() {
return List.of(name);
}
@Override
public String filter() {
return Filter.filter(
Map.of(
"name", name.getOptionalValue().map(s -> "*" + s + "*")
));
}
}
private static class QueryCell extends Div {
private QueryCell(final TargetFilterQueryGridItem filterQuery) {
String query = filterQuery.getQuery();
if (query != null) {
setText(query);
setTitle(query);
}
getStyle().setOverflow(Style.Overflow.HIDDEN);
getStyle().set("text-overflow", "ellipsis");
setWhiteSpace(WhiteSpace.NOWRAP);
setMaxWidth(400, Unit.PIXELS);
}
}
private static class DistributionSetCell extends HorizontalLayout {
private DistributionSetCell(final TargetFilterQueryGridItem filterQuery) {
filterQuery.getDs().ifPresent(ds -> {
Icon icon = getActionTypeIcon(filterQuery.getAutoAssignActionType());
icon.getStyle().setFlexShrink("0");
Span dsName = new Span(ds.getName() + ":" + ds.getVersion());
dsName.getStyle().setOverflow(Style.Overflow.HIDDEN);
dsName.getStyle().set("text-overflow", "ellipsis");
dsName.getStyle().setWhiteSpace(Style.WhiteSpace.NOWRAP);
add(icon, dsName);
});
setAlignItems(Alignment.CENTER);
setSpacing(true);
getStyle().setFlexWrap(Style.FlexWrap.NOWRAP);
}
private Icon getActionTypeIcon(MgmtActionType actionType) {
Icon icon = switch (actionType) {
case FORCED -> VaadinIcon.BOLT.create();
case SOFT -> VaadinIcon.USER_CHECK.create();
case DOWNLOAD_ONLY -> VaadinIcon.DOWNLOAD.create();
default -> VaadinIcon.QUESTION_CIRCLE.create();
};
icon.addClassNames(LumoUtility.IconSize.SMALL);
return Utils.tooltip(icon, actionType.getName());
}
}
private static class Actions extends HorizontalLayout {
private final Grid<TargetFilterQueryGridItem> grid;
private final transient HawkbitMgmtClient hawkbitClient;
private Actions(final MgmtTargetFilterQuery filter, final Grid<TargetFilterQueryGridItem> grid,
final HawkbitMgmtClient hawkbitClient) {
this.grid = grid;
this.hawkbitClient = hawkbitClient;
init(filter);
}
private void init(final MgmtTargetFilterQuery filter) {
if (filter.getAutoAssignDistributionSet() == null) {
Button autoAssignButton = new Button(VaadinIcon.LINK.create());
autoAssignButton.addClickListener(e ->
new AutoAssignDialog(filter.getId(), hawkbitClient, () -> refresh(filter.getId())).open()
);
add(Utils.tooltip(autoAssignButton, "Auto assign"));
} else {
Button unassignButton = new Button(VaadinIcon.UNLINK.create());
unassignButton.addClickListener(e -> {
ConfirmDialog dialog = Utils.confirmDialog("Unassign Distribution Set",
"Are you sure you want to unassign the distribution set of target filter query '" + filter.getName() + "'?",
"Unassign",
() -> {
hawkbitClient.getTargetFilterQueryRestApi().deleteAssignedDistributionSet(filter.getId());
refresh(filter.getId());
});
dialog.open();
});
add(Utils.tooltip(unassignButton, "Unassign"));
}
Button deleteButton = new Button(VaadinIcon.TRASH.create());
deleteButton.addClickListener(e -> {
ConfirmDialog dialog = Utils.confirmDialog("Delete Target Filter Query",
"Are you sure you want to delete the target filter query '" + filter.getName() + "'?",
"Delete",
() -> {
hawkbitClient.getTargetFilterQueryRestApi().deleteFilter(filter.getId());
grid.getDataProvider().refreshAll();
});
dialog.open();
});
add(Utils.tooltip(deleteButton, "Delete"));
}
private void refresh(Long filterId) {
removeAll();
final MgmtTargetFilterQuery body = hawkbitClient.getTargetFilterQueryRestApi().getFilter(filterId).getBody();
if (body != null) {
grid.getDataProvider().refreshItem(TargetFilterQueryGridItem.from(hawkbitClient, body));
init(body);
}
}
}
private static class AutoAssignDialog extends Utils.BaseDialog<Void> {
private final Long filterId;
private final Select<MgmtActionType> actionType;
private final ComboBox<MgmtDistributionSet> distributionSet;
private final Button assign = new Button("Assign");
private AutoAssignDialog(final Long filterId, final HawkbitMgmtClient hawkbitClient, Runnable onSuccess) {
super("Select auto assignment distribution set");
this.filterId = filterId;
Paragraph description = new Paragraph("When an auto assign distribution set is selected, " +
"it will be automatically assigned to all targets that match the target filter.");
actionType = Utils.actionTypeControls(new MgmtActionType[]{MgmtActionType.SOFT, MgmtActionType.FORCED, MgmtActionType.DOWNLOAD_ONLY}, MgmtActionType.FORCED, null);
distributionSet = Utils.nameComboBox("Distribution Set", this::readyToAssign, query -> Optional.ofNullable(
hawkbitClient.getDistributionSetRestApi()
.getDistributionSets(
query.getFilter().orElse(null),
query.getOffset(),
query.getLimit(),
Constants.NAME_ASC)
.getBody()).stream().flatMap(body -> body.getContent().stream()));
distributionSet.setItemLabelGenerator(ds -> ds.getName() + ":" + ds.getVersion());
distributionSet.focus();
distributionSet.setRequiredIndicatorVisible(true);
distributionSet.setWidthFull();
assign.setEnabled(false);
assign.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addAssignClickListener(hawkbitClient, onSuccess);
final Button cancel = Utils.tooltip(new Button(CANCEL), CANCEL_ESC);
cancel.addClickListener(e -> close());
cancel.addClickShortcut(Key.ESCAPE);
getFooter().add(cancel);
getFooter().add(assign);
final VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
layout.setSpacing(false);
layout.add(description, actionType, distributionSet);
add(layout);
open();
}
private void readyToAssign(final Object v) {
final boolean createEnabled = !distributionSet.isEmpty();
if (assign.isEnabled() != createEnabled) {
assign.setEnabled(createEnabled);
}
}
private void addAssignClickListener(final HawkbitMgmtClient hawkbitClient, Runnable onSuccess) {
assign.addClickListener(e -> {
MgmtDistributionSetAutoAssignment newAssignment = new MgmtDistributionSetAutoAssignment();
newAssignment.setId(distributionSet.getValue().getId());
newAssignment.setType(actionType.getValue());
hawkbitClient.getTargetFilterQueryRestApi().postAssignedDistributionSet(filterId, newAssignment);
onSuccess.run();
close();
});
}
}
private static class TargetFilterQueryDetailedView extends VerticalLayout {
private final Span filterName;
private final TargetFilterQueryDetails details;
private TargetFilterQueryDetailedView() {
filterName = new Span();
details = new TargetFilterQueryDetails();
setWidthFull();
add(filterName);
final TabSheet tabSheet = new TabSheet();
tabSheet.setWidthFull();
tabSheet.add("Details", details);
add(tabSheet);
}
private void setItem(final TargetFilterQueryGridItem filterQuery) {
this.filterName.setText(filterQuery.getName());
this.details.setItem(filterQuery);
}
}
private static class TargetFilterQueryDetails extends FormLayout {
private final TextField name = Utils.textField(Constants.NAME);
private final TextArea query = new TextArea("Query");
private final TextField createdBy = Utils.textField(Constants.CREATED_BY);
private final TextField createdAt = Utils.textField(Constants.CREATED_AT);
private final TextField lastModifiedBy = Utils.textField(Constants.LAST_MODIFIED_BY);
private final TextField lastModifiedAt = Utils.textField(Constants.LAST_MODIFIED_AT);
private final TextField autoAssignDistributionSet = Utils.textField("Auto Assign Distribution Set");
private final TextField autoAssignActionType = Utils.textField("Auto Assign Action Type");
private final TextField autoAssignWeight = Utils.textField("Auto Assign Weight");
private final TextField confirmationRequired = Utils.textField("Confirmation Required");
private TargetFilterQueryDetails() {
query.setMinLength(2);
Stream.of(
name, query,
createdBy, createdAt,
lastModifiedBy, lastModifiedAt,
autoAssignDistributionSet, autoAssignActionType,
autoAssignWeight, confirmationRequired
)
.forEach(field -> {
field.setReadOnly(true);
add(field);
});
setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
setColspan(query, 2);
}
private void setItem(final TargetFilterQueryGridItem filterQuery) {
name.setValue(filterQuery.getName() != null ? filterQuery.getName() : "");
query.setValue(filterQuery.getQuery() != null ? filterQuery.getQuery() : "");
createdBy.setValue(filterQuery.getCreatedBy() != null ? filterQuery.getCreatedBy() : "");
createdAt.setValue(Utils.localDateTimeFromTs(filterQuery.getCreatedAt()));
lastModifiedBy.setValue(filterQuery.getLastModifiedBy() != null ? filterQuery.getLastModifiedBy() : "");
lastModifiedAt.setValue(Utils.localDateTimeFromTs(filterQuery.getLastModifiedAt()));
filterQuery.getDs().ifPresentOrElse(
ds -> autoAssignDistributionSet.setValue(ds.getName() + ":" + ds.getVersion()),
() -> autoAssignDistributionSet.setValue("")
);
autoAssignActionType.setValue(filterQuery.getAutoAssignActionType() != null ?
filterQuery.getAutoAssignActionType().getName() : "");
autoAssignWeight.setValue(filterQuery.getAutoAssignWeight() != null ?
filterQuery.getAutoAssignWeight().toString() : "");
confirmationRequired.setValue(filterQuery.getConfirmationRequired() != null ?
filterQuery.getConfirmationRequired().toString() : "");
}
}
// todo change /targetfilters api to reduce api calls ?
@Getter
public static class TargetFilterQueryGridItem extends MgmtTargetFilterQuery {
TargetFilterQueryGridItem() {
super();
}
private Optional<MgmtDistributionSet> ds;
static ObjectMapper objectMapper = new ObjectMapper();
public static TargetFilterQueryGridItem from(final HawkbitMgmtClient hawkbitClient, MgmtTargetFilterQuery filter) {
TargetFilterQueryGridItem filterGridItem = objectMapper.convertValue(filter, TargetFilterQueryGridItem.class);
if (filterGridItem.getAutoAssignDistributionSet() != null) {
filterGridItem.ds = Optional.ofNullable(
hawkbitClient.getTargetFilterQueryRestApi().getAssignedDistributionSet(filterGridItem.getId()).getBody()
);
} else {
filterGridItem.ds = Optional.empty();
}
return filterGridItem;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TargetFilterQueryGridItem other)) return false;
return Objects.equals(getId(), other.getId());
}
@Override
public int hashCode() {
return Objects.hashCode(getId());
}
}
}

View File

@@ -638,11 +638,10 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
metadataArea.setEmptyStateText("No metadata found");
metadataArea.addColumn(MgmtMetadata::getKey).setHeader(KEY).setAutoWidth(true);
metadataArea.addColumn(MgmtMetadata::getValue).setHeader(VALUE).setAutoWidth(true);
metadataArea.addComponentColumn(metadata -> {
final Button deleteBtn = Utils.tooltip(new Button(VaadinIcon.TRASH.create()), "Delete Metadata");
deleteBtn.addClickListener(e -> confirmDeleteDialog(metadata.getKey()));
return deleteBtn;
}).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
metadataArea.addComponentColumn(metadata -> Utils.deleteButton("Delete metadata", () -> {
hawkbitClient.getTargetRestApi().deleteMetadata(target.getControllerId(), metadata.getKey());
refreshMetadatas();
})).setHeader("Actions").setAutoWidth(true).setFlexGrow(0);
metadataArea.setWidthFull();
add(metadataArea);
@@ -671,24 +670,6 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
.map(PagedList::getContent)
.orElse(Collections.emptyList()));
}
private void confirmDeleteDialog(String key) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Deletion");
dialog.setText("Are you sure you want to delete metadata " + key + "?");
dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());
dialog.setConfirmButtonTheme(ButtonVariant.LUMO_ERROR.getVariantName());
dialog.setConfirmText("Delete");
dialog.addConfirmListener(event -> {
hawkbitClient.getTargetRestApi().deleteMetadata(target.getControllerId(), key);
refreshMetadatas();
dialog.close();
});
dialog.open();
}
}
public static final class TargetActionsHistoryLayout extends VerticalLayout {
@@ -899,7 +880,7 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
distributionSet.setWidthFull();
actionType = Utils.actionTypeControls(forceTime);
actionType = Utils.actionTypeControls(MgmtActionType.FORCED, forceTime);
assign.setEnabled(false);
assign.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

View File

@@ -112,6 +112,15 @@ public class Utils {
return combo;
}
public static Button deleteButton(String tooltipText, Runnable deleteAction) {
final Button button = Utils.tooltip(new Button(VaadinIcon.TRASH.create()), tooltipText);
button.addClickListener(e -> {
ConfirmDialog dialog = Utils.deleteConfirmDialog(deleteAction);
dialog.open();
});
return button;
}
@SuppressWarnings("java:S119") // better readability
public static <T, ID> HorizontalLayout addRemoveControls(
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> addHandler,
@@ -137,7 +146,10 @@ public class Utils {
layout.add(addBtn);
}
if (removeHandler != null) {
final ConfirmDialog dialog = promptForDeleteConfirmation(removeHandler, selectionGrid);
final ConfirmDialog dialog = deleteConfirmDialog(
() -> removeHandler
.apply(selectionGrid)
.thenAccept(v -> selectionGrid.refreshGrid(false)));
final Button removeBtn = tooltip(new Button(VaadinIcon.MINUS.create()), "Remove");
removeBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_CONTRAST);
removeBtn.addClickListener(e -> dialog.open());
@@ -147,19 +159,29 @@ public class Utils {
return layout;
}
private static <T, ID> ConfirmDialog promptForDeleteConfirmation(
final Function<SelectionGrid<T, ID>, CompletionStage<Void>> removeHandler, final SelectionGrid<T, ID> selectionGrid) {
private static ConfirmDialog deleteConfirmDialog(final Runnable removeHandler) {
return confirmDialog("Confirm Deletion",
"Are you sure you want to delete the selected items? This action cannot be undone.",
"Delete",
removeHandler);
}
public static ConfirmDialog confirmDialog(
final String header,
final String text,
final String confirmText,
final Runnable onConfirm) {
final ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm Deletion");
dialog.setText("Are you sure you want to delete the selected items? This action cannot be undone.");
dialog.setHeader(header);
dialog.setText(text);
dialog.setCancelable(true);
dialog.addCancelListener(event -> dialog.close());
dialog.setConfirmButtonTheme(ButtonVariant.LUMO_ERROR.getVariantName());
dialog.setConfirmText("Delete");
dialog.setConfirmText(confirmText);
dialog.addConfirmListener(event -> {
removeHandler.apply(selectionGrid).thenAccept(v -> selectionGrid.refreshGrid(false));
onConfirm.run();
dialog.close();
});
return dialog;
@@ -210,12 +232,15 @@ public class Utils {
return icon;
}
public static Select<MgmtActionType> actionTypeControls(DateTimePicker forceTime) {
public static Select<MgmtActionType> actionTypeControls(MgmtActionType defaultValue, DateTimePicker forceTime) {
return actionTypeControls(MgmtActionType.values(), defaultValue, forceTime);
}
public static Select<MgmtActionType> actionTypeControls(MgmtActionType[] displayedValues, MgmtActionType defaultValue, DateTimePicker forceTime) {
Select<MgmtActionType> actionType = new Select<>();
actionType.setLabel(Constants.ACTION_TYPE);
actionType.setItems(MgmtActionType.values());
actionType.setValue(MgmtActionType.FORCED);
actionType.setItems(displayedValues);
actionType.setValue(defaultValue);
final ComponentRenderer<Component, MgmtActionType> actionTypeRenderer = new ComponentRenderer<>(actionTypeO -> switch (actionTypeO) {
case SOFT -> new Text(Constants.SOFT);
case FORCED -> new Text(Constants.FORCED);