Skip to content

Commit 64aaaf1

Browse files
fix(model): fixes DashboardContainsDashboard relationship in DashboardInfo aspect (#12433)
1 parent ffc98da commit 64aaaf1

File tree

13 files changed

+381
-8
lines changed

13 files changed

+381
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.linkedin.datahub.upgrade.config.restoreindices;
2+
3+
import com.linkedin.datahub.upgrade.config.SystemUpdateCondition;
4+
import com.linkedin.datahub.upgrade.system.NonBlockingSystemUpgrade;
5+
import com.linkedin.datahub.upgrade.system.restoreindices.dashboardinfo.ReindexDashboardInfo;
6+
import com.linkedin.metadata.entity.AspectDao;
7+
import com.linkedin.metadata.entity.EntityService;
8+
import io.datahubproject.metadata.context.OperationContext;
9+
import org.springframework.beans.factory.annotation.Value;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Conditional;
12+
import org.springframework.context.annotation.Configuration;
13+
14+
@Configuration
15+
@Conditional(SystemUpdateCondition.NonBlockingSystemUpdateCondition.class)
16+
public class ReindexDashboardInfoConfig {
17+
18+
@Bean
19+
public NonBlockingSystemUpgrade reindexDashboardInfo(
20+
final OperationContext opContext,
21+
final EntityService<?> entityService,
22+
final AspectDao aspectDao,
23+
@Value("${systemUpdate.dashboardInfo.enabled}") final boolean enabled,
24+
@Value("${systemUpdate.dashboardInfo.batchSize}") final Integer batchSize,
25+
@Value("${systemUpdate.dashboardInfo.delayMs}") final Integer delayMs,
26+
@Value("${systemUpdate.dashboardInfo.limit}") final Integer limit) {
27+
return new ReindexDashboardInfo(
28+
opContext, entityService, aspectDao, enabled, batchSize, delayMs, limit);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.linkedin.datahub.upgrade.system.restoreindices.dashboardinfo;
2+
3+
import com.google.common.collect.ImmutableList;
4+
import com.linkedin.datahub.upgrade.UpgradeStep;
5+
import com.linkedin.datahub.upgrade.system.NonBlockingSystemUpgrade;
6+
import com.linkedin.metadata.entity.AspectDao;
7+
import com.linkedin.metadata.entity.EntityService;
8+
import io.datahubproject.metadata.context.OperationContext;
9+
import java.util.List;
10+
import javax.annotation.Nonnull;
11+
import lombok.extern.slf4j.Slf4j;
12+
13+
/**
14+
* A job that reindexes all dashboard info aspects as part of reindexing dashboards relationship.
15+
* This is required to fix the dashboards relationships for dashboards
16+
*/
17+
@Slf4j
18+
public class ReindexDashboardInfo implements NonBlockingSystemUpgrade {
19+
20+
private final List<UpgradeStep> _steps;
21+
22+
public ReindexDashboardInfo(
23+
@Nonnull OperationContext opContext,
24+
EntityService<?> entityService,
25+
AspectDao aspectDao,
26+
boolean enabled,
27+
Integer batchSize,
28+
Integer batchDelayMs,
29+
Integer limit) {
30+
if (enabled) {
31+
_steps =
32+
ImmutableList.of(
33+
new ReindexDashboardInfoStep(
34+
opContext, entityService, aspectDao, batchSize, batchDelayMs, limit));
35+
} else {
36+
_steps = ImmutableList.of();
37+
}
38+
}
39+
40+
@Override
41+
public String id() {
42+
return this.getClass().getName();
43+
}
44+
45+
@Override
46+
public List<UpgradeStep> steps() {
47+
return _steps;
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.linkedin.datahub.upgrade.system.restoreindices.dashboardinfo;
2+
3+
import static com.linkedin.metadata.Constants.*;
4+
5+
import com.linkedin.datahub.upgrade.system.AbstractMCLStep;
6+
import com.linkedin.metadata.entity.AspectDao;
7+
import com.linkedin.metadata.entity.EntityService;
8+
import io.datahubproject.metadata.context.OperationContext;
9+
import javax.annotation.Nonnull;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
@Slf4j
14+
public class ReindexDashboardInfoStep extends AbstractMCLStep {
15+
16+
public ReindexDashboardInfoStep(
17+
OperationContext opContext,
18+
EntityService<?> entityService,
19+
AspectDao aspectDao,
20+
Integer batchSize,
21+
Integer batchDelayMs,
22+
Integer limit) {
23+
super(opContext, entityService, aspectDao, batchSize, batchDelayMs, limit);
24+
}
25+
26+
@Override
27+
public String id() {
28+
return "dashboard-info-v1";
29+
}
30+
31+
@Nonnull
32+
@Override
33+
protected String getAspectName() {
34+
return DASHBOARD_INFO_ASPECT_NAME;
35+
}
36+
37+
@Nullable
38+
@Override
39+
protected String getUrnLike() {
40+
return "urn:li:" + DASHBOARD_ENTITY_NAME + ":%";
41+
}
42+
}

docs/how/updating-datahub.md

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ This file documents any backwards-incompatible changes in DataHub and assists pe
2828

2929
### Other Notable Changes
3030

31+
- #12433: Fixes the searchable annotations in the model supporting `Dashboard` to `Dashboard` lineage within the `DashboardInfo` aspect. Mainly, users of Sigma and PowerBI Apps ingestion may be affected by this adjustment. Consequently, a [reindex](https://datahubproject.io/docs/how/restore-indices/) will be automatically triggered during the system upgrade.
32+
3133
## 0.15.0
3234

3335
- OpenAPI Update: PIT Keep Alive parameter added to scroll endpoints. NOTE: This parameter requires the `pointInTimeCreationEnabled` feature flag to be enabled and the `elasticSearch.implementation` configuration to be `elasticsearch`. This feature is not supported for OpenSearch at this time and the parameter will not be respected without both of these set.

entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/DashboardInfoPatchBuilder.java

+23
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.fasterxml.jackson.databind.node.ObjectNode;
1010
import com.linkedin.common.Edge;
1111
import com.linkedin.common.urn.ChartUrn;
12+
import com.linkedin.common.urn.DashboardUrn;
1213
import com.linkedin.common.urn.DatasetUrn;
1314
import com.linkedin.common.urn.Urn;
1415
import com.linkedin.metadata.aspect.patch.PatchOperationType;
@@ -19,6 +20,7 @@ public class DashboardInfoPatchBuilder
1920
extends AbstractMultiFieldPatchBuilder<DashboardInfoPatchBuilder> {
2021
private static final String CHART_EDGES_PATH_START = "/chartEdges/";
2122
private static final String DATASET_EDGES_PATH_START = "/datasetEdges/";
23+
private static final String DASHBOARDS_PATH_START = "/dashboards/";
2224

2325
// Simplified with just Urn
2426
public DashboardInfoPatchBuilder addChartEdge(@Nonnull ChartUrn urn) {
@@ -36,6 +38,7 @@ public DashboardInfoPatchBuilder removeChartEdge(@Nonnull ChartUrn urn) {
3638
return this;
3739
}
3840

41+
// Simplified with just Urn
3942
public DashboardInfoPatchBuilder addDatasetEdge(@Nonnull DatasetUrn urn) {
4043
ObjectNode value = createEdgeValue(urn);
4144

@@ -52,6 +55,22 @@ public DashboardInfoPatchBuilder removeDatasetEdge(@Nonnull DatasetUrn urn) {
5255
return this;
5356
}
5457

58+
// Simplified with just Urn
59+
public DashboardInfoPatchBuilder addDashboard(@Nonnull DashboardUrn urn) {
60+
ObjectNode value = createEdgeValue(urn);
61+
62+
pathValues.add(
63+
ImmutableTriple.of(PatchOperationType.ADD.getValue(), DASHBOARDS_PATH_START + urn, value));
64+
return this;
65+
}
66+
67+
public DashboardInfoPatchBuilder removeDashboard(@Nonnull DashboardUrn urn) {
68+
pathValues.add(
69+
ImmutableTriple.of(
70+
PatchOperationType.REMOVE.getValue(), DASHBOARDS_PATH_START + urn, null));
71+
return this;
72+
}
73+
5574
// Full Edge modification
5675
public DashboardInfoPatchBuilder addEdge(@Nonnull Edge edge) {
5776
ObjectNode value = createEdgeValue(edge);
@@ -87,6 +106,10 @@ private String getEdgePath(@Nonnull Edge edge) {
87106
return CHART_EDGES_PATH_START + destinationUrn;
88107
}
89108

109+
if (DASHBOARD_ENTITY_NAME.equals(destinationUrn.getEntityType())) {
110+
return DASHBOARDS_PATH_START + destinationUrn;
111+
}
112+
90113
// TODO: Output Data Jobs not supported by aspect, add here if this changes
91114

92115
throw new IllegalArgumentException(

entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/template/dashboard/DashboardInfoTemplate.java

+13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class DashboardInfoTemplate implements ArrayMergingTemplate<DashboardInfo
2222
private static final String DATASETS_FIELD_NAME = "datasets";
2323
private static final String CHARTS_FIELD_NAME = "charts";
2424
private static final String DESTINATION_URN_FIELD_NAME = "destinationUrn";
25+
private static final String DASHBOARDS_FIELD_NAME = "dashboards";
2526

2627
@Override
2728
public DashboardInfo getSubtype(RecordTemplate recordTemplate) throws ClassCastException {
@@ -74,6 +75,12 @@ public JsonNode transformFields(JsonNode baseNode) {
7475
DATASET_EDGES_FIELD_NAME,
7576
Collections.singletonList(DESTINATION_URN_FIELD_NAME));
7677

78+
transformedNode =
79+
arrayFieldToMap(
80+
transformedNode,
81+
DASHBOARDS_FIELD_NAME,
82+
Collections.singletonList(DESTINATION_URN_FIELD_NAME));
83+
7784
transformedNode =
7885
arrayFieldToMap(transformedNode, DATASETS_FIELD_NAME, Collections.emptyList());
7986

@@ -97,6 +104,12 @@ public JsonNode rebaseFields(JsonNode patched) {
97104
CHART_EDGES_FIELD_NAME,
98105
Collections.singletonList(DESTINATION_URN_FIELD_NAME));
99106

107+
rebasedNode =
108+
transformedMapToArray(
109+
rebasedNode,
110+
DASHBOARDS_FIELD_NAME,
111+
Collections.singletonList(DESTINATION_URN_FIELD_NAME));
112+
100113
rebasedNode = transformedMapToArray(rebasedNode, DATASETS_FIELD_NAME, Collections.emptyList());
101114
rebasedNode = transformedMapToArray(rebasedNode, CHARTS_FIELD_NAME, Collections.emptyList());
102115

entity-registry/src/test/java/com/linkedin/metadata/aspect/patch/template/DashboardInfoTemplateTest.java

+22
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,26 @@ public void testDashboardInfoTemplate() throws Exception {
3131
UrnUtils.getUrn("urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)"),
3232
result.getDatasetEdges().get(0).getDestinationUrn());
3333
}
34+
35+
@Test
36+
public void testDashboardInfoTemplateDashboardsField() throws Exception {
37+
DashboardInfoTemplate dashboardInfoTemplate = new DashboardInfoTemplate();
38+
DashboardInfo dashboardInfo = dashboardInfoTemplate.getDefault();
39+
JsonPatchBuilder jsonPatchBuilder = Json.createPatchBuilder();
40+
jsonPatchBuilder.add(
41+
"/dashboards/urn:li:dashboard:(urn:li:dataPlatform:tableau,SampleDashboard,PROD)",
42+
Json.createObjectBuilder()
43+
.add(
44+
"destinationUrn",
45+
Json.createValue(
46+
"urn:li:dashboard:(urn:li:dataPlatform:tableau,SampleDashboard,PROD)"))
47+
.build());
48+
49+
DashboardInfo result =
50+
dashboardInfoTemplate.applyPatch(dashboardInfo, jsonPatchBuilder.build());
51+
52+
Assert.assertEquals(
53+
UrnUtils.getUrn("urn:li:dashboard:(urn:li:dataPlatform:tableau,SampleDashboard,PROD)"),
54+
result.getDashboards().get(0).getDestinationUrn());
55+
}
3456
}

metadata-ingestion/src/datahub/ingestion/api/incremental_lineage_helper.py

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def convert_dashboard_info_to_patch(
102102
if aspect.datasets:
103103
patch_builder.add_datasets(aspect.datasets)
104104

105+
if aspect.dashboards:
106+
for dashboard in aspect.dashboards:
107+
patch_builder.add_dashboard(dashboard)
108+
105109
if aspect.access:
106110
patch_builder.set_access(aspect.access)
107111

metadata-ingestion/src/datahub/specific/dashboard.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def add_chart_edge(self, chart: Union[Edge, Urn, str]) -> "DashboardPatchBuilder
161161
lastModified=self._mint_auditstamp(),
162162
)
163163

164-
self._ensure_urn_type("dataset", [chart_edge], "add_chart_edge")
164+
self._ensure_urn_type("chart", [chart_edge], "add_chart_edge")
165165
self._add_patch(
166166
DashboardInfo.ASPECT_NAME,
167167
"add",
@@ -271,6 +271,48 @@ def add_datasets(
271271

272272
return self
273273

274+
def add_dashboard(
275+
self, dashboard: Union[Edge, Urn, str]
276+
) -> "DashboardPatchBuilder":
277+
"""
278+
Adds an dashboard to the DashboardPatchBuilder.
279+
280+
Args:
281+
dashboard: The dashboard, which can be an Edge object, Urn object, or a string.
282+
283+
Returns:
284+
The DashboardPatchBuilder instance.
285+
286+
Raises:
287+
ValueError: If the dashboard is not a Dashboard urn.
288+
289+
Notes:
290+
If `dashboard` is an Edge object, it is used directly. If `dashboard` is a Urn object or string,
291+
it is converted to an Edge object and added with default audit stamps.
292+
"""
293+
if isinstance(dashboard, Edge):
294+
dashboard_urn: str = dashboard.destinationUrn
295+
dashboard_edge: Edge = dashboard
296+
elif isinstance(dashboard, (Urn, str)):
297+
dashboard_urn = str(dashboard)
298+
if not dashboard_urn.startswith("urn:li:dashboard:"):
299+
raise ValueError(f"Input {dashboard} is not a Dashboard urn")
300+
301+
dashboard_edge = Edge(
302+
destinationUrn=dashboard_urn,
303+
created=self._mint_auditstamp(),
304+
lastModified=self._mint_auditstamp(),
305+
)
306+
307+
self._ensure_urn_type("dashboard", [dashboard_edge], "add_dashboard")
308+
self._add_patch(
309+
DashboardInfo.ASPECT_NAME,
310+
"add",
311+
path=("dashboards", dashboard_urn),
312+
value=dashboard_edge,
313+
)
314+
return self
315+
274316
def set_dashboard_url(
275317
self, dashboard_url: Optional[str]
276318
) -> "DashboardPatchBuilder":

0 commit comments

Comments
 (0)