From 02cbc557715cf41b96f13a60ae56ba240697558f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=87=E9=A2=86?= Date: Fri, 29 May 2026 12:50:43 +0800 Subject: [PATCH 1/5] Add Maintenance tab for table process management --- .../server/dashboard/DashboardServer.java | 3 ++ .../MixedAndIcebergTableDescriptor.java | 52 ++++++++++++++++++- .../dashboard/ServerTableDescriptor.java | 15 +++++- .../dashboard/controller/TableController.java | 23 +++++++- .../mapper/TableProcessMapper.java | 4 ++ .../TestIcebergServerTableDescriptor.java | 9 ++-- .../optimizing/BaseOptimizingChecker.java | 7 +-- .../TestProcessDataExpiringExecutor.java | 4 +- .../descriptor/FormatTableDescriptor.java | 30 ++++++++++- .../formats/hudi/HudiTableDescriptor.java | 12 ++++- .../formats/paimon/PaimonTableDescriptor.java | 13 ++++- amoro-web/src/language/en.ts | 3 ++ amoro-web/src/language/zh.ts | 3 ++ amoro-web/src/services/table.service.ts | 18 ++++++- .../views/tables/components/Maintenance.vue | 26 ++++++++++ .../views/tables/components/Optimizing.vue | 42 +++++++++------ amoro-web/src/views/tables/index.vue | 3 ++ 17 files changed, 234 insertions(+), 33 deletions(-) create mode 100644 amoro-web/src/views/tables/components/Maintenance.vue diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java index 2246cf68b7..4cf8850ff7 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java @@ -270,6 +270,9 @@ private EndpointGroup apiGroup() { get( "/catalogs/{catalog}/dbs/{db}/tables/{table}/optimizing-types", tableController::getOptimizingTypes); + get( + "/catalogs/{catalog}/dbs/{db}/tables/{table}/maintenance-types", + tableController::getMaintenanceTypes); get( "/catalogs/{catalog}/dbs/{db}/tables/{table}/optimizing-processes/{processId}/tasks", tableController::getOptimizingProcessTasks); diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java index 7f3a49d0c5..6570c1a468 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java @@ -22,6 +22,7 @@ import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.apache.amoro.AmoroTable; +import org.apache.amoro.IcebergActions; import org.apache.amoro.ServerTableIdentifier; import org.apache.amoro.TableFormat; import org.apache.amoro.api.CommitMetaProducer; @@ -73,6 +74,7 @@ import org.apache.amoro.utils.MixedDataFiles; import org.apache.amoro.utils.MixedTableUtil; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.iceberg.ContentFile; import org.apache.iceberg.FileScanTask; @@ -118,6 +120,12 @@ public class MixedAndIcebergTableDescriptor extends PersistentBase private static final Logger LOG = LoggerFactory.getLogger(MixedAndIcebergTableDescriptor.class); + private static final List OPTIMIZING_TYPE_LIST = + Arrays.stream(OptimizingType.values()).map(Enum::name).collect(Collectors.toList()); + + private static final String OPTIMIZING = "OPTIMIZING"; + private static final String MAINTENANCE = "MAINTENANCE"; + private ExecutorService executorService; @Override @@ -655,7 +663,12 @@ public List getTableConsumerInfos(AmoroTable amoroTable) { @Override public Pair, Integer> getOptimizingProcessesInfo( - AmoroTable amoroTable, String type, ProcessStatus status, int limit, int offset) { + AmoroTable amoroTable, + String type, + String processCategory, + ProcessStatus status, + int limit, + int offset) { TableIdentifier tableIdentifier = amoroTable.id(); ServerTableIdentifier identifier = getAs( @@ -671,12 +684,35 @@ public Pair, Integer> getOptimizingProcessesInfo( int total = 0; // page helper is 1-based int pageNumber = (offset / limit) + 1; + + // Only apply category filtering when type is not specified + final List includeTypes; + final List excludeTypes; + + if (StringUtils.isBlank(type)) { + if (OPTIMIZING.equalsIgnoreCase(processCategory)) { + includeTypes = OPTIMIZING_TYPE_LIST; + excludeTypes = null; + } else if (MAINTENANCE.equalsIgnoreCase(processCategory)) { + includeTypes = null; + excludeTypes = OPTIMIZING_TYPE_LIST; + } else { + includeTypes = null; + excludeTypes = null; + } + } else { + includeTypes = null; + excludeTypes = null; + } + List processMetaList = Collections.emptyList(); try (Page ignored = PageHelper.startPage(pageNumber, limit, true)) { processMetaList = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), type, status)); + mapper -> + mapper.listProcessMeta( + identifier.getId(), type, includeTypes, excludeTypes, status)); PageInfo pageInfo = new PageInfo<>(processMetaList); total = (int) pageInfo.getTotal(); LOG.info( @@ -716,6 +752,18 @@ public Map getTableOptimizingTypes(AmoroTable amoroTable) { return types; } + @Override + public Map getTableMaintenanceTypes(AmoroTable amoroTable) { + Map types = Maps.newHashMap(); + types.put(IcebergActions.EXPIRE_SNAPSHOTS.getName(), "Expire Snapshots"); + types.put(IcebergActions.CLEAN_ORPHAN.getName(), "Clean Orphan Files"); + types.put(IcebergActions.CLEAN_DANGLING_DELETE.getName(), "Clean Dangling Delete Files"); + types.put(IcebergActions.EXPIRE_DATA.getName(), "Expire Data"); + types.put(IcebergActions.SYNC_HIVE_TABLES.getName(), "Sync Hive Tables"); + types.put(IcebergActions.AUTO_CREATE_TAGS.getName(), "Auto Create Tags"); + return types; + } + @Override public List getOptimizingTaskInfos( AmoroTable amoroTable, String processId) { diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java index 4e2b1aef8c..1168a06c48 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java @@ -129,11 +129,16 @@ public List getTableConsumersInfos(TableIdentifier tableIdentifier } public Pair, Integer> getOptimizingProcessesInfo( - TableIdentifier tableIdentifier, String type, ProcessStatus status, int limit, int offset) { + TableIdentifier tableIdentifier, + String type, + String processCategory, + ProcessStatus status, + int limit, + int offset) { AmoroTable amoroTable = loadTable(tableIdentifier); FormatTableDescriptor formatTableDescriptor = formatDescriptorMap.get(amoroTable.format()); return formatTableDescriptor.getOptimizingProcessesInfo( - amoroTable, type, status, limit, offset); + amoroTable, type, processCategory, status, limit, offset); } public List getOptimizingProcessTaskInfos( @@ -149,6 +154,12 @@ public Map getTableOptimizingTypes(TableIdentifier tableIdentifi return formatTableDescriptor.getTableOptimizingTypes(amoroTable); } + public Map getTableMaintenanceTypes(TableIdentifier tableIdentifier) { + AmoroTable amoroTable = loadTable(tableIdentifier); + FormatTableDescriptor formatTableDescriptor = formatDescriptorMap.get(amoroTable.format()); + return formatTableDescriptor.getTableMaintenanceTypes(amoroTable); + } + private AmoroTable loadTable(TableIdentifier identifier) { ServerCatalog catalog = catalogManager.getServerCatalog(identifier.getCatalog()); return catalog.loadTable(identifier.getDatabase(), identifier.getTableName()); diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java index 8b4acba44a..1eb98c05c2 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java @@ -332,6 +332,11 @@ public void getOptimizingProcesses(Context ctx) { type = null; } + String processCategory = ctx.queryParam("processCategory"); + if (StringUtils.isBlank(processCategory)) { + processCategory = null; + } + String status = ctx.queryParam("status"); Integer page = ctx.queryParamAsClass("page", Integer.class).getOrDefault(1); Integer pageSize = ctx.queryParamAsClass("pageSize", Integer.class).getOrDefault(20); @@ -346,7 +351,12 @@ public void getOptimizingProcesses(Context ctx) { StringUtils.isBlank(status) ? null : ProcessStatus.valueOf(status); Pair, Integer> optimizingProcessesInfo = tableDescriptor.getOptimizingProcessesInfo( - tableIdentifier.buildTableIdentifier(), type, processStatus, limit, offset); + tableIdentifier.buildTableIdentifier(), + type, + processCategory, + processStatus, + limit, + offset); List result = optimizingProcessesInfo.getLeft(); int total = optimizingProcessesInfo.getRight(); @@ -364,6 +374,17 @@ public void getOptimizingTypes(Context ctx) { ctx.json(OkResponse.of(values)); } + public void getMaintenanceTypes(Context ctx) { + String catalog = ctx.pathParam("catalog"); + String db = ctx.pathParam("db"); + String table = ctx.pathParam("table"); + TableIdentifier tableIdentifier = TableIdentifier.of(catalog, db, table); + + Map values = + tableDescriptor.getTableMaintenanceTypes(tableIdentifier.buildTableIdentifier()); + ctx.json(OkResponse.of(values)); + } + /** * Get tasks of optimizing process. * diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java b/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java index bbf4019c7d..06a70edc3b 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java @@ -131,6 +131,8 @@ void updateProcess( + "create_time, finish_time, fail_message, process_parameters, summary " + "FROM table_process WHERE table_id = #{tableId} " + " AND process_type = #{processType}" + + " AND process_type IN #{type}" + + " AND process_type NOT IN #{type}" + " AND status = #{status}" + " ORDER BY process_id desc" + "") @@ -138,6 +140,8 @@ void updateProcess( List listProcessMeta( @Param("tableId") long tableId, @Param("processType") String processType, + @Param("includeTypes") List includeTypes, + @Param("excludeTypes") List excludeTypes, @Param("status") ProcessStatus optimizingStatus); @Select( diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java index 4b9d6b296e..1ec08380e2 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java @@ -301,21 +301,22 @@ public void testOptimizingProcess() { doReturn(tableIdentifier).when(table).id(); Pair, Integer> res = - descriptor.getOptimizingProcessesInfo(table, null, null, 4, 4); + descriptor.getOptimizingProcessesInfo(table, null, null, null, 4, 4); Integer expectReturnItemSizeForNoTypeNoStatusOffset0Limit5 = 4; Integer expectTotalForNoTypeNoStatusOffset0Limit5 = 10; Assert.assertEquals( expectReturnItemSizeForNoTypeNoStatusOffset0Limit5, (Integer) res.getLeft().size()); Assert.assertEquals(expectTotalForNoTypeNoStatusOffset0Limit5, res.getRight()); - res = descriptor.getOptimizingProcessesInfo(table, null, ProcessStatus.SUCCESS, 5, 0); + res = descriptor.getOptimizingProcessesInfo(table, null, null, ProcessStatus.SUCCESS, 5, 0); Integer expectReturnItemSizeForOnlyStatusOffset0limit5 = 5; Integer expectedTotalForOnlyStatusOffset0Limit5 = 7; Assert.assertEquals( expectReturnItemSizeForOnlyStatusOffset0limit5, (Integer) res.getLeft().size()); Assert.assertEquals(expectedTotalForOnlyStatusOffset0Limit5, res.getRight()); - res = descriptor.getOptimizingProcessesInfo(table, OptimizingType.MINOR.name(), null, 5, 0); + res = + descriptor.getOptimizingProcessesInfo(table, OptimizingType.MINOR.name(), null, null, 5, 0); Integer expectedRetItemsSizeForOnlyTypeOffset0Limit5 = 4; Integer expectedRetTotalForOnlyTypeOffset0Limit5 = 4; Assert.assertEquals( @@ -324,7 +325,7 @@ public void testOptimizingProcess() { res = descriptor.getOptimizingProcessesInfo( - table, OptimizingType.MINOR.name(), ProcessStatus.SUCCESS, 2, 2); + table, OptimizingType.MINOR.name(), null, ProcessStatus.SUCCESS, 2, 2); Integer expectedRetItemSizeForBothTypeAndStatusOffset2Limit2 = 2; Integer expectedRetTotalForBothTypeAndStatusOffset2Limit2 = 4; Assert.assertEquals( diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java index 06ae381a59..b9ff95f1a7 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java @@ -124,7 +124,8 @@ protected TableProcessMeta waitOptimizeResult() { List tableOptimizingProcesses = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), null, null)); + mapper -> + mapper.listProcessMeta(identifier.getId(), null, null, null, null)); if (tableOptimizingProcesses == null || tableOptimizingProcesses.isEmpty()) { LOG.info("optimize history is empty"); return Status.RUNNING; @@ -157,7 +158,7 @@ protected TableProcessMeta waitOptimizeResult() { List result = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), null, null)) + mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null, null)) .stream() .filter(p -> p.getProcessId() > lastProcessId) .filter(p -> p.getStatus().equals(ProcessStatus.SUCCESS)) @@ -190,7 +191,7 @@ protected void assertOptimizeHangUp() { List tableOptimizingProcesses = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), null, null)) + mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null, null)) .stream() .filter(p -> p.getProcessId() > lastProcessId) .collect(Collectors.toList()); diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java b/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java index bd251b02dd..29359f2453 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java @@ -184,7 +184,9 @@ public void insertProcess(long tableId, long processId, ProcessStatus status, lo } public List listProcesses(long tableId) { - return getAs(TableProcessMapper.class, mapper -> mapper.listProcessMeta(tableId, null, null)); + return getAs( + TableProcessMapper.class, + mapper -> mapper.listProcessMeta(tableId, null, null, null, null)); } public void cleanAll(long tableId) { diff --git a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java index 1e8bc62c2c..439a8e835e 100644 --- a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java +++ b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java @@ -23,8 +23,10 @@ import org.apache.amoro.process.ProcessStatus; import org.apache.commons.lang3.tuple.Pair; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutorService; /** API for obtaining metadata information of various formats. */ @@ -63,11 +65,37 @@ List getTableFiles( /** Get the paged optimizing process information of the {@link AmoroTable} and total size. */ Pair, Integer> getOptimizingProcessesInfo( - AmoroTable amoroTable, String type, ProcessStatus status, int limit, int offset); + AmoroTable amoroTable, + String type, + String processCategory, + ProcessStatus status, + int limit, + int offset); /** Return the optimizing types of the {@link AmoroTable} is supported. */ Map getTableOptimizingTypes(AmoroTable amoroTable); + /** Return the maintenance types of the {@link AmoroTable} is supported. */ + default Map getTableMaintenanceTypes(AmoroTable amoroTable) { + return Collections.emptyMap(); + } + + static boolean matchProcessCategory( + String processCategory, Set optimizingTypes, String type) { + if (processCategory == null) { + return true; + } + boolean isOptimizingType = + type != null && optimizingTypes.stream().anyMatch(t -> t.equalsIgnoreCase(type)); + if ("OPTIMIZING".equalsIgnoreCase(processCategory)) { + return isOptimizingType; + } + if ("MAINTENANCE".equalsIgnoreCase(processCategory)) { + return !isOptimizingType; + } + return true; + } + /** Get the paged optimizing process tasks information of the {@link AmoroTable}. */ List getOptimizingTaskInfos(AmoroTable amoroTable, String processId); diff --git a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java index 04d089233a..973bf9b4fc 100644 --- a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java +++ b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java @@ -97,6 +97,7 @@ public class HudiTableDescriptor implements FormatTableDescriptor { private static final Logger LOG = LoggerFactory.getLogger(HudiTableDescriptor.class); private static final String COMPACTION = "compaction"; private static final String CLUSTERING = "clustering"; + private static final Set OPTIMIZING_TYPES = Sets.newHashSet(COMPACTION, CLUSTERING); // table comment private static final String COMMENT = "comment"; @@ -340,7 +341,12 @@ private Stream fileSliceToFileStream(String partition, Fi @Override public Pair, Integer> getOptimizingProcessesInfo( - AmoroTable amoroTable, String type, ProcessStatus status, int limit, int offset) { + AmoroTable amoroTable, + String type, + String processCategory, + ProcessStatus status, + int limit, + int offset) { HoodieJavaTable hoodieTable = (HoodieJavaTable) amoroTable.originalTable(); HoodieDefaultTimeline timeline = new HoodieActiveTimeline(hoodieTable.getMetaClient(), false); List instants = timeline.getInstants(); @@ -387,6 +393,10 @@ public Pair, Integer> getOptimizingProcessesInfo( i -> StringUtils.isNullOrEmpty(type) || type.equalsIgnoreCase(i.getOptimizingType())) .filter(i -> status == null || status == i.getStatus()) + .filter( + i -> + FormatTableDescriptor.matchProcessCategory( + processCategory, OPTIMIZING_TYPES, i.getOptimizingType())) .collect(Collectors.toList()); int total = infos.size(); infos = infos.stream().skip(offset).limit(limit).collect(Collectors.toList()); diff --git a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java index 566da688ab..eeec52c719 100644 --- a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java +++ b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java @@ -26,6 +26,7 @@ import org.apache.amoro.process.ProcessStatus; import org.apache.amoro.shade.guava32.com.google.common.collect.Lists; import org.apache.amoro.shade.guava32.com.google.common.collect.Maps; +import org.apache.amoro.shade.guava32.com.google.common.collect.Sets; import org.apache.amoro.shade.guava32.com.google.common.collect.Streams; import org.apache.amoro.table.TableIdentifier; import org.apache.amoro.table.descriptor.AMSColumnInfo; @@ -89,6 +90,7 @@ public class PaimonTableDescriptor implements FormatTableDescriptor { public static final String PAIMON_MAIN_BRANCH_NAME = "main"; + private static final Set OPTIMIZING_TYPES = Sets.newHashSet("FULL", "MINOR"); private ExecutorService executor; @@ -372,7 +374,12 @@ public List getTableFiles( @Override public Pair, Integer> getOptimizingProcessesInfo( - AmoroTable amoroTable, String type, ProcessStatus status, int limit, int offset) { + AmoroTable amoroTable, + String type, + String processCategory, + ProcessStatus status, + int limit, + int offset) { // Temporary solution for Paimon. TODO: Get compaction info from Paimon compaction task List processInfoList; TableIdentifier tableIdentifier = amoroTable.id(); @@ -449,6 +456,10 @@ public Pair, Integer> getOptimizingProcessesInfo( processInfoList.stream() .filter(p -> StringUtils.isBlank(type) || type.equalsIgnoreCase(p.getOptimizingType())) .filter(p -> status == null || status == p.getStatus()) + .filter( + p -> + FormatTableDescriptor.matchProcessCategory( + processCategory, OPTIMIZING_TYPES, p.getOptimizingType())) .collect(Collectors.toList()); int total = processInfoList.size(); processInfoList = diff --git a/amoro-web/src/language/en.ts b/amoro-web/src/language/en.ts index d28c05cfdc..11d605dbca 100644 --- a/amoro-web/src/language/en.ts +++ b/amoro-web/src/language/en.ts @@ -28,6 +28,7 @@ export default { catalog: 'Catalog', tables: 'Tables', optimizing: 'Optimizing', + maintenance: 'Maintenance', terminal: 'Terminal', settings: 'Settings', systemSetting: 'System Settings', @@ -124,6 +125,8 @@ export default { resourceGroup: 'Resource Group', releaseOptModalTitle: 'Release this optimizer?', cancelOptimizingProcessOptModalTitle: 'Cancel the optimizing process of this table?', + cancelMaintenanceProcessOptModalTitle: 'Cancel the maintenance process of this table?', + loadProcessTypesFailed: 'Failed to load process types', welcomeTip: 'Welcome to Amoro!', signIn: 'Sign in', username: 'Username', diff --git a/amoro-web/src/language/zh.ts b/amoro-web/src/language/zh.ts index 33087d9b18..72e3c663c3 100644 --- a/amoro-web/src/language/zh.ts +++ b/amoro-web/src/language/zh.ts @@ -28,6 +28,7 @@ export default { catalog: '目录', tables: '表', optimizing: '优化', + maintenance: '维护', terminal: '终端', settings: '设置', systemSetting: '系统设置', @@ -124,6 +125,8 @@ export default { resourceGroup: '资源组', releaseOptModalTitle: '释放此优化器?', cancelOptimizingProcessOptModalTitle: '取消该表的优化过程?', + cancelMaintenanceProcessOptModalTitle: '取消该表的维护过程?', + loadProcessTypesFailed: '加载流程类型失败', welcomeTip: '欢迎访问 Amoro!', signIn: '登入', username: '用户名', diff --git a/amoro-web/src/services/table.service.ts b/amoro-web/src/services/table.service.ts index 08f046ccac..83d7dc8c4f 100644 --- a/amoro-web/src/services/table.service.ts +++ b/amoro-web/src/services/table.service.ts @@ -158,14 +158,15 @@ export function getOptimizingProcesses( db: string table: string type: string + processCategory?: string status: string page: number pageSize: number token?: string }, ) { - const { catalog, db, table, type, status, page, pageSize, token } = params - return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/optimizing-processes`, { params: { page, pageSize, token, type, status } }) + const { catalog, db, table, type, processCategory, status, page, pageSize, token } = params + return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/optimizing-processes`, { params: { page, pageSize, token, type, processCategory, status } }) } // get optimizing process types @@ -181,6 +182,19 @@ export function getTableOptimizingTypes( return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/optimizing-types`, { params: { token } }) } +// get maintenance process types +export function getTableMaintenanceTypes( + params: { + catalog: string + db: string + table: string + token?: string + }, +) { + const { catalog, db, table, token } = params + return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/maintenance-types`, { params: { token } }) +} + // get optimizing tasks export function getTasksByOptimizingProcessId( params: { diff --git a/amoro-web/src/views/tables/components/Maintenance.vue b/amoro-web/src/views/tables/components/Maintenance.vue new file mode 100644 index 0000000000..a0a955de77 --- /dev/null +++ b/amoro-web/src/views/tables/components/Maintenance.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/amoro-web/src/views/tables/components/Optimizing.vue b/amoro-web/src/views/tables/components/Optimizing.vue index c7ccc0b506..1cfa44ea5f 100644 --- a/amoro-web/src/views/tables/components/Optimizing.vue +++ b/amoro-web/src/views/tables/components/Optimizing.vue @@ -20,13 +20,21 @@ limitations under the License. import { onMounted, reactive, ref, shallowReactive } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute } from 'vue-router' -import { Modal } from 'ant-design-vue' +import { Modal, message } from 'ant-design-vue' import { usePagination } from '@/hooks/usePagination' import type { BreadcrumbOptimizingItem, IColumns, ILableAndValue } from '@/types/common.type' -import { cancelOptimizingProcess, getOptimizingProcesses, getTableOptimizingTypes, getTasksByOptimizingProcessId } from '@/services/table.service' +import { cancelOptimizingProcess, getOptimizingProcesses, getTableOptimizingTypes, getTableMaintenanceTypes, getTasksByOptimizingProcessId } from '@/services/table.service' import { bytesToSize, dateFormat, formatMS2Time } from '@/utils/index' import { canManageTable } from '@/utils/permission' +const props = withDefaults(defineProps<{ + processCategory?: string + cancelModalTitleKey?: string +}>(), { + processCategory: 'OPTIMIZING', + cancelModalTitleKey: 'cancelOptimizingProcessOptModalTitle', +}) + const hasBreadcrumb = ref(false) const statusMap = { @@ -95,12 +103,19 @@ const statusType = ref() const statusTypeList = ref([]) async function getQueryDataDictList() { - const tableProcessTypes = await getTableOptimizingTypes({ ...sourceData }) - const typesList = Object.entries(tableProcessTypes).map(([typeName, displayName]) => ({ label: displayName as string, value: typeName })) const status = Object.entries(statusMap).map(([key, value]) => ({ label: value.title, value: key })) - - actionTypeList.value = typesList statusTypeList.value = status + + try { + const fetchFn = props.processCategory === 'MAINTENANCE' ? getTableMaintenanceTypes : getTableOptimizingTypes + const tableProcessTypes = await fetchFn({ ...sourceData }) + actionTypeList.value = Object.entries(tableProcessTypes).map(([typeName, displayName]) => ({ label: displayName as string, value: typeName })) + } + catch (error) { + console.error('Failed to load process types:', error) + message.error(t('loadProcessTypesFailed')) + actionTypeList.value = [] + } } async function refreshOptimizingProcesses() { @@ -109,14 +124,15 @@ async function refreshOptimizingProcesses() { dataSource.length = 0 const result = await getOptimizingProcesses({ ...sourceData, - type: actionType.value || '', - status: statusType.value || '', + type: String(actionType.value || ''), + processCategory: props.processCategory, + status: String(statusType.value || ''), page: pagination.current, pageSize: pagination.pageSize, - } as any) + }) const { list, total = 0 } = result pagination.total = total - dataSource.push(...[...list || []].map((item) => { + dataSource.push(...[...list || []].map((item: any) => { const { inputFiles = {}, outputFiles = {} } = item return { ...item, @@ -142,7 +158,7 @@ async function refreshOptimizingProcesses() { async function cancel() { Modal.confirm({ - title: t('cancelOptimizingProcessOptModalTitle'), + title: t(props.cancelModalTitleKey), onOk: async () => { try { loading.value = true @@ -421,10 +437,6 @@ onMounted(() => { padding: 4px 16px !important; } - :deep(.ant-table-thead > tr > th) { - padding: 4px 16px !important; - } - :deep(.ant-table-row-expand-icon) { border-radius: 0 !important; } diff --git a/amoro-web/src/views/tables/index.vue b/amoro-web/src/views/tables/index.vue index f3704dbe23..4dcc8be048 100644 --- a/amoro-web/src/views/tables/index.vue +++ b/amoro-web/src/views/tables/index.vue @@ -24,6 +24,7 @@ import UFiles from './components/Files.vue' import UOperations from './components/Operations.vue' import USnapshots from './components/Snapshots.vue' import UOptimizing from './components/Optimizing.vue' +import UMaintenance from './components/Maintenance.vue' import UHealthScore from './components/HealthScoreDetails.vue' import TableExplorer from './components/TableExplorer.vue' import useStore from '@/store/index' @@ -38,6 +39,7 @@ export default defineComponent({ UOperations, USnapshots, UOptimizing, + UMaintenance, UHealthScore, TableExplorer, }, @@ -106,6 +108,7 @@ export default defineComponent({ const tabConfigs = shallowReactive([ { key: 'Snapshots', label: 'snapshots' }, { key: 'Optimizing', label: 'optimizing' }, + { key: 'Maintenance', label: 'maintenance' }, { key: 'Operations', label: 'operations' }, ]) From 127170357b972c980e39fd4b1a35f431c6960b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=87=E9=A2=86?= Date: Mon, 1 Jun 2026 10:18:08 +0800 Subject: [PATCH 2/5] fix optimizing types in PaimonTableDescriptor --- .../amoro/formats/paimon/PaimonTableDescriptor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java index eeec52c719..6b98102292 100644 --- a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java +++ b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java @@ -90,7 +90,9 @@ public class PaimonTableDescriptor implements FormatTableDescriptor { public static final String PAIMON_MAIN_BRANCH_NAME = "main"; - private static final Set OPTIMIZING_TYPES = Sets.newHashSet("FULL", "MINOR"); + private static final String FULL_TYPE = "FULL"; + private static final String MINOR_TYPE = "MINOR"; + private static final Set OPTIMIZING_TYPES = Sets.newHashSet(FULL_TYPE, MINOR_TYPE); private ExecutorService executor; @@ -435,9 +437,9 @@ public Pair, Integer> getOptimizingProcessesInfo( } } if (isPrimaryTable && hasMaxLevels) { - optimizingProcessInfo.setOptimizingType("FULL"); + optimizingProcessInfo.setOptimizingType(FULL_TYPE); } else { - optimizingProcessInfo.setOptimizingType("MINOR"); + optimizingProcessInfo.setOptimizingType(MINOR_TYPE); } optimizingProcessInfo.setSuccessTasks(buckets.size()); optimizingProcessInfo.setTotalTasks(buckets.size()); @@ -470,8 +472,8 @@ public Pair, Integer> getOptimizingProcessesInfo( @Override public Map getTableOptimizingTypes(AmoroTable amoroTable) { Map types = Maps.newHashMap(); - types.put("FULL", "full"); - types.put("MINOR", "MINOR"); + types.put(FULL_TYPE, "full"); + types.put(MINOR_TYPE, "MINOR"); return types; } From c213e2ac5e32001c64b2cd6c272ba0816ff095ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=87=E9=A2=86?= Date: Fri, 5 Jun 2026 10:42:45 +0800 Subject: [PATCH 3/5] Refactor OPTIMIZING tab from OPTIMIZING to OPTIMIZING/CLEANUP/PROFILING tab --- .../server/dashboard/DashboardServer.java | 7 +- .../MixedAndIcebergTableDescriptor.java | 69 +++++++---- .../dashboard/ServerTableDescriptor.java | 11 +- .../dashboard/controller/TableController.java | 24 ++-- .../mapper/TableProcessMapper.java | 2 - .../TestIcebergServerTableDescriptor.java | 116 +++++++++++++++++- .../optimizing/BaseOptimizingChecker.java | 7 +- .../TestProcessDataExpiringExecutor.java | 3 +- .../descriptor/FormatTableDescriptor.java | 49 ++++++-- .../table/descriptor/ProcessCategory.java | 55 +++++++++ .../formats/hudi/HudiTableDescriptor.java | 8 +- .../formats/paimon/PaimonTableDescriptor.java | 8 +- amoro-web/mock/modules/table.js | 33 +++-- amoro-web/src/language/en.ts | 6 +- amoro-web/src/language/zh.ts | 6 +- amoro-web/src/services/table.service.ts | 22 +--- .../{Maintenance.vue => Cleanup.vue} | 2 +- .../views/tables/components/Optimizing.vue | 7 +- .../src/views/tables/components/Profiling.vue | 26 ++++ amoro-web/src/views/tables/index.vue | 9 +- 20 files changed, 351 insertions(+), 119 deletions(-) create mode 100644 amoro-common/src/main/java/org/apache/amoro/table/descriptor/ProcessCategory.java rename amoro-web/src/views/tables/components/{Maintenance.vue => Cleanup.vue} (88%) create mode 100644 amoro-web/src/views/tables/components/Profiling.vue diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java index 4cf8850ff7..f6e1ab29de 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/DashboardServer.java @@ -268,11 +268,8 @@ private EndpointGroup apiGroup() { "/catalogs/{catalog}/dbs/{db}/tables/{table}/optimizing-processes", tableController::getOptimizingProcesses); get( - "/catalogs/{catalog}/dbs/{db}/tables/{table}/optimizing-types", - tableController::getOptimizingTypes); - get( - "/catalogs/{catalog}/dbs/{db}/tables/{table}/maintenance-types", - tableController::getMaintenanceTypes); + "/catalogs/{catalog}/dbs/{db}/tables/{table}/process-types", + tableController::getProcessTypes); get( "/catalogs/{catalog}/dbs/{db}/tables/{table}/optimizing-processes/{processId}/tasks", tableController::getOptimizingProcessTasks); diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java index 6570c1a468..140e30cf3f 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java @@ -68,6 +68,7 @@ import org.apache.amoro.table.descriptor.OptimizingTaskInfo; import org.apache.amoro.table.descriptor.PartitionBaseInfo; import org.apache.amoro.table.descriptor.PartitionFileBaseInfo; +import org.apache.amoro.table.descriptor.ProcessCategory; import org.apache.amoro.table.descriptor.ServerTableMeta; import org.apache.amoro.table.descriptor.TableSummary; import org.apache.amoro.table.descriptor.TagOrBranchInfo; @@ -123,8 +124,15 @@ public class MixedAndIcebergTableDescriptor extends PersistentBase private static final List OPTIMIZING_TYPE_LIST = Arrays.stream(OptimizingType.values()).map(Enum::name).collect(Collectors.toList()); - private static final String OPTIMIZING = "OPTIMIZING"; - private static final String MAINTENANCE = "MAINTENANCE"; + private static final List CLEANUP_TYPE_LIST = + ImmutableList.of( + IcebergActions.EXPIRE_SNAPSHOTS.getName(), + IcebergActions.CLEAN_ORPHAN.getName(), + IcebergActions.CLEAN_DANGLING_DELETE.getName(), + IcebergActions.EXPIRE_DATA.getName()); + + private static final List PROFILING_TYPE_LIST = + ImmutableList.of(IcebergActions.AUTO_CREATE_TAGS.getName()); private ExecutorService executorService; @@ -687,22 +695,24 @@ public Pair, Integer> getOptimizingProcessesInfo( // Only apply category filtering when type is not specified final List includeTypes; - final List excludeTypes; if (StringUtils.isBlank(type)) { - if (OPTIMIZING.equalsIgnoreCase(processCategory)) { - includeTypes = OPTIMIZING_TYPE_LIST; - excludeTypes = null; - } else if (MAINTENANCE.equalsIgnoreCase(processCategory)) { - includeTypes = null; - excludeTypes = OPTIMIZING_TYPE_LIST; + if (processCategory == null) { + // No category specified: return empty results + return Pair.of(Collections.emptyList(), 0); } else { - includeTypes = null; - excludeTypes = null; + List categoryTypes = + FormatTableDescriptor.resolveCategoryTypes( + processCategory, OPTIMIZING_TYPE_LIST, CLEANUP_TYPE_LIST, PROFILING_TYPE_LIST); + if (categoryTypes.isEmpty()) { + // Unknown category: return empty results to avoid exposing all processes + return Pair.of(Collections.emptyList(), 0); + } + + includeTypes = categoryTypes; } } else { includeTypes = null; - excludeTypes = null; } List processMetaList = Collections.emptyList(); @@ -710,9 +720,7 @@ public Pair, Integer> getOptimizingProcessesInfo( processMetaList = getAs( TableProcessMapper.class, - mapper -> - mapper.listProcessMeta( - identifier.getId(), type, includeTypes, excludeTypes, status)); + mapper -> mapper.listProcessMeta(identifier.getId(), type, includeTypes, status)); PageInfo pageInfo = new PageInfo<>(processMetaList); total = (int) pageInfo.getTotal(); LOG.info( @@ -753,15 +761,28 @@ public Map getTableOptimizingTypes(AmoroTable amoroTable) { } @Override - public Map getTableMaintenanceTypes(AmoroTable amoroTable) { - Map types = Maps.newHashMap(); - types.put(IcebergActions.EXPIRE_SNAPSHOTS.getName(), "Expire Snapshots"); - types.put(IcebergActions.CLEAN_ORPHAN.getName(), "Clean Orphan Files"); - types.put(IcebergActions.CLEAN_DANGLING_DELETE.getName(), "Clean Dangling Delete Files"); - types.put(IcebergActions.EXPIRE_DATA.getName(), "Expire Data"); - types.put(IcebergActions.SYNC_HIVE_TABLES.getName(), "Sync Hive Tables"); - types.put(IcebergActions.AUTO_CREATE_TAGS.getName(), "Auto Create Tags"); - return types; + public Map getTableProcessTypes( + AmoroTable amoroTable, String processCategory) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return getTableOptimizingTypes(amoroTable); + } + + if (ProcessCategory.CLEANUP.getName().equalsIgnoreCase(processCategory)) { + Map types = Maps.newHashMap(); + types.put(IcebergActions.EXPIRE_SNAPSHOTS.getName(), "Expire Snapshots"); + types.put(IcebergActions.CLEAN_ORPHAN.getName(), "Clean Orphan Files"); + types.put(IcebergActions.CLEAN_DANGLING_DELETE.getName(), "Clean Dangling Delete Files"); + types.put(IcebergActions.EXPIRE_DATA.getName(), "Expire Data"); + return types; + } + + if (ProcessCategory.PROFILING.getName().equalsIgnoreCase(processCategory)) { + Map types = Maps.newHashMap(); + types.put(IcebergActions.AUTO_CREATE_TAGS.getName(), "Auto Create Tags"); + return types; + } + + return Collections.emptyMap(); } @Override diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java index 1168a06c48..bbd68bf691 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/ServerTableDescriptor.java @@ -148,16 +148,11 @@ public List getOptimizingProcessTaskInfos( return formatTableDescriptor.getOptimizingTaskInfos(amoroTable, processId); } - public Map getTableOptimizingTypes(TableIdentifier tableIdentifier) { + public Map getTableProcessTypes( + TableIdentifier tableIdentifier, String processCategory) { AmoroTable amoroTable = loadTable(tableIdentifier); FormatTableDescriptor formatTableDescriptor = formatDescriptorMap.get(amoroTable.format()); - return formatTableDescriptor.getTableOptimizingTypes(amoroTable); - } - - public Map getTableMaintenanceTypes(TableIdentifier tableIdentifier) { - AmoroTable amoroTable = loadTable(tableIdentifier); - FormatTableDescriptor formatTableDescriptor = formatDescriptorMap.get(amoroTable.format()); - return formatTableDescriptor.getTableMaintenanceTypes(amoroTable); + return formatTableDescriptor.getTableProcessTypes(amoroTable, processCategory); } private AmoroTable loadTable(TableIdentifier identifier) { diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java index 1eb98c05c2..81d29a4254 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/controller/TableController.java @@ -71,6 +71,7 @@ import org.apache.amoro.table.descriptor.OptimizingTaskInfo; import org.apache.amoro.table.descriptor.PartitionBaseInfo; import org.apache.amoro.table.descriptor.PartitionFileBaseInfo; +import org.apache.amoro.table.descriptor.ProcessCategory; import org.apache.amoro.table.descriptor.ServerTableMeta; import org.apache.amoro.table.descriptor.TableSummary; import org.apache.amoro.table.descriptor.TagOrBranchInfo; @@ -363,25 +364,20 @@ public void getOptimizingProcesses(Context ctx) { ctx.json(OkResponse.of(PageResult.of(result, total))); } - public void getOptimizingTypes(Context ctx) { - String catalog = ctx.pathParam("catalog"); - String db = ctx.pathParam("db"); - String table = ctx.pathParam("table"); - TableIdentifier tableIdentifier = TableIdentifier.of(catalog, db, table); - - Map values = - tableDescriptor.getTableOptimizingTypes(tableIdentifier.buildTableIdentifier()); - ctx.json(OkResponse.of(values)); - } - - public void getMaintenanceTypes(Context ctx) { + public void getProcessTypes(Context ctx) { String catalog = ctx.pathParam("catalog"); String db = ctx.pathParam("db"); String table = ctx.pathParam("table"); + String processCategory = + ctx.queryParamAsClass("processCategory", String.class).getOrDefault(null); + if (StringUtils.isBlank(processCategory)) { + processCategory = ProcessCategory.OPTIMIZING.getName(); + } TableIdentifier tableIdentifier = TableIdentifier.of(catalog, db, table); Map values = - tableDescriptor.getTableMaintenanceTypes(tableIdentifier.buildTableIdentifier()); + tableDescriptor.getTableProcessTypes( + tableIdentifier.buildTableIdentifier(), processCategory); ctx.json(OkResponse.of(values)); } @@ -692,7 +688,7 @@ public void getTableConsumerInfos(Context ctx) { } /** - * cancel the running optimizing process of one certain table. + * Cancel the running process of one certain table. * * @param ctx - context for handling the request and response */ diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java b/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java index 06a70edc3b..ba21e01ff6 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/persistence/mapper/TableProcessMapper.java @@ -132,7 +132,6 @@ void updateProcess( + "FROM table_process WHERE table_id = #{tableId} " + " AND process_type = #{processType}" + " AND process_type IN #{type}" - + " AND process_type NOT IN #{type}" + " AND status = #{status}" + " ORDER BY process_id desc" + "") @@ -141,7 +140,6 @@ List listProcessMeta( @Param("tableId") long tableId, @Param("processType") String processType, @Param("includeTypes") List includeTypes, - @Param("excludeTypes") List excludeTypes, @Param("status") ProcessStatus optimizingStatus); @Select( diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java index 1ec08380e2..cf2a856fe9 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/dashboard/TestIcebergServerTableDescriptor.java @@ -21,7 +21,9 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import org.apache.amoro.Action; import org.apache.amoro.AmoroTable; +import org.apache.amoro.IcebergActions; import org.apache.amoro.ServerTableIdentifier; import org.apache.amoro.TableFormat; import org.apache.amoro.formats.AmoroCatalogTestHelper; @@ -301,14 +303,16 @@ public void testOptimizingProcess() { doReturn(tableIdentifier).when(table).id(); Pair, Integer> res = - descriptor.getOptimizingProcessesInfo(table, null, null, null, 4, 4); + descriptor.getOptimizingProcessesInfo(table, null, "OPTIMIZING", null, 4, 4); Integer expectReturnItemSizeForNoTypeNoStatusOffset0Limit5 = 4; Integer expectTotalForNoTypeNoStatusOffset0Limit5 = 10; Assert.assertEquals( expectReturnItemSizeForNoTypeNoStatusOffset0Limit5, (Integer) res.getLeft().size()); Assert.assertEquals(expectTotalForNoTypeNoStatusOffset0Limit5, res.getRight()); - res = descriptor.getOptimizingProcessesInfo(table, null, null, ProcessStatus.SUCCESS, 5, 0); + res = + descriptor.getOptimizingProcessesInfo( + table, null, "OPTIMIZING", ProcessStatus.SUCCESS, 5, 0); Integer expectReturnItemSizeForOnlyStatusOffset0limit5 = 5; Integer expectedTotalForOnlyStatusOffset0Limit5 = 7; Assert.assertEquals( @@ -316,7 +320,8 @@ public void testOptimizingProcess() { Assert.assertEquals(expectedTotalForOnlyStatusOffset0Limit5, res.getRight()); res = - descriptor.getOptimizingProcessesInfo(table, OptimizingType.MINOR.name(), null, null, 5, 0); + descriptor.getOptimizingProcessesInfo( + table, OptimizingType.MINOR.name(), "OPTIMIZING", null, 5, 0); Integer expectedRetItemsSizeForOnlyTypeOffset0Limit5 = 4; Integer expectedRetTotalForOnlyTypeOffset0Limit5 = 4; Assert.assertEquals( @@ -325,7 +330,7 @@ public void testOptimizingProcess() { res = descriptor.getOptimizingProcessesInfo( - table, OptimizingType.MINOR.name(), null, ProcessStatus.SUCCESS, 2, 2); + table, OptimizingType.MINOR.name(), "OPTIMIZING", ProcessStatus.SUCCESS, 2, 2); Integer expectedRetItemSizeForBothTypeAndStatusOffset2Limit2 = 2; Integer expectedRetTotalForBothTypeAndStatusOffset2Limit2 = 4; Assert.assertEquals( @@ -333,6 +338,85 @@ public void testOptimizingProcess() { Assert.assertEquals(expectedRetTotalForBothTypeAndStatusOffset2Limit2, res.getRight()); } + @Test + public void testProcessCategoryFiltering() { + TestMixedAndIcebergTableDescriptor descriptor = new TestMixedAndIcebergTableDescriptor(); + + String catalogName = "catalog1"; + String dbName = "db1"; + String tableName = "table1"; + + ServerTableIdentifier identifier = + ServerTableIdentifier.of(1L, catalogName, dbName, tableName, TableFormat.ICEBERG); + descriptor.insertTable(identifier); + MetricsSummary dummySummary = new MetricsSummary(); + dummySummary.setNewDeleteFileCnt(1); + dummySummary.setNewDataFileCnt(1); + dummySummary.setNewDataSize(1); + dummySummary.setNewDeleteSize(1); + + // Insert OPTIMIZING processes: MAJOR, MINOR + descriptor.insertOptimizingProcess( + identifier, + 1L, + 1, + 1, + ProcessStatus.SUCCESS, + OptimizingType.MAJOR, + 1L, + dummySummary, + Collections.emptyMap(), + Collections.emptyMap()); + descriptor.insertOptimizingProcess( + identifier, + 2L, + 2L, + 2L, + ProcessStatus.SUCCESS, + OptimizingType.MINOR, + 2L, + dummySummary, + Collections.emptyMap(), + Collections.emptyMap()); + + // Insert CLEANUP processes: EXPIRE_SNAPSHOTS, CLEAN_ORPHAN + descriptor.insertIcebergActionProcess( + identifier, 3L, ProcessStatus.SUCCESS, IcebergActions.EXPIRE_SNAPSHOTS, 3L, dummySummary); + descriptor.insertIcebergActionProcess( + identifier, 4L, ProcessStatus.SUCCESS, IcebergActions.CLEAN_ORPHAN, 4L, dummySummary); + + // Insert PROFILING process: AUTO_CREATE_TAGS + descriptor.insertIcebergActionProcess( + identifier, 5L, ProcessStatus.SUCCESS, IcebergActions.AUTO_CREATE_TAGS, 5L, dummySummary); + + AmoroTable table = mock(IcebergTable.class); + TableIdentifier tableIdentifier = + TableIdentifier.of( + identifier.getCatalog(), identifier.getDatabase(), identifier.getTableName()); + doReturn(tableIdentifier).when(table).id(); + + // Test OPTIMIZING category: should return only MAJOR and MINOR + Pair, Integer> res = + descriptor.getOptimizingProcessesInfo(table, null, "OPTIMIZING", null, 10, 0); + Assert.assertEquals(2, (int) res.getRight()); + Assert.assertEquals(2, res.getLeft().size()); + + // Test CLEANUP category: should return EXPIRE_SNAPSHOTS and CLEAN_ORPHAN + res = descriptor.getOptimizingProcessesInfo(table, null, "CLEANUP", null, 10, 0); + Assert.assertEquals(2, (int) res.getRight()); + Assert.assertEquals(2, res.getLeft().size()); + + // Test PROFILING category: should return only AUTO_CREATE_TAGS + res = descriptor.getOptimizingProcessesInfo(table, null, "PROFILING", null, 10, 0); + Assert.assertEquals(1, (int) res.getRight()); + Assert.assertEquals(1, res.getLeft().size()); + + // Test null category: should return empty results + res = descriptor.getOptimizingProcessesInfo(table, null, null, null, 10, 0); + Assert.assertEquals(0, (int) res.getRight()); + Assert.assertEquals(0, res.getLeft().size()); + } + @Override protected void tableOperationsRenameColumns() { getTable().updateSchema().renameColumn("new_col", "renamed_col").commit(); @@ -419,5 +503,29 @@ public void insertOptimizingProcess( fromSequence, toSequence)); } + + public void insertIcebergActionProcess( + ServerTableIdentifier identifier, + long processId, + ProcessStatus status, + Action action, + long planTime, + MetricsSummary summary) { + doAs( + TableProcessMapper.class, + mapper -> + mapper.insertProcess( + identifier.getId(), + processId, + "", + status, + action.getName(), + action.getName(), + "AMORO", + 0, + planTime, + new HashMap<>(), + summary.summaryAsMap(false))); + } } } diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java index b9ff95f1a7..d440b9e54e 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/optimizing/BaseOptimizingChecker.java @@ -124,8 +124,7 @@ protected TableProcessMeta waitOptimizeResult() { List tableOptimizingProcesses = getAs( TableProcessMapper.class, - mapper -> - mapper.listProcessMeta(identifier.getId(), null, null, null, null)); + mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null)); if (tableOptimizingProcesses == null || tableOptimizingProcesses.isEmpty()) { LOG.info("optimize history is empty"); return Status.RUNNING; @@ -158,7 +157,7 @@ protected TableProcessMeta waitOptimizeResult() { List result = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null, null)) + mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null)) .stream() .filter(p -> p.getProcessId() > lastProcessId) .filter(p -> p.getStatus().equals(ProcessStatus.SUCCESS)) @@ -191,7 +190,7 @@ protected void assertOptimizeHangUp() { List tableOptimizingProcesses = getAs( TableProcessMapper.class, - mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null, null)) + mapper -> mapper.listProcessMeta(identifier.getId(), null, null, null)) .stream() .filter(p -> p.getProcessId() > lastProcessId) .collect(Collectors.toList()); diff --git a/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java b/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java index 29359f2453..8e2139ba69 100644 --- a/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java +++ b/amoro-ams/src/test/java/org/apache/amoro/server/scheduler/inline/TestProcessDataExpiringExecutor.java @@ -185,8 +185,7 @@ public void insertProcess(long tableId, long processId, ProcessStatus status, lo public List listProcesses(long tableId) { return getAs( - TableProcessMapper.class, - mapper -> mapper.listProcessMeta(tableId, null, null, null, null)); + TableProcessMapper.class, mapper -> mapper.listProcessMeta(tableId, null, null, null)); } public void cleanAll(long tableId) { diff --git a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java index 439a8e835e..0deb515c6b 100644 --- a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java +++ b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutorService; /** API for obtaining metadata information of various formats. */ @@ -75,25 +74,49 @@ Pair, Integer> getOptimizingProcessesInfo( /** Return the optimizing types of the {@link AmoroTable} is supported. */ Map getTableOptimizingTypes(AmoroTable amoroTable); - /** Return the maintenance types of the {@link AmoroTable} is supported. */ - default Map getTableMaintenanceTypes(AmoroTable amoroTable) { + /** Return the process types for the given category of the {@link AmoroTable}. */ + default Map getTableProcessTypes( + AmoroTable amoroTable, String processCategory) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return getTableOptimizingTypes(amoroTable); + } + return Collections.emptyMap(); } static boolean matchProcessCategory( - String processCategory, Set optimizingTypes, String type) { - if (processCategory == null) { - return true; + String processCategory, List categoryTypes, String type) { + // When no category is specified, no records should match + if (processCategory == null || categoryTypes.isEmpty()) { + return false; + } + return type != null && categoryTypes.stream().anyMatch(t -> t.equalsIgnoreCase(type)); + } + + /** + * Resolve the list of process types for a given process category. + * + * @param processCategory the process category string, must not be null + * @return the matching type list, or empty list for unknown category + */ + static List resolveCategoryTypes( + String processCategory, + List optimizingTypes, + List cleanupTypes, + List profilingTypes) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return optimizingTypes; } - boolean isOptimizingType = - type != null && optimizingTypes.stream().anyMatch(t -> t.equalsIgnoreCase(type)); - if ("OPTIMIZING".equalsIgnoreCase(processCategory)) { - return isOptimizingType; + + if (ProcessCategory.CLEANUP.getName().equalsIgnoreCase(processCategory)) { + return cleanupTypes; } - if ("MAINTENANCE".equalsIgnoreCase(processCategory)) { - return !isOptimizingType; + + if (ProcessCategory.PROFILING.getName().equalsIgnoreCase(processCategory)) { + return profilingTypes; } - return true; + + return Collections.emptyList(); } /** Get the paged optimizing process tasks information of the {@link AmoroTable}. */ diff --git a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/ProcessCategory.java b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/ProcessCategory.java new file mode 100644 index 0000000000..070a5bc2f9 --- /dev/null +++ b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/ProcessCategory.java @@ -0,0 +1,55 @@ +/* + * 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.amoro.table.descriptor; + +import java.util.Arrays; + +/** + * Categories for table processes displayed in the dashboard. + *
  • OPTIMIZING: Performance optimization processes (e.g., compaction, clustering). + *
  • CLEANUP: Space reclamation and lifecycle management processes (e.g., expire snapshots, clean + * orphan files). + *
  • PROFILING: Information enrichment and metadata augmentation processes (e.g., auto create + * tags). + */ +public enum ProcessCategory { + OPTIMIZING("OPTIMIZING"), + CLEANUP("CLEANUP"), + PROFILING("PROFILING"); + + private final String name; + + ProcessCategory(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static ProcessCategory fromString(String value) { + if (value == null) { + return null; + } + return Arrays.stream(values()) + .filter(c -> c.name.equalsIgnoreCase(value)) + .findFirst() + .orElse(null); + } +} diff --git a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java index 973bf9b4fc..6f2a580b6a 100644 --- a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java +++ b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java @@ -79,6 +79,7 @@ import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -97,7 +98,7 @@ public class HudiTableDescriptor implements FormatTableDescriptor { private static final Logger LOG = LoggerFactory.getLogger(HudiTableDescriptor.class); private static final String COMPACTION = "compaction"; private static final String CLUSTERING = "clustering"; - private static final Set OPTIMIZING_TYPES = Sets.newHashSet(COMPACTION, CLUSTERING); + private static final List OPTIMIZING_TYPES = Arrays.asList(COMPACTION, CLUSTERING); // table comment private static final String COMMENT = "comment"; @@ -387,6 +388,9 @@ public Pair, Integer> getOptimizingProcessesInfo( }) .filter(Objects::nonNull) .collect(Collectors.toList()); + List categoryTypes = + FormatTableDescriptor.resolveCategoryTypes( + processCategory, OPTIMIZING_TYPES, Collections.emptyList(), Collections.emptyList()); infos = infos.stream() .filter( @@ -396,7 +400,7 @@ public Pair, Integer> getOptimizingProcessesInfo( .filter( i -> FormatTableDescriptor.matchProcessCategory( - processCategory, OPTIMIZING_TYPES, i.getOptimizingType())) + processCategory, categoryTypes, i.getOptimizingType())) .collect(Collectors.toList()); int total = infos.size(); infos = infos.stream().skip(offset).limit(limit).collect(Collectors.toList()); diff --git a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java index 6b98102292..050509492f 100644 --- a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java +++ b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java @@ -26,7 +26,6 @@ import org.apache.amoro.process.ProcessStatus; import org.apache.amoro.shade.guava32.com.google.common.collect.Lists; import org.apache.amoro.shade.guava32.com.google.common.collect.Maps; -import org.apache.amoro.shade.guava32.com.google.common.collect.Sets; import org.apache.amoro.shade.guava32.com.google.common.collect.Streams; import org.apache.amoro.table.TableIdentifier; import org.apache.amoro.table.descriptor.AMSColumnInfo; @@ -92,7 +91,7 @@ public class PaimonTableDescriptor implements FormatTableDescriptor { public static final String PAIMON_MAIN_BRANCH_NAME = "main"; private static final String FULL_TYPE = "FULL"; private static final String MINOR_TYPE = "MINOR"; - private static final Set OPTIMIZING_TYPES = Sets.newHashSet(FULL_TYPE, MINOR_TYPE); + private static final List OPTIMIZING_TYPES = Arrays.asList(FULL_TYPE, MINOR_TYPE); private ExecutorService executor; @@ -454,6 +453,9 @@ public Pair, Integer> getOptimizingProcessesInfo( } catch (IOException e) { throw new RuntimeException(e); } + List categoryTypes = + FormatTableDescriptor.resolveCategoryTypes( + processCategory, OPTIMIZING_TYPES, Collections.emptyList(), Collections.emptyList()); processInfoList = processInfoList.stream() .filter(p -> StringUtils.isBlank(type) || type.equalsIgnoreCase(p.getOptimizingType())) @@ -461,7 +463,7 @@ public Pair, Integer> getOptimizingProcessesInfo( .filter( p -> FormatTableDescriptor.matchProcessCategory( - processCategory, OPTIMIZING_TYPES, p.getOptimizingType())) + processCategory, categoryTypes, p.getOptimizingType())) .collect(Collectors.toList()); int total = processInfoList.size(); processInfoList = diff --git a/amoro-web/mock/modules/table.js b/amoro-web/mock/modules/table.js index 372e81fd97..119cf7b3fd 100644 --- a/amoro-web/mock/modules/table.js +++ b/amoro-web/mock/modules/table.js @@ -307,17 +307,32 @@ export default [ }), }, { - url: '/mock/api/ams/v1/tables/catalogs/test_catalog/dbs/db/tables/user/optimizing-types', + url: '/mock/api/ams/v1/tables/catalogs/test_catalog/dbs/db/tables/user/process-types', method: 'get', - response: () => ({ - "message": "success", - "code": 200, - "result": { - "MINOR": "minor", - "MAJOR": "major", - "FULL": "full", + response: ({ query }) => { + const processCategory = query?.processCategory || 'OPTIMIZING' + const result = { + OPTIMIZING: { + 'MINOR': 'minor', + 'MAJOR': 'major', + 'FULL': 'full', + }, + CLEANUP: { + 'EXPIRE-SNAPSHOTS': 'Expire Snapshots', + 'CLEAN-ORPHAN-FILES': 'Clean Orphan Files', + 'CLEAN-DANGLING-DELETE-FILES': 'Clean Dangling Delete Files', + 'EXPIRE-DATA': 'Expire Data', + }, + PROFILING: { + 'AUTO-CREATE-TAGS': 'Auto Create Tags', + }, } - }), + return { + 'message': 'success', + 'code': 200, + 'result': result[processCategory] || result.OPTIMIZING, + } + }, }, { url: '/mock/api/ams/v1/tables/catalogs/test_catalog/dbs/db/tables/user/operations', diff --git a/amoro-web/src/language/en.ts b/amoro-web/src/language/en.ts index 11d605dbca..b0be90e2db 100644 --- a/amoro-web/src/language/en.ts +++ b/amoro-web/src/language/en.ts @@ -28,7 +28,8 @@ export default { catalog: 'Catalog', tables: 'Tables', optimizing: 'Optimizing', - maintenance: 'Maintenance', + cleanup: 'Cleanup', + profiling: 'Profiling', terminal: 'Terminal', settings: 'Settings', systemSetting: 'System Settings', @@ -125,7 +126,8 @@ export default { resourceGroup: 'Resource Group', releaseOptModalTitle: 'Release this optimizer?', cancelOptimizingProcessOptModalTitle: 'Cancel the optimizing process of this table?', - cancelMaintenanceProcessOptModalTitle: 'Cancel the maintenance process of this table?', + cancelCleanupProcessOptModalTitle: 'Cancel the cleanup process of this table?', + cancelProfilingProcessOptModalTitle: 'Cancel the profiling process of this table?', loadProcessTypesFailed: 'Failed to load process types', welcomeTip: 'Welcome to Amoro!', signIn: 'Sign in', diff --git a/amoro-web/src/language/zh.ts b/amoro-web/src/language/zh.ts index 72e3c663c3..cf20bcefc5 100644 --- a/amoro-web/src/language/zh.ts +++ b/amoro-web/src/language/zh.ts @@ -28,7 +28,8 @@ export default { catalog: '目录', tables: '表', optimizing: '优化', - maintenance: '维护', + cleanup: '清理', + profiling: '剖析', terminal: '终端', settings: '设置', systemSetting: '系统设置', @@ -125,7 +126,8 @@ export default { resourceGroup: '资源组', releaseOptModalTitle: '释放此优化器?', cancelOptimizingProcessOptModalTitle: '取消该表的优化过程?', - cancelMaintenanceProcessOptModalTitle: '取消该表的维护过程?', + cancelCleanupProcessOptModalTitle: '取消该表的清理过程?', + cancelProfilingProcessOptModalTitle: '取消该表的剖析过程?', loadProcessTypesFailed: '加载流程类型失败', welcomeTip: '欢迎访问 Amoro!', signIn: '登入', diff --git a/amoro-web/src/services/table.service.ts b/amoro-web/src/services/table.service.ts index 83d7dc8c4f..0ff5ba928a 100644 --- a/amoro-web/src/services/table.service.ts +++ b/amoro-web/src/services/table.service.ts @@ -169,30 +169,18 @@ export function getOptimizingProcesses( return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/optimizing-processes`, { params: { page, pageSize, token, type, processCategory, status } }) } -// get optimizing process types -export function getTableOptimizingTypes( +// get process types by category +export function getTableProcessTypes( params: { catalog: string db: string table: string + processCategory: string token?: string }, ) { - const { catalog, db, table, token } = params - return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/optimizing-types`, { params: { token } }) -} - -// get maintenance process types -export function getTableMaintenanceTypes( - params: { - catalog: string - db: string - table: string - token?: string - }, -) { - const { catalog, db, table, token } = params - return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/maintenance-types`, { params: { token } }) + const { catalog, db, table, processCategory, token } = params + return request.get(`api/ams/v1/tables/catalogs/${catalog}/dbs/${db}/tables/${table}/process-types`, { params: { processCategory, token } }) } // get optimizing tasks diff --git a/amoro-web/src/views/tables/components/Maintenance.vue b/amoro-web/src/views/tables/components/Cleanup.vue similarity index 88% rename from amoro-web/src/views/tables/components/Maintenance.vue rename to amoro-web/src/views/tables/components/Cleanup.vue index a0a955de77..4bb29700d2 100644 --- a/amoro-web/src/views/tables/components/Maintenance.vue +++ b/amoro-web/src/views/tables/components/Cleanup.vue @@ -22,5 +22,5 @@ import Optimizing from './Optimizing.vue' diff --git a/amoro-web/src/views/tables/components/Optimizing.vue b/amoro-web/src/views/tables/components/Optimizing.vue index 1cfa44ea5f..8aa424456a 100644 --- a/amoro-web/src/views/tables/components/Optimizing.vue +++ b/amoro-web/src/views/tables/components/Optimizing.vue @@ -23,7 +23,7 @@ import { useRoute } from 'vue-router' import { Modal, message } from 'ant-design-vue' import { usePagination } from '@/hooks/usePagination' import type { BreadcrumbOptimizingItem, IColumns, ILableAndValue } from '@/types/common.type' -import { cancelOptimizingProcess, getOptimizingProcesses, getTableOptimizingTypes, getTableMaintenanceTypes, getTasksByOptimizingProcessId } from '@/services/table.service' +import { cancelOptimizingProcess, getOptimizingProcesses, getTableProcessTypes, getTasksByOptimizingProcessId } from '@/services/table.service' import { bytesToSize, dateFormat, formatMS2Time } from '@/utils/index' import { canManageTable } from '@/utils/permission' @@ -107,9 +107,8 @@ async function getQueryDataDictList() { statusTypeList.value = status try { - const fetchFn = props.processCategory === 'MAINTENANCE' ? getTableMaintenanceTypes : getTableOptimizingTypes - const tableProcessTypes = await fetchFn({ ...sourceData }) - actionTypeList.value = Object.entries(tableProcessTypes).map(([typeName, displayName]) => ({ label: displayName as string, value: typeName })) + const rawTypes = await getTableProcessTypes({ ...sourceData, processCategory: props.processCategory }) + actionTypeList.value = Object.entries(rawTypes).map(([typeName, displayName]) => ({ label: displayName as string, value: typeName })) } catch (error) { console.error('Failed to load process types:', error) diff --git a/amoro-web/src/views/tables/components/Profiling.vue b/amoro-web/src/views/tables/components/Profiling.vue new file mode 100644 index 0000000000..704b55b3c4 --- /dev/null +++ b/amoro-web/src/views/tables/components/Profiling.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/amoro-web/src/views/tables/index.vue b/amoro-web/src/views/tables/index.vue index 4dcc8be048..b3413d982d 100644 --- a/amoro-web/src/views/tables/index.vue +++ b/amoro-web/src/views/tables/index.vue @@ -24,7 +24,8 @@ import UFiles from './components/Files.vue' import UOperations from './components/Operations.vue' import USnapshots from './components/Snapshots.vue' import UOptimizing from './components/Optimizing.vue' -import UMaintenance from './components/Maintenance.vue' +import UCleanup from './components/Cleanup.vue' +import UProfiling from './components/Profiling.vue' import UHealthScore from './components/HealthScoreDetails.vue' import TableExplorer from './components/TableExplorer.vue' import useStore from '@/store/index' @@ -39,7 +40,8 @@ export default defineComponent({ UOperations, USnapshots, UOptimizing, - UMaintenance, + UCleanup, + UProfiling, UHealthScore, TableExplorer, }, @@ -108,7 +110,8 @@ export default defineComponent({ const tabConfigs = shallowReactive([ { key: 'Snapshots', label: 'snapshots' }, { key: 'Optimizing', label: 'optimizing' }, - { key: 'Maintenance', label: 'maintenance' }, + { key: 'Cleanup', label: 'cleanup' }, + { key: 'Profiling', label: 'profiling' }, { key: 'Operations', label: 'operations' }, ]) From ead2f281ae6c68b85695389e1fbcc39883b1eb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=87=E9=A2=86?= Date: Mon, 8 Jun 2026 17:21:04 +0800 Subject: [PATCH 4/5] Replace static resolveCategoryTypes/matchProcessCategory with instance method getProcessTypesByCategory, moving routing logic into each descriptor. --- .../MixedAndIcebergTableDescriptor.java | 18 ++++++++-- .../descriptor/FormatTableDescriptor.java | 33 ++----------------- .../formats/hudi/HudiTableDescriptor.java | 18 +++++++--- .../formats/paimon/PaimonTableDescriptor.java | 18 +++++++--- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java index 140e30cf3f..be854a6048 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java @@ -701,9 +701,7 @@ public Pair, Integer> getOptimizingProcessesInfo( // No category specified: return empty results return Pair.of(Collections.emptyList(), 0); } else { - List categoryTypes = - FormatTableDescriptor.resolveCategoryTypes( - processCategory, OPTIMIZING_TYPE_LIST, CLEANUP_TYPE_LIST, PROFILING_TYPE_LIST); + List categoryTypes = getProcessTypesByCategory(processCategory); if (categoryTypes.isEmpty()) { // Unknown category: return empty results to avoid exposing all processes return Pair.of(Collections.emptyList(), 0); @@ -785,6 +783,20 @@ public Map getTableProcessTypes( return Collections.emptyMap(); } + @Override + public List getProcessTypesByCategory(String processCategory) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return OPTIMIZING_TYPE_LIST; + } + if (ProcessCategory.CLEANUP.getName().equalsIgnoreCase(processCategory)) { + return CLEANUP_TYPE_LIST; + } + if (ProcessCategory.PROFILING.getName().equalsIgnoreCase(processCategory)) { + return PROFILING_TYPE_LIST; + } + return Collections.emptyList(); + } + @Override public List getOptimizingTaskInfos( AmoroTable amoroTable, String processId) { diff --git a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java index 0deb515c6b..8b3ec118b2 100644 --- a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java +++ b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java @@ -84,38 +84,11 @@ default Map getTableProcessTypes( return Collections.emptyMap(); } - static boolean matchProcessCategory( - String processCategory, List categoryTypes, String type) { - // When no category is specified, no records should match - if (processCategory == null || categoryTypes.isEmpty()) { - return false; - } - return type != null && categoryTypes.stream().anyMatch(t -> t.equalsIgnoreCase(type)); - } - /** - * Resolve the list of process types for a given process category. - * - * @param processCategory the process category string, must not be null - * @return the matching type list, or empty list for unknown category + * Returns the list of process type names belonging to the given category. Used internally to + * filter processes by category. */ - static List resolveCategoryTypes( - String processCategory, - List optimizingTypes, - List cleanupTypes, - List profilingTypes) { - if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { - return optimizingTypes; - } - - if (ProcessCategory.CLEANUP.getName().equalsIgnoreCase(processCategory)) { - return cleanupTypes; - } - - if (ProcessCategory.PROFILING.getName().equalsIgnoreCase(processCategory)) { - return profilingTypes; - } - + default List getProcessTypesByCategory(String processCategory) { return Collections.emptyList(); } diff --git a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java index 6f2a580b6a..cc632ca843 100644 --- a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java +++ b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java @@ -37,6 +37,7 @@ import org.apache.amoro.table.descriptor.OptimizingTaskInfo; import org.apache.amoro.table.descriptor.PartitionBaseInfo; import org.apache.amoro.table.descriptor.PartitionFileBaseInfo; +import org.apache.amoro.table.descriptor.ProcessCategory; import org.apache.amoro.table.descriptor.ServerTableMeta; import org.apache.amoro.table.descriptor.TableSummary; import org.apache.amoro.table.descriptor.TagOrBranchInfo; @@ -388,9 +389,7 @@ public Pair, Integer> getOptimizingProcessesInfo( }) .filter(Objects::nonNull) .collect(Collectors.toList()); - List categoryTypes = - FormatTableDescriptor.resolveCategoryTypes( - processCategory, OPTIMIZING_TYPES, Collections.emptyList(), Collections.emptyList()); + List categoryTypes = getProcessTypesByCategory(processCategory); infos = infos.stream() .filter( @@ -399,8 +398,9 @@ public Pair, Integer> getOptimizingProcessesInfo( .filter(i -> status == null || status == i.getStatus()) .filter( i -> - FormatTableDescriptor.matchProcessCategory( - processCategory, categoryTypes, i.getOptimizingType())) + i.getOptimizingType() != null + && categoryTypes.stream() + .anyMatch(t -> t.equalsIgnoreCase(i.getOptimizingType()))) .collect(Collectors.toList()); int total = infos.size(); infos = infos.stream().skip(offset).limit(limit).collect(Collectors.toList()); @@ -415,6 +415,14 @@ public Map getTableOptimizingTypes(AmoroTable amoroTable) { return types; } + @Override + public List getProcessTypesByCategory(String processCategory) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return OPTIMIZING_TYPES; + } + return Collections.emptyList(); + } + protected OptimizingProcessInfo getOptimizingInfo( String instantTimestamp, Map instantMap, HoodieTimeline timeline) throws IOException { diff --git a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java index 050509492f..b5dd915a2f 100644 --- a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java +++ b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java @@ -41,6 +41,7 @@ import org.apache.amoro.table.descriptor.OptimizingTaskInfo; import org.apache.amoro.table.descriptor.PartitionBaseInfo; import org.apache.amoro.table.descriptor.PartitionFileBaseInfo; +import org.apache.amoro.table.descriptor.ProcessCategory; import org.apache.amoro.table.descriptor.ServerTableMeta; import org.apache.amoro.table.descriptor.TableSummary; import org.apache.amoro.table.descriptor.TagOrBranchInfo; @@ -453,17 +454,16 @@ public Pair, Integer> getOptimizingProcessesInfo( } catch (IOException e) { throw new RuntimeException(e); } - List categoryTypes = - FormatTableDescriptor.resolveCategoryTypes( - processCategory, OPTIMIZING_TYPES, Collections.emptyList(), Collections.emptyList()); + List categoryTypes = getProcessTypesByCategory(processCategory); processInfoList = processInfoList.stream() .filter(p -> StringUtils.isBlank(type) || type.equalsIgnoreCase(p.getOptimizingType())) .filter(p -> status == null || status == p.getStatus()) .filter( p -> - FormatTableDescriptor.matchProcessCategory( - processCategory, categoryTypes, p.getOptimizingType())) + p.getOptimizingType() != null + && categoryTypes.stream() + .anyMatch(t -> t.equalsIgnoreCase(p.getOptimizingType()))) .collect(Collectors.toList()); int total = processInfoList.size(); processInfoList = @@ -479,6 +479,14 @@ public Map getTableOptimizingTypes(AmoroTable amoroTable) { return types; } + @Override + public List getProcessTypesByCategory(String processCategory) { + if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { + return OPTIMIZING_TYPES; + } + return Collections.emptyList(); + } + @Override public List getOptimizingTaskInfos( AmoroTable amoroTable, String processId) { From fd6387580b0ec9b3009a5aab65f9c54be9f4586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=87=E9=A2=86?= Date: Mon, 8 Jun 2026 17:33:40 +0800 Subject: [PATCH 5/5] fixup style --- .../server/dashboard/MixedAndIcebergTableDescriptor.java | 3 +++ .../amoro/table/descriptor/FormatTableDescriptor.java | 9 ++------- .../apache/amoro/formats/hudi/HudiTableDescriptor.java | 1 + .../amoro/formats/paimon/PaimonTableDescriptor.java | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java index be854a6048..f6c1dd8d32 100644 --- a/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java +++ b/amoro-ams/src/main/java/org/apache/amoro/server/dashboard/MixedAndIcebergTableDescriptor.java @@ -788,12 +788,15 @@ public List getProcessTypesByCategory(String processCategory) { if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { return OPTIMIZING_TYPE_LIST; } + if (ProcessCategory.CLEANUP.getName().equalsIgnoreCase(processCategory)) { return CLEANUP_TYPE_LIST; } + if (ProcessCategory.PROFILING.getName().equalsIgnoreCase(processCategory)) { return PROFILING_TYPE_LIST; } + return Collections.emptyList(); } diff --git a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java index 8b3ec118b2..40261fc87a 100644 --- a/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java +++ b/amoro-common/src/main/java/org/apache/amoro/table/descriptor/FormatTableDescriptor.java @@ -84,13 +84,8 @@ default Map getTableProcessTypes( return Collections.emptyMap(); } - /** - * Returns the list of process type names belonging to the given category. Used internally to - * filter processes by category. - */ - default List getProcessTypesByCategory(String processCategory) { - return Collections.emptyList(); - } + /** Returns the list of process type names belonging to the given category. */ + List getProcessTypesByCategory(String processCategory); /** Get the paged optimizing process tasks information of the {@link AmoroTable}. */ List getOptimizingTaskInfos(AmoroTable amoroTable, String processId); diff --git a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java index cc632ca843..7f1a358807 100644 --- a/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java +++ b/amoro-format-hudi/src/main/java/org/apache/amoro/formats/hudi/HudiTableDescriptor.java @@ -420,6 +420,7 @@ public List getProcessTypesByCategory(String processCategory) { if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { return OPTIMIZING_TYPES; } + return Collections.emptyList(); } diff --git a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java index b5dd915a2f..7263dc8a5a 100644 --- a/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java +++ b/amoro-format-paimon/src/main/java/org/apache/amoro/formats/paimon/PaimonTableDescriptor.java @@ -484,6 +484,7 @@ public List getProcessTypesByCategory(String processCategory) { if (ProcessCategory.OPTIMIZING.getName().equalsIgnoreCase(processCategory)) { return OPTIMIZING_TYPES; } + return Collections.emptyList(); }