This is an automated email from the ASF dual-hosted git repository.
dominikriemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new 0141c1bfc4 feat(#3968): Assets Do Not Consider Dependencies on Labels
and Sites (#4559)
0141c1bfc4 is described below
commit 0141c1bfc4a5288442af0f320b1f616b25ef726f
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Fri Jun 19 16:02:35 2026 +0200
feat(#3968): Assets Do Not Consider Dependencies on Labels and Sites (#4559)
Co-authored-by: Dominik Riemer <[email protected]>
---
.../export/dataimport/ImportGenerator.java | 25 +++++-
.../export/dataimport/PerformImportGenerator.java | 14 ++++
.../export/dataimport/PreviewImportGenerator.java | 22 ++++-
.../export/generator/ExportPackageGenerator.java | 8 ++
.../model/export/AssetExportConfiguration.java | 28 +++++++
ui/deployment/i18n/de.json | 13 ++-
ui/deployment/i18n/en.json | 7 ++
ui/deployment/i18n/pl.json | 11 ++-
.../src/lib/model/gen/streampipes-model.ts | 4 +
.../export/data-export-import.component.ts | 96 +++++++++++++++++++++-
.../data-export-dialog.component.html | 20 ++++-
.../export-dialog/data-export-dialog.component.ts | 41 ++++++++-
.../data-import-dialog.component.html | 8 ++
.../import-dialog/data-import-dialog.component.ts | 4 +-
.../label-configuration.component.html | 1 -
.../label-configuration.component.ts | 55 ++++++++++++-
.../site-area-configuration.component.html | 1 -
.../site-area-configuration.component.ts | 72 +++++++++++++++-
18 files changed, 411 insertions(+), 19 deletions(-)
diff --git
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
index 71025092e5..d2e582aaef 100644
---
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
+++
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/ImportGenerator.java
@@ -24,6 +24,7 @@ import
org.apache.streampipes.model.export.StreamPipesApplicationPackage;
import org.apache.streampipes.serializers.json.JacksonSerializer;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -39,6 +40,9 @@ import java.util.Map;
public abstract class ImportGenerator<T> {
private static final Logger LOG =
LoggerFactory.getLogger(ImportGenerator.class);
+ private static final String LABELS_APP_DOC_TYPE = "sp-labels";
+ private static final String SITES_APP_DOC_TYPE = "asset-sites";
+ private static final String APP_DOC_TYPE_FIELD = "appDocType";
protected ObjectMapper defaultMapper;
@@ -120,7 +124,7 @@ public abstract class ImportGenerator<T> {
for (String documentId : manifest.getGenericStorageDocuments()) {
try {
- handleGenericStorageDocument(asString(previewFiles.get(documentId)),
documentId);
+
handleGenericStorageDocumentByType(asString(previewFiles.get(documentId)),
documentId);
} catch (DocumentConflictException e) {
LOG.warn("Skipping import of generic storage doc {} (already present
with the same id)", documentId);
}
@@ -158,6 +162,12 @@ public abstract class ImportGenerator<T> {
protected abstract void handleFile(String document, String fileMetadataId,
Map<String, byte[]> zipContent)
throws IOException;
+ protected abstract void handleLabel(String document, String labelId)
+ throws JsonProcessingException;
+
+ protected abstract void handleSite(String document, String siteId)
+ throws JsonProcessingException;
+
protected abstract void handleGenericStorageDocument(String document, String
dataLakeMeasureId)
throws JsonProcessingException;
@@ -165,4 +175,17 @@ public abstract class ImportGenerator<T> {
protected abstract void afterResourcesCreated();
+ private void handleGenericStorageDocumentByType(String document, String
documentId) throws JsonProcessingException {
+ Map<String, Object> genericStorageDocument =
this.defaultMapper.readValue(document, new TypeReference<>() {
+ });
+ var appDocType = genericStorageDocument.get(APP_DOC_TYPE_FIELD);
+
+ if (LABELS_APP_DOC_TYPE.equals(appDocType)) {
+ handleLabel(document, documentId);
+ } else if (SITES_APP_DOC_TYPE.equals(appDocType)) {
+ handleSite(document, documentId);
+ } else {
+ handleGenericStorageDocument(document, documentId);
+ }
+ }
}
diff --git
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
index 0e4532deec..54dcd448cc 100644
---
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
+++
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PerformImportGenerator.java
@@ -145,6 +145,20 @@ public class PerformImportGenerator extends
ImportGenerator<Void> {
new FileHandler().storeFile(fileMetadata.getFilename(), new
ByteArrayInputStream(file));
}
+ @Override
+ protected void handleLabel(String document, String labelId) throws
JsonProcessingException {
+ if (shouldStore(labelId, config.getLabels())) {
+ writeDocument(document, new GenericStorageDocumentResolver());
+ }
+ }
+
+ @Override
+ protected void handleSite(String document, String siteId) throws
JsonProcessingException {
+ if (shouldStore(siteId, config.getSites())) {
+ writeDocument(document, new GenericStorageDocumentResolver());
+ }
+ }
+
@Override
protected void handleGenericStorageDocument(String document, String
documentId) throws JsonProcessingException {
if (shouldStore(documentId, config.getGenericStorageDocuments())) {
diff --git
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
index de8a0c976c..fe818cb59c 100644
---
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
+++
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/dataimport/PreviewImportGenerator.java
@@ -41,6 +41,8 @@ import java.util.function.Consumer;
public class PreviewImportGenerator extends
ImportGenerator<AssetExportConfiguration> {
private static final Logger LOG =
LoggerFactory.getLogger(PreviewImportGenerator.class);
+ private static final String LABEL_FIELD = "label";
+
private final AssetExportConfiguration importConfig;
private final ExtensionServiceRequestManager extensionServiceRequestManager;
@@ -121,7 +123,19 @@ public class PreviewImportGenerator extends
ImportGenerator<AssetExportConfigura
}
@Override
- protected void handleGenericStorageDocument(String document, String
genericDocId) throws JsonProcessingException {
+ protected void handleLabel(String document, String labelId) throws
JsonProcessingException {
+ addExportItem(labelId, getGenericStorageDocumentLabel(document, labelId),
+ importConfig::addLabel);
+ }
+
+ @Override
+ protected void handleSite(String document, String siteId) throws
JsonProcessingException {
+ addExportItem(siteId, getGenericStorageDocumentLabel(document, siteId),
+ importConfig::addSite);
+ }
+
+ @Override
+ protected void handleGenericStorageDocument(String document, String
genericDocId) {
addExportItem(genericDocId, genericDocId,
importConfig::addGenericStorageDocument);
}
@@ -133,4 +147,10 @@ public class PreviewImportGenerator extends
ImportGenerator<AssetExportConfigura
@Override
protected void afterResourcesCreated() {
}
+
+ private String getGenericStorageDocumentLabel(String document, String
genericDocId) throws JsonProcessingException {
+ Map<String, Object> genericStorageDocument =
this.defaultMapper.readValue(document, new TypeReference<>() {
+ });
+ return String.valueOf(genericStorageDocument.getOrDefault(LABEL_FIELD,
genericDocId));
+ }
}
diff --git
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
index 1bc2829977..9637e204d1 100644
---
a/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
+++
b/streampipes-data-export/src/main/java/org/apache/streampipes/export/generator/ExportPackageGenerator.java
@@ -122,6 +122,14 @@ public class ExportPackageGenerator {
addDoc(builder, item, new GenericStorageDocumentResolver(),
manifest::addGenericStorageDocument);
});
+ config.getLabels().forEach(item -> {
+ addDoc(builder, item, new GenericStorageDocumentResolver(),
manifest::addGenericStorageDocument);
+ });
+
+ config.getSites().forEach(item -> {
+ addDoc(builder, item, new GenericStorageDocumentResolver(),
manifest::addGenericStorageDocument);
+ });
+
config.getFiles().forEach(item -> {
if (item.isSelected()) {
var fileResolver = new FileResolver();
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/export/AssetExportConfiguration.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/export/AssetExportConfiguration.java
index 84c6ad3f46..2ac67ab67f 100644
---
a/streampipes-model/src/main/java/org/apache/streampipes/model/export/AssetExportConfiguration.java
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/export/AssetExportConfiguration.java
@@ -34,6 +34,8 @@ public class AssetExportConfiguration {
private Set<ExportItem> dataSources;
private Set<ExportItem> pipelines;
private Set<ExportItem> files;
+ private Set<ExportItem> labels;
+ private Set<ExportItem> sites;
private Set<ExportItem> genericStorageDocuments;
private boolean overrideBrokerSettings;
@@ -48,6 +50,8 @@ public class AssetExportConfiguration {
this.pipelines = new HashSet<>();
this.files = new HashSet<>();
this.assets = new HashSet<>();
+ this.labels = new HashSet<>();
+ this.sites = new HashSet<>();
this.genericStorageDocuments = new HashSet<>();
}
@@ -143,6 +147,30 @@ public class AssetExportConfiguration {
this.files.add(item);
}
+ public Set<ExportItem> getLabels() {
+ return labels;
+ }
+
+ public void setLabels(Set<ExportItem> labels) {
+ this.labels = labels;
+ }
+
+ public void addLabel(ExportItem label) {
+ this.labels.add(label);
+ }
+
+ public Set<ExportItem> getSites() {
+ return sites;
+ }
+
+ public void setSites(Set<ExportItem> sites) {
+ this.sites = sites;
+ }
+
+ public void addSite(ExportItem site) {
+ this.sites.add(site);
+ }
+
public String getAssetName() {
return assetName;
}
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index 684f06474f..dfc76c4696 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -109,7 +109,9 @@
"Are you sure you want to delete this account?": "Sind Sie sicher, dass Sie
dieses Konto löschen möchten?",
"Are you sure you want to delete this asset?": "Möchten Sie dieses Asset
wirklich löschen?",
"Are you sure you want to delete this group?": "Sind Sie sicher, dass Sie
diese Gruppe löschen wollen?",
+ "Are you sure you want to delete this label?": "Möchten Sie dieses Label
wirklich löschen?",
"Are you sure you want to delete this role?": "Sind Sie sicher, dass Sie
diese Rolle löschen wollen?",
+ "Are you sure you want to delete this site?": "Sind Sie sicher, dass Sie
diese Standort löschen wollen?",
"Area": "Bereich",
"Areas": "Bereiche",
"Asset": "Asset",
@@ -583,6 +585,7 @@
"Label Line": "Beschriftungslinie",
"Label Position": "Beschriftungsposition",
"Label font size": "Schriftgröße der Bezeichnung",
+ "Label is still in use": "Das Label wird weiterhin verwendet",
"Label shown below the current gauge value.": "Beschriftung, die unter dem
aktuellen Tacho-Wert angezeigt wird.",
"Labels": "Labels",
"Labels & custom fields": "Label und benutzerdefinierte Felder",
@@ -837,6 +840,7 @@
"Re-enter mail address": "E-Mail-Adresse erneut eingeben",
"Recipient for test mail": "Empfänger für Test-Mail",
"Reduce event rate": "Datenrate reduzieren",
+ "Referenced resources": "Verwendete Ressourcen",
"Refresh": "Neu laden",
"Refresh Fields": "Felder aktualisieren",
"Refresh adapters": "Adapter neu laden",
@@ -881,7 +885,7 @@
"Restore password": "Passwort wiederherstellen",
"Restrict to service tags": "Auf Service-Tags beschränken",
"Result": "Ergebnis",
- "Result Labels": "Ergebnisbeschriftungen",
+ "Result Labels": "Ergebnisbezeichnungen",
"Retention": "Aufbewahrung",
"Retention Log": "Speicherprotokoll",
"Right": "Rechts",
@@ -1004,6 +1008,7 @@
"Simple filters below are kept for fallback, but the advanced filter is
currently used for queries.": "Die einfachen Filter unten bleiben als
Rückfalloption erhalten, aber derzeit wird der erweiterte Filter für Abfragen
verwendet.",
"Single": "Einzeln",
"Site": "Standort",
+ "Site is still in use": "Der Standort wird genutzt",
"Site name is required": "Standortname erforderlich",
"Sites": "Standorte",
"Sites & Areas": "Standorte & Bereiche",
@@ -1136,8 +1141,10 @@
"Title": "Titel",
"Title must not be empty": "Titel darf nicht leer sein!",
"To": "Bis",
- "To delete a label, please remove the label from all assets": "Um ein Label
zu löschen, entfernen Sie das Label bitte von allen Assets",
- "To delete a site, please remove the site from all assets": "Um einen
Standort zu löschen, entfernen Sie den Standort bitte von allen Assets",
+ "To delete a label, please remove the label from all assets": "Um ein Label
zu löschen, entfernen Sie es bitte aus allen Assets",
+ "To delete a label, please remove the label from all assets.": "Um ein Label
zu löschen, entfernen Sie es bitte aus allen Assets.",
+ "To delete a site, please remove the site from all assets": "Um einen
Standort zu löschen, entfernen Sie diese bitte aus allen Assets",
+ "To delete a site, please remove the site from all assets.": "Um einen
Standort zu löschen, entfernen Sie den Standort bitte von allen Assets.",
"To enable the map view, a map provider needs to be configured. Admins can
configure map providers under Settings -> Sites.": "Um die Kartenansicht zu
aktivieren, ist ein konfigurierter Kartenanbieter erforderlich. Administratoren
können Kartenanbieter unter Einstellungen → Standorte verwalten.",
"Tooltip": "Tooltip",
"Tooltip Content": "Tooltip-Inhalt",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index e31a855d66..8cc64c0bb6 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -109,7 +109,9 @@
"Are you sure you want to delete this account?": null,
"Are you sure you want to delete this asset?": null,
"Are you sure you want to delete this group?": null,
+ "Are you sure you want to delete this label?": null,
"Are you sure you want to delete this role?": null,
+ "Are you sure you want to delete this site?": null,
"Area": null,
"Areas": null,
"Asset": null,
@@ -583,6 +585,7 @@
"Label Line": null,
"Label Position": null,
"Label font size": null,
+ "Label is still in use": null,
"Label shown below the current gauge value.": null,
"Labels": null,
"Labels & custom fields": null,
@@ -837,6 +840,7 @@
"Re-enter mail address": null,
"Recipient for test mail": null,
"Reduce event rate": null,
+ "Referenced resources": null,
"Refresh": null,
"Refresh Fields": null,
"Refresh adapters": null,
@@ -1004,6 +1008,7 @@
"Simple filters below are kept for fallback, but the advanced filter is
currently used for queries.": null,
"Single": null,
"Site": null,
+ "Site is still in use": null,
"Site name is required": null,
"Sites": null,
"Sites & Areas": null,
@@ -1137,7 +1142,9 @@
"Title must not be empty": null,
"To": null,
"To delete a label, please remove the label from all assets": null,
+ "To delete a label, please remove the label from all assets.": null,
"To delete a site, please remove the site from all assets": null,
+ "To delete a site, please remove the site from all assets.": null,
"To enable the map view, a map provider needs to be configured. Admins can
configure map providers under Settings -> Sites.": null,
"Tooltip": null,
"Tooltip Content": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index e86a27129f..eba340d80e 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -109,7 +109,9 @@
"Are you sure you want to delete this account?": "Czy na pewno chcesz usunąć
to konto?",
"Are you sure you want to delete this asset?": "Czy na pewno chcesz usunąć
ten zasób?",
"Are you sure you want to delete this group?": "Czy na pewno chcesz usunąć
tę grupę?",
+ "Are you sure you want to delete this label?": "Czy na pewno chcesz usunąć
tę etykietę?",
"Are you sure you want to delete this role?": "Czy na pewno chcesz usunąć tę
rolę?",
+ "Are you sure you want to delete this site?": "Czy na pewno chcesz usunąć tę
stronę?",
"Area": "Obszar",
"Areas": "Obszary",
"Asset": "Zasób",
@@ -583,6 +585,7 @@
"Label Line": "Linia etykiety",
"Label Position": "Pozycja etykiety",
"Label font size": "Rozmiar czcionki etykiety",
+ "Label is still in use": "Etykieta jest nadal w użyciu",
"Label shown below the current gauge value.": "Etykieta wyświetlana poniżej
bieżącej wartości wskaźnika.",
"Labels": "Etykiety",
"Labels & custom fields": "Etykiety i pola niestandardowe",
@@ -837,6 +840,7 @@
"Re-enter mail address": "Wprowadź ponownie adres e-mail",
"Recipient for test mail": "Odbiorca wiadomości testowej",
"Reduce event rate": "Zredukuj częstość zdarzeń",
+ "Referenced resources": "Wykaz źródeł",
"Refresh": "Odśwież",
"Refresh Fields": "Odśwież pola",
"Refresh adapters": "Odśwież adaptery",
@@ -1004,6 +1008,7 @@
"Simple filters below are kept for fallback, but the advanced filter is
currently used for queries.": "Poniższe proste filtry są zachowane jako opcja
zapasowa, ale obecnie do zapytań używany jest filtr zaawansowany.",
"Single": "Pojedynczy",
"Site": "Lokalizacja",
+ "Site is still in use": "Strona jest nadal aktywna",
"Site name is required": "Nazwa lokalizacji jest wymagana",
"Sites": "Lokalizacje",
"Sites & Areas": "Lokalizacje i obszary",
@@ -1136,8 +1141,10 @@
"Title": "Tytuł",
"Title must not be empty": "Tytuł nie może być pusty",
"To": "Do",
- "To delete a label, please remove the label from all assets": "Aby usunąć
etykietę, usuń ją ze wszystkich zasobów",
- "To delete a site, please remove the site from all assets": "Aby usunąć
lokalizację, usuń ją ze wszystkich zasobów",
+ "To delete a label, please remove the label from all assets": "Aby usunąć
etykietę, należy ją usunąć ze wszystkich zasobów",
+ "To delete a label, please remove the label from all assets.": "Aby usunąć
etykietę, należy ją usunąć ze wszystkich zasobów.",
+ "To delete a site, please remove the site from all assets": "Aby usunąć
witrynę, należy usunąć ją ze wszystkich zasobów",
+ "To delete a site, please remove the site from all assets.": "Aby usunąć
witrynę, należy ją usunąć ze wszystkich zasobów.",
"To enable the map view, a map provider needs to be configured. Admins can
configure map providers under Settings -> Sites.": "Aby włączyć widok mapy,
należy skonfigurować dostawcę map. Administratorzy mogą to zrobić w Ustawienia
-> Lokalizacje.",
"Tooltip": "Podpowiedź",
"Tooltip Content": "Zawartość podpowiedzi",
diff --git
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index b2ae2c4487..ba62219b0e 100644
---
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -572,9 +572,11 @@ export class AssetExportConfiguration {
dataViews: ExportItem[];
files: ExportItem[];
genericStorageDocuments: ExportItem[];
+ labels: ExportItem[];
overrideBrokerSettings: boolean;
overwriteExistingDocuments: boolean;
pipelines: ExportItem[];
+ sites: ExportItem[];
static fromData(
data: AssetExportConfiguration,
@@ -606,11 +608,13 @@ export class AssetExportConfiguration {
instance.genericStorageDocuments = __getCopyArrayFn(
ExportItem.fromData,
)(data.genericStorageDocuments);
+ instance.labels = __getCopyArrayFn(ExportItem.fromData)(data.labels);
instance.overrideBrokerSettings = data.overrideBrokerSettings;
instance.overwriteExistingDocuments = data.overwriteExistingDocuments;
instance.pipelines = __getCopyArrayFn(ExportItem.fromData)(
data.pipelines,
);
+ instance.sites = __getCopyArrayFn(ExportItem.fromData)(data.sites);
return instance;
}
}
diff --git a/ui/src/app/configuration/export/data-export-import.component.ts
b/ui/src/app/configuration/export/data-export-import.component.ts
index b9b06a1873..add7982b58 100644
--- a/ui/src/app/configuration/export/data-export-import.component.ts
+++ b/ui/src/app/configuration/export/data-export-import.component.ts
@@ -28,8 +28,15 @@ import {
import { SpConfigurationRoutes } from '../configuration.breadcrumb';
import { SpConfigurationTabsService } from '../configuration-tabs.service';
import {
+ AssetConstants,
AssetManagementService,
+ AssetSiteDesc,
+ ExportItem,
+ GenericStorageService,
+ LabelsService,
SpAsset,
+ SpAssetModel,
+ SpLabel,
} from '@streampipes/platform-services';
import { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox';
import { SpDataExportDialogComponent } from
'./export-dialog/data-export-dialog.component';
@@ -41,6 +48,13 @@ import {
LayoutDirective,
} from '@ngbracket/ngx-layout/flex';
import { MatButton } from '@angular/material/button';
+import { forkJoin, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+interface AssetReferenceExportItems {
+ referencedLabels: ExportItem[];
+ referencedSites: ExportItem[];
+}
@Component({
selector: 'sp-data-export-import',
@@ -60,13 +74,15 @@ import { MatButton } from '@angular/material/button';
export class SpDataExportImportComponent implements OnInit {
private breadcrumbService = inject(SpBreadcrumbService);
private assetManagementService = inject(AssetManagementService);
+ private genericStorageService = inject(GenericStorageService);
+ private labelsService = inject(LabelsService);
private dialogService = inject(DialogService);
private tabService = inject(SpConfigurationTabsService);
private translateService = inject(TranslateService);
tabs: SpNavigationItem[] = [];
- assets: SpAsset[];
+ assets: SpAssetModel[] = [];
selectedAssets: string[] = [];
ngOnInit(): void {
@@ -98,12 +114,24 @@ export class SpDataExportImportComponent implements OnInit
{
}
openExportDialog(): void {
+ this.getReferencedAssetDocuments().subscribe(
+ referencedAssetDocuments => {
+ this.openExportPreviewDialog(referencedAssetDocuments);
+ },
+ );
+ }
+
+ openExportPreviewDialog(
+ referencedAssetDocuments: AssetReferenceExportItems,
+ ): void {
const dialogRef = this.dialogService.open(SpDataExportDialogComponent,
{
panelType: PanelType.SLIDE_IN_PANEL,
title: this.translateService.instant('Export resources'),
width: '50vw',
data: {
selectedAssets: this.selectedAssets,
+ referencedLabels: referencedAssetDocuments.referencedLabels,
+ referencedSites: referencedAssetDocuments.referencedSites,
},
});
@@ -124,4 +152,70 @@ export class SpDataExportImportComponent implements OnInit
{
}
});
}
+
+ private getReferencedAssetDocuments():
Observable<AssetReferenceExportItems> {
+ return forkJoin({
+ assets: this.assetManagementService.getAllAssets(),
+ labels: this.labelsService.getAllLabels(),
+ sites: this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_SITES_APP_DOC_NAME,
+ ),
+ }).pipe(
+ map(({ assets, labels, sites }) =>
+ this.toReferencedAssetDocuments(
+ assets.filter(asset =>
+ this.selectedAssets.includes(asset.elementId),
+ ),
+ labels,
+ sites as AssetSiteDesc[],
+ ),
+ ),
+ );
+ }
+
+ private toReferencedAssetDocuments(
+ assets: SpAssetModel[],
+ labels: SpLabel[],
+ sites: AssetSiteDesc[],
+ ): AssetReferenceExportItems {
+ const labelIds = new Set<string>();
+ const siteIds = new Set<string>();
+
+ assets.forEach(asset =>
+ this.collectAssetReferences(asset, labelIds, siteIds),
+ );
+
+ return {
+ referencedLabels: labels
+ .filter(label => label._id && labelIds.has(label._id))
+ .map(label => ({
+ resourceId: label._id!,
+ label: label.label,
+ selected: true,
+ })),
+ referencedSites: sites
+ .filter(site => site._id && siteIds.has(site._id))
+ .map(site => ({
+ resourceId: site._id,
+ label: site.label,
+ selected: true,
+ })),
+ };
+ }
+
+ private collectAssetReferences(
+ asset: SpAsset,
+ labelIds: Set<string>,
+ siteIds: Set<string>,
+ ): void {
+ asset.labelIds?.forEach(labelId => labelIds.add(labelId));
+
+ if (asset.assetSite?.siteId) {
+ siteIds.add(asset.assetSite.siteId);
+ }
+
+ asset.assets?.forEach(subAsset =>
+ this.collectAssetReferences(subAsset, labelIds, siteIds),
+ );
+ }
}
diff --git
a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
index c85f514d02..1863ff8511 100644
---
a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
+++
b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.html
@@ -22,7 +22,8 @@
<div fxFlex="100" fxLayout="column" class="p-15">
@for (
config of preview.assetExportConfiguration;
- track config
+ track config;
+ let first = $first
) {
<div>
<h4>
@@ -57,12 +58,29 @@
[exportItems]="config.pipelines"
[sectionTitle]="'Pipelines' | translate"
></sp-data-export-item>
+
<sp-generic-storage-items
[exportItems]="config.genericStorageDocuments"
>
</sp-generic-storage-items>
</div>
}
+ @if (referencedLabels.length || referencedSites.length) {
+ <h4>{{ 'Referenced resources' | translate }}</h4>
+ @if (referencedLabels.length > 0) {
+ <sp-data-export-item
+ [exportItems]="referencedLabels"
+ [sectionTitle]="'Labels' | translate"
+ ></sp-data-export-item>
+ }
+
+ @if (referencedSites.length > 0) {
+ <sp-data-export-item
+ [exportItems]="referencedSites"
+ [sectionTitle]="'Sites' | translate"
+ ></sp-data-export-item>
+ }
+ }
</div>
}
@if (exportInProgress) {
diff --git
a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.ts
b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.ts
index 55282c7145..31b6dd1c28 100644
---
a/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.ts
+++
b/ui/src/app/configuration/export/export-dialog/data-export-dialog.component.ts
@@ -19,7 +19,10 @@
import { Component, Input, OnInit, inject } from '@angular/core';
import { DialogRef } from '@streampipes/shared-ui';
import { DataExportService } from '../data-export.service';
-import { ExportConfiguration } from '@streampipes/platform-services';
+import {
+ ExportConfiguration,
+ ExportItem,
+} from '@streampipes/platform-services';
import {
FlexDirective,
LayoutAlignDirective,
@@ -55,6 +58,12 @@ export class SpDataExportDialogComponent implements OnInit {
@Input()
selectedAssets: string[];
+ @Input()
+ referencedLabels: ExportItem[] = [];
+
+ @Input()
+ referencedSites: ExportItem[] = [];
+
preview: ExportConfiguration;
exportInProgress = false;
@@ -72,6 +81,7 @@ export class SpDataExportDialogComponent implements OnInit {
generateDownloadPackage(): void {
this.exportInProgress = true;
+ this.addReferencedAssetDocuments(this.preview);
this.dataExportService.triggerExport(this.preview).subscribe(result =>
{
this.downloadFile(result);
});
@@ -92,4 +102,33 @@ export class SpDataExportDialogComponent implements OnInit {
window.URL.revokeObjectURL(url);
this.dialogRef.close();
}
+
+ private addReferencedAssetDocuments(preview: ExportConfiguration): void {
+ const firstAssetExportConfig = preview.assetExportConfiguration?.[0];
+
+ if (firstAssetExportConfig) {
+ firstAssetExportConfig.labels = this.mergeExportItems(
+ firstAssetExportConfig.labels,
+ this.referencedLabels,
+ );
+ firstAssetExportConfig.sites = this.mergeExportItems(
+ firstAssetExportConfig.sites,
+ this.referencedSites,
+ );
+ }
+ }
+
+ private mergeExportItems(
+ existingItems: ExportItem[] = [],
+ newItems: ExportItem[],
+ ): ExportItem[] {
+ const existingItemIds = new Set(
+ existingItems.map(item => item.resourceId),
+ );
+ const missingItems = newItems.filter(
+ item => !existingItemIds.has(item.resourceId),
+ );
+
+ return [...existingItems, ...missingItems];
+ }
}
diff --git
a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
index 7ae8cdce00..9d64939a1f 100644
---
a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
+++
b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.html
@@ -140,6 +140,14 @@
[exportItems]="importConfiguration.pipelines"
[sectionTitle]="'Pipelines' | translate"
></sp-data-export-item>
+ <sp-data-export-item
+ [exportItems]="importConfiguration.labels"
+ [sectionTitle]="'Labels' | translate"
+ ></sp-data-export-item>
+ <sp-data-export-item
+ [exportItems]="importConfiguration.sites"
+ [sectionTitle]="'Sites' | translate"
+ ></sp-data-export-item>
<sp-generic-storage-items
[exportItems]="importConfiguration.genericStorageDocuments"
[importMode]="true"
diff --git
a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.ts
b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.ts
index 6a95b69b63..4a15ae4e1a 100644
---
a/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.ts
+++
b/ui/src/app/configuration/export/import-dialog/data-import-dialog.component.ts
@@ -165,6 +165,8 @@ export class SpDataImportDialogComponent {
this.toggleAllItems(this.importConfiguration.dashboards, select);
this.toggleAllItems(this.importConfiguration.dataViews, select);
this.toggleAllItems(this.importConfiguration.dataLakeMeasures, select);
+ this.toggleAllItems(this.importConfiguration.labels, select);
+ this.toggleAllItems(this.importConfiguration.sites, select);
this.toggleAllItems(
this.importConfiguration.genericStorageDocuments,
select,
@@ -173,6 +175,6 @@ export class SpDataImportDialogComponent {
}
private toggleAllItems(exportItem: ExportItem[], select: boolean): void {
- exportItem.forEach(e => (e.selected = select));
+ exportItem?.forEach(e => (e.selected = select));
}
}
diff --git
a/ui/src/app/configuration/label-configuration/label-configuration.component.html
b/ui/src/app/configuration/label-configuration/label-configuration.component.html
index 5420df12f5..c0375c8988 100644
---
a/ui/src/app/configuration/label-configuration/label-configuration.component.html
+++
b/ui/src/app/configuration/label-configuration/label-configuration.component.html
@@ -125,7 +125,6 @@
color="accent"
(click)="deleteLabel(label)"
data-cy="delete-label-button"
-
[disabled]="labelsinUse.includes(label._id)"
>
<mat-icon>delete</mat-icon>
</button>
diff --git
a/ui/src/app/configuration/label-configuration/label-configuration.component.ts
b/ui/src/app/configuration/label-configuration/label-configuration.component.ts
index 960edf19b3..c6e03b4b35 100644
---
a/ui/src/app/configuration/label-configuration/label-configuration.component.ts
+++
b/ui/src/app/configuration/label-configuration/label-configuration.component.ts
@@ -21,6 +21,7 @@ import { SpConfigurationTabsService } from
'../configuration-tabs.service';
import { LabelsService, SpLabel } from '@streampipes/platform-services';
import { SpConfigurationRoutes } from '../configuration.breadcrumb';
import {
+ ConfirmDialogComponent,
SpBasicNavTabsComponent,
SpBreadcrumbService,
SpLabelComponent,
@@ -46,7 +47,8 @@ import { MatButton, MatIconButton } from
'@angular/material/button';
import { SpEditLabelComponent } from './edit-label/edit-label.component';
import { MatTooltip } from '@angular/material/tooltip';
import { MatIcon } from '@angular/material/icon';
-import { TranslatePipe } from '@ngx-translate/core';
+import { TranslatePipe, TranslateService } from '@ngx-translate/core';
+import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'sp-label-configuration',
@@ -79,6 +81,8 @@ export class SpLabelConfigurationComponent implements OnInit {
private breadcrumbService = inject(SpBreadcrumbService);
private labelsService = inject(LabelsService);
private tabService = inject(SpConfigurationTabsService);
+ private dialog = inject(MatDialog);
+ private translateService = inject(TranslateService);
tabs: SpNavigationItem[] = [];
@@ -132,8 +136,53 @@ export class SpLabelConfigurationComponent implements
OnInit {
}
deleteLabel(label: SpLabel): void {
- this.labelsService.deleteLabel(label._id, label._rev).subscribe(() => {
- this.reloadLabels();
+ this.labelsService.getLabelsInUse().subscribe(labelsInUse => {
+ this.labelsinUse = labelsInUse;
+
+ if (labelsInUse.includes(label._id)) {
+ this.showLabelInUseWarning();
+ } else {
+ this.showDeleteLabelDialog(label);
+ }
+ });
+ }
+
+ showLabelInUseWarning(): void {
+ this.dialog.open(ConfirmDialogComponent, {
+ width: '500px',
+ data: {
+ title: this.translateService.instant('Label is still in use'),
+ subtitle: this.translateService.instant(
+ 'To delete a label, please remove the label from all
assets.',
+ ),
+ confirmTitle: this.translateService.instant('Ok'),
+ },
+ });
+ }
+
+ showDeleteLabelDialog(label: SpLabel): void {
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+ width: '500px',
+ data: {
+ title: this.translateService.instant(
+ 'Are you sure you want to delete this label?',
+ ),
+ subtitle: this.translateService.instant(
+ 'This action cannot be reversed!',
+ ),
+ cancelTitle: this.translateService.instant('Cancel'),
+ confirmTitle: this.translateService.instant('Delete label'),
+ },
+ });
+
+ dialogRef.afterClosed().subscribe(result => {
+ if (result === 'confirm') {
+ this.labelsService
+ .deleteLabel(label._id, label._rev)
+ .subscribe(() => {
+ this.reloadLabels();
+ });
+ }
});
}
diff --git
a/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.html
b/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.html
index 39eeff4bf0..a43162717a 100644
---
a/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.html
+++
b/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.html
@@ -87,7 +87,6 @@
(click)="deleteSite(site)"
mat-icon-button
color="accent"
- [disabled]="allUsedSiteIds.includes(site._id)"
>
<mat-icon>delete</mat-icon>
</button>
diff --git
a/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.ts
b/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.ts
index 0b9e86dbbc..4d6c12e234 100644
---
a/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.ts
+++
b/ui/src/app/configuration/sites-configuration/site-area-configuration/site-area-configuration.component.ts
@@ -33,6 +33,7 @@ import {
} from '@angular/material/table';
import { ManageSiteDialogComponent } from
'../../dialog/manage-site/manage-site-dialog.component';
import {
+ ConfirmDialogComponent,
DialogService,
PanelType,
SplitSectionComponent,
@@ -47,6 +48,14 @@ import {
LayoutDirective,
} from '@ngbracket/ngx-layout/flex';
import { MatTooltip } from '@angular/material/tooltip';
+import { MatDialog } from '@angular/material/dialog';
+
+interface AssetWithSite {
+ assetSite?: {
+ siteId?: string;
+ };
+ assets?: AssetWithSite[];
+}
@Component({
selector: 'sp-site-area-configuration',
@@ -74,6 +83,7 @@ export class SiteAreaConfigurationComponent implements OnInit
{
private genericStorageService = inject(GenericStorageService);
private dialogService = inject(DialogService);
private translateService = inject(TranslateService);
+ private dialog = inject(MatDialog);
@Input()
locationConfig: LocationConfig;
@@ -82,7 +92,7 @@ export class SiteAreaConfigurationComponent implements OnInit
{
dataSource: MatTableDataSource<AssetSiteDesc> =
new MatTableDataSource<AssetSiteDesc>();
- allUsedSiteIds = [];
+ allUsedSiteIds: string[] = [];
@ViewChild(MatSort)
sort: MatSort;
@@ -121,15 +131,71 @@ export class SiteAreaConfigurationComponent implements
OnInit {
});
}
- extractSiteIds(assets) {
+ extractSiteIds(assets: AssetWithSite[]): string[] {
const allSiteIds = new Set<string>();
- assets.forEach(asset => allSiteIds.add(asset.assetSite.siteId));
+ const extractSiteFromAsset = (asset: AssetWithSite) => {
+ if (asset.assetSite?.siteId) {
+ allSiteIds.add(asset.assetSite.siteId);
+ }
+ asset.assets?.forEach(subAsset => extractSiteFromAsset(subAsset));
+ };
+
+ assets.forEach(asset => extractSiteFromAsset(asset));
return Array.from(allSiteIds);
}
deleteSite(site: AssetSiteDesc): void {
+ this.genericStorageService
+ .getAllDocuments(AssetConstants.ASSET_APP_DOC_NAME)
+ .subscribe(res => {
+ this.allUsedSiteIds = this.extractSiteIds(res);
+
+ if (this.allUsedSiteIds.includes(site._id)) {
+ this.showSiteInUseWarning();
+ } else {
+ this.showDeleteSiteDialog(site);
+ }
+ });
+ }
+
+ showSiteInUseWarning(): void {
+ this.dialog.open(ConfirmDialogComponent, {
+ width: '500px',
+ data: {
+ title: this.translateService.instant('Site is still in use'),
+ subtitle: this.translateService.instant(
+ 'To delete a site, please remove the site from all
assets.',
+ ),
+ confirmTitle: this.translateService.instant('Ok'),
+ },
+ });
+ }
+
+ showDeleteSiteDialog(site: AssetSiteDesc): void {
+ const dialogRef = this.dialog.open(ConfirmDialogComponent, {
+ width: '500px',
+ data: {
+ title: this.translateService.instant(
+ 'Are you sure you want to delete this site?',
+ ),
+ subtitle: this.translateService.instant(
+ 'This action cannot be reversed!',
+ ),
+ cancelTitle: this.translateService.instant('Cancel'),
+ confirmTitle: this.translateService.instant('Delete site'),
+ },
+ });
+
+ dialogRef.afterClosed().subscribe(result => {
+ if (result === 'confirm') {
+ this.deleteSiteDocument(site);
+ }
+ });
+ }
+
+ deleteSiteDocument(site: AssetSiteDesc): void {
this.genericStorageService
.deleteDocument(
AssetConstants.ASSET_SITES_APP_DOC_NAME,