-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat(timeline-ui): [HUDI-9315] Add Hudi Timeline UI #13147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.hudi.common.table.timeline.dto.v2; | ||
|
|
||
| import org.apache.hudi.common.table.timeline.HoodieInstant; | ||
| import org.apache.hudi.common.table.timeline.InstantGenerator; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| /** | ||
| * The data transfer object of instant. | ||
| */ | ||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public class InstantDTO { | ||
|
|
||
| @JsonProperty("action") | ||
| public String action; | ||
| @JsonProperty("requestTs") | ||
| public String requestedTime; | ||
| @JsonProperty("completionTs") | ||
| public String completionTime; | ||
| @JsonProperty("state") | ||
| public String state; | ||
|
|
||
| public static InstantDTO fromInstant(HoodieInstant instant) { | ||
| if (null == instant) { | ||
| return null; | ||
| } | ||
|
|
||
| InstantDTO dto = new InstantDTO(); | ||
| dto.action = instant.getAction(); | ||
| dto.requestedTime = instant.requestedTime(); | ||
| dto.completionTime = instant.getCompletionTime(); | ||
| dto.state = instant.getState().toString(); | ||
| return dto; | ||
| } | ||
|
|
||
| public static HoodieInstant toInstant(InstantDTO dto, InstantGenerator factory) { | ||
| if (null == dto) { | ||
| return null; | ||
| } | ||
|
|
||
| return factory.createNewInstant(HoodieInstant.State.valueOf(dto.state), dto.action, | ||
| dto.requestedTime, dto.completionTime); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.hudi.common.table.timeline.dto.v2; | ||
|
|
||
| import org.apache.hudi.common.table.HoodieTableMetaClient; | ||
| import org.apache.hudi.common.table.timeline.HoodieTimeline; | ||
| import org.apache.hudi.common.table.timeline.InstantGenerator; | ||
| import org.apache.hudi.common.table.timeline.TimelineFactory; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| /** | ||
| * The data transfer object of timeline. | ||
| */ | ||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public class TimelineDTOV2 { | ||
|
|
||
| @JsonProperty("instants") | ||
| public List<InstantDTO> instants; | ||
|
|
||
| public static TimelineDTOV2 fromTimeline(HoodieTimeline timeline) { | ||
| TimelineDTOV2 dto = new TimelineDTOV2(); | ||
| dto.instants = timeline.getInstantsAsStream().map(InstantDTO::fromInstant).collect(Collectors.toList()); | ||
| return dto; | ||
| } | ||
|
|
||
| public static HoodieTimeline toTimeline(TimelineDTOV2 dto, HoodieTableMetaClient metaClient) { | ||
| InstantGenerator instantGenerator = metaClient.getInstantGenerator(); | ||
| TimelineFactory factory = metaClient.getTimelineLayout().getTimelineFactory(); | ||
| // TODO: For Now, we will assume, only active-timeline will be transferred. | ||
| return factory.createDefaultTimeline(dto.instants.stream().map(d -> InstantDTO.toInstant(d, instantGenerator)), | ||
| metaClient.getActiveTimeline()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| import org.apache.hudi.common.table.timeline.dto.FileSliceDTO; | ||
| import org.apache.hudi.common.table.timeline.dto.InstantDTO; | ||
| import org.apache.hudi.common.table.timeline.dto.TimelineDTO; | ||
| import org.apache.hudi.common.table.timeline.dto.v2.TimelineDTOV2; | ||
| import org.apache.hudi.common.table.view.FileSystemViewManager; | ||
| import org.apache.hudi.common.table.view.RemoteHoodieTableFileSystemView; | ||
| import org.apache.hudi.common.table.view.SyncableFileSystemView; | ||
|
|
@@ -171,6 +172,18 @@ private static String getMinInstantParam(Context ctx) { | |
| return ctx.queryParamAsClass(RemoteHoodieTableFileSystemView.MIN_INSTANT_PARAM, String.class).getOrDefault(""); | ||
| } | ||
|
|
||
| private static String getInstantParam(Context ctx) { | ||
| return ctx.queryParamAsClass(RemoteHoodieTableFileSystemView.INSTANT_PARAM, String.class).getOrThrow(e -> new BadRequestResponse("INSTANT_PARAM is required")); | ||
| } | ||
|
|
||
| private static String getInstantActionParam(Context ctx) { | ||
| return ctx.queryParamAsClass(RemoteHoodieTableFileSystemView.INSTANT_ACTION_PARAM, String.class).getOrThrow(e -> new BadRequestResponse("INSTANT_ACTION_PARAM is required")); | ||
| } | ||
|
|
||
| private static String getInstantStateParam(Context ctx) { | ||
| return ctx.queryParamAsClass(RemoteHoodieTableFileSystemView.INSTANT_STATE_PARAM, String.class).getOrThrow(e -> new BadRequestResponse("INSTANT_STATE_PARAM is required")); | ||
| } | ||
|
voonhous marked this conversation as resolved.
|
||
|
|
||
| private static String getMarkerDirParam(Context ctx) { | ||
| return ctx.queryParamAsClass(MarkerOperation.MARKER_DIR_PATH_PARAM, String.class).getOrDefault(""); | ||
| } | ||
|
|
@@ -185,6 +198,9 @@ public void register() { | |
| registerDataFilesAPI(); | ||
| registerFileSlicesAPI(); | ||
| registerTimelineAPI(); | ||
| if (timelineServiceConfig.enableUi) { | ||
| registerTimelineV2API(); | ||
| } | ||
| if (markerHandler != null) { | ||
| registerMarkerAPI(); | ||
| } | ||
|
|
@@ -242,6 +258,43 @@ private void registerTimelineAPI() { | |
| }, false)); | ||
| } | ||
|
|
||
| /** | ||
| * Register v2 Timeline API calls used by the Timeline UI. Gated behind --enable-ui. | ||
| */ | ||
| private void registerTimelineV2API() { | ||
| app.get(RemoteHoodieTableFileSystemView.TIMELINE_V2_URL, new ViewHandler(ctx -> { | ||
|
voonhous marked this conversation as resolved.
|
||
| metricsRegistry.add("TIMELINE_V2", 1); | ||
| TimelineDTOV2 dto = instantHandler.getTimelineV2(getBasePathParam(ctx)); | ||
| writeValueAsString(ctx, dto); | ||
| }, false)); | ||
|
|
||
| app.get(RemoteHoodieTableFileSystemView.INSTANT_DETAILS_URL, new ViewHandler(ctx -> { | ||
| metricsRegistry.add("INSTANT_DETAILS", 1); | ||
| Object instantDetails = instantHandler.getInstantDetails(getBasePathParam(ctx), | ||
| getInstantParam(ctx), getInstantActionParam(ctx), getInstantStateParam(ctx)); | ||
| writeValueAsString(ctx, instantDetails); | ||
| }, false)); | ||
|
|
||
| app.get(RemoteHoodieTableFileSystemView.TABLE_CONFIG_V2_URL, new ViewHandler(ctx -> { | ||
| metricsRegistry.add("TABLE_CONFIG", 1); | ||
| writeValueAsString(ctx, instantHandler.getTableConfig(getBasePathParam(ctx))); | ||
| }, false)); | ||
|
|
||
| app.get(RemoteHoodieTableFileSystemView.SCHEMA_HISTORY_V2_URL, new ViewHandler(ctx -> { | ||
| metricsRegistry.add("SCHEMA_HISTORY", 1); | ||
|
voonhous marked this conversation as resolved.
|
||
| int limit; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 nit: the - AI-generated; verify before applying. React 👍/👎 to flag quality. |
||
| try { | ||
| limit = Integer.parseInt(ctx.queryParamAsClass("limit", String.class).getOrDefault("200")); | ||
| } catch (NumberFormatException e) { | ||
| throw new BadRequestResponse("limit must be an integer"); | ||
| } | ||
| if (limit <= 0 || limit > 1000) { | ||
| throw new BadRequestResponse("limit must be between 1 and 1000"); | ||
| } | ||
| writeValueAsString(ctx, instantHandler.getSchemaHistory(getBasePathParam(ctx), limit)); | ||
|
voonhous marked this conversation as resolved.
|
||
| }, false)); | ||
| } | ||
|
|
||
| /** | ||
| * Register Data-Files API calls. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖 nit: could you rename
factorytoinstantGeneratorhere? The same type is referred to asinstantGeneratorinTimelineDTOV2.toTimelineand inTimelineHandler, so calling itfactoryin this one spot breaks the naming convention and makes readers wonder whether it's a different object.- AI-generated; verify before applying. React 👍/👎 to flag quality.