[Improve] support resource group (#2742)
* [Improve] support resource group
* [Improve] change group icon
---------
Co-authored-by: zhoulii <zhouli16@hikvision.com.cn>
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/AppBuildPipeServiceImpl.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/AppBuildPipeServiceImpl.java
index af3a23c..ac4917b 100644
--- a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/AppBuildPipeServiceImpl.java
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/AppBuildPipeServiceImpl.java
@@ -42,6 +42,7 @@
import org.apache.streampark.console.core.enums.NoticeType;
import org.apache.streampark.console.core.enums.OptionState;
import org.apache.streampark.console.core.enums.ReleaseState;
+import org.apache.streampark.console.core.enums.ResourceType;
import org.apache.streampark.console.core.mapper.ApplicationBuildPipelineMapper;
import org.apache.streampark.console.core.service.AppBuildPipeService;
import org.apache.streampark.console.core.service.ApplicationBackUpService;
@@ -82,6 +83,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
@@ -595,32 +597,58 @@
.forEach(
resourceId -> {
Resource resource = resourceService.getById(resourceId);
- Dependency dependency = Dependency.toDependency(resource.getResource());
- dependency
- .getPom()
- .forEach(
- pom -> {
- mvnArtifacts.add(
- new Artifact(
- pom.getGroupId(),
- pom.getArtifactId(),
- pom.getVersion(),
- pom.getClassifier()));
- });
- dependency
- .getJar()
- .forEach(
- jar -> {
- jarLibs.add(
- String.format(
- "%s/%d/%s",
- Workspace.local().APP_UPLOADS(), application.getTeamId(), jar));
- });
+
+ if (resource.getResourceType() != ResourceType.GROUP) {
+ mergeDependency(application, mvnArtifacts, jarLibs, resource);
+ } else {
+ try {
+ String[] groupElements =
+ JacksonUtils.read(resource.getResource(), String[].class);
+ Arrays.stream(groupElements)
+ .forEach(
+ resourceIdInGroup -> {
+ mergeDependency(
+ application,
+ mvnArtifacts,
+ jarLibs,
+ resourceService.getById(resourceIdInGroup));
+ });
+ } catch (JsonProcessingException e) {
+ throw new ApiAlertException("Parse resource group failed.", e);
+ }
+ }
});
return dependencyInfo.merge(mvnArtifacts, jarLibs);
} catch (Exception e) {
- log.warn("Merge team dependency failed.");
+ log.warn("Merge team dependency failed.", e);
return dependencyInfo;
}
}
+
+ private static void mergeDependency(
+ Application application,
+ List<Artifact> mvnArtifacts,
+ List<String> jarLibs,
+ Resource resource) {
+ Dependency dependency = Dependency.toDependency(resource.getResource());
+ dependency
+ .getPom()
+ .forEach(
+ pom -> {
+ mvnArtifacts.add(
+ new Artifact(
+ pom.getGroupId(),
+ pom.getArtifactId(),
+ pom.getVersion(),
+ pom.getClassifier()));
+ });
+ dependency
+ .getJar()
+ .forEach(
+ jar -> {
+ jarLibs.add(
+ String.format(
+ "%s/%d/%s", Workspace.local().APP_UPLOADS(), application.getTeamId(), jar));
+ });
+ }
}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/ResourceServiceImpl.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/ResourceServiceImpl.java
index ad39530..f702ad0 100644
--- a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/ResourceServiceImpl.java
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/ResourceServiceImpl.java
@@ -91,7 +91,8 @@
ApiAlertException.throwIfNull(resourceStr, "Please add pom or jar resource.");
if (resource.getResourceType() == ResourceType.GROUP) {
- // TODO: will support later
+ ApiAlertException.throwIfNull(
+ resource.getResourceName(), "The name of resource group is required.");
} else {
Dependency dependency = Dependency.toDependency(resourceStr);
List<String> jars = dependency.getJar();
diff --git a/streampark-console/streampark-console-webapp/src/assets/icons/group.svg b/streampark-console/streampark-console-webapp/src/assets/icons/group.svg
new file mode 100644
index 0000000..e7a937d
--- /dev/null
+++ b/streampark-console/streampark-console-webapp/src/assets/icons/group.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683865708492" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16803" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M128 64h256a64 64 0 0 1 64 64v256a64 64 0 0 1-64 64H128a64 64 0 0 1-64-64V128a64 64 0 0 1 64-64z m512 0h256a64 64 0 0 1 64 64v256a64 64 0 0 1-64 64h-256a64 64 0 0 1-64-64V128a64 64 0 0 1 64-64zM128 576h256a64 64 0 0 1 64 64v256a64 64 0 0 1-64 64H128a64 64 0 0 1-64-64v-256a64 64 0 0 1 64-64z m512 0h256a64 64 0 0 1 64 64v256a64 64 0 0 1-64 64h-256a64 64 0 0 1-64-64v-256a64 64 0 0 1 64-64zM128 128v256h256V128H128z m512 0v256h256V128h-256z m-512 512v256h256v-256H128z m512 0v256h256v-256h-256z" fill="#1890ff" p-id="16804"></path></svg>
\ No newline at end of file
diff --git a/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/resource.ts b/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/resource.ts
index 84cc1d1..19ffd16 100644
--- a/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/resource.ts
+++ b/streampark-console/streampark-console-webapp/src/locales/lang/en/flink/resource.ts
@@ -23,7 +23,12 @@
uploadResource: 'Upload Resource',
resourceType: 'Resource Type',
engineType: 'Engine Type',
+ resourceGroup: 'Resource Group',
+ groupName: 'Group Name',
engineTypePlaceholder: 'Please select compute engine type',
+ resourceGroupPlaceholder: 'Please choose resource',
+ groupNamePlaceholder: 'Please input the group name',
+ groupNameIsRequiredMessage: 'Group Name is required',
multiPomTip: 'Do not add multiple dependencies at one time',
addResourceTip: 'Please add a resource',
add: 'Add',
diff --git a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/resource.ts b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/resource.ts
index 7d9c9ba..023d0f3 100644
--- a/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/resource.ts
+++ b/streampark-console/streampark-console-webapp/src/locales/lang/zh-CN/flink/resource.ts
@@ -23,6 +23,11 @@
uploadResource: '上传资源',
resourceType: '资源类型',
engineType: '计算引擎类型',
+ resourceGroup: '资源组',
+ groupName: '资源组名称',
+ resourceGroupPlaceholder: '请选择组资源',
+ groupNamePlaceholder: '请输入资源组名称',
+ groupNameIsRequiredMessage: '资源组名称必填',
engineTypePlaceholder: '请选择计算引擎类型',
multiPomTip: '不支持同时添加多个依赖',
addResourceTip: '请添加资源',
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/resource/View.vue b/streampark-console/streampark-console-webapp/src/views/flink/resource/View.vue
index df46ce3..bc5fb08 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/resource/View.vue
+++ b/streampark-console/streampark-console-webapp/src/views/flink/resource/View.vue
@@ -54,6 +54,13 @@
>
UDXF
</Tag>
+ <Tag
+ class="bold-tag"
+ color="#fcaa80"
+ v-if="record.resourceType == ResourceTypeEnum.GROUP"
+ >
+ GROUP
+ </Tag>
</template>
<template v-if="column.dataIndex === 'engineType'">
<Tag
@@ -95,7 +102,7 @@
</template>
</template>
</BasicTable>
- <ResourceDrawer @register="registerDrawer" @success="handleSuccess" />
+ <ResourceDrawer :teamResource="teamResource" @register="registerDrawer" @success="handleSuccess" />
</div>
</template>
<script lang="ts">
@@ -105,7 +112,7 @@
</script>
<script lang="ts" setup>
- import {defineComponent, ref} from 'vue';
+import {defineComponent, onMounted, ref} from 'vue';
import { BasicTable, useTable, TableAction, SorterResult } from '/@/components/Table';
import ResourceDrawer from './components/ResourceDrawer.vue';
import { useDrawer } from '/@/components/Drawer';
@@ -114,10 +121,15 @@
import { useI18n } from '/@/hooks/web/useI18n';
import Icon from '/@/components/Icon';
import { useRouter } from 'vue-router';
- import { fetchResourceDelete, fetchResourceList } from "/@/api/flink/resource";
+ import {
+ fetchResourceDelete,
+ fetchResourceList,
+ fetchTeamResource
+ } from "/@/api/flink/resource";
import { EngineTypeEnum, ResourceTypeEnum } from "/@/views/flink/resource/resource.data";
import { Tag } from 'ant-design-vue';
+ const teamResource = ref<Array<any>>([]);
const router = useRouter();
const [registerDrawer, { openDrawer }] = useDrawer();
const [registerInfo, { openDrawer: openInfoDraw }] = useDrawer();
@@ -180,6 +192,7 @@
if (data.status === 'success') {
createMessage.success(t('flink.resource.deleteResource') + t('flink.resource.success'));
reload();
+ updateTeamResource();
} else {
createMessage.error(t('flink.resource.deleteResource') + t('flink.resource.fail'));
}
@@ -190,6 +203,18 @@
`${isUpdate ? t('common.edit') : t('flink.resource.add')}${t('flink.resource.success')}`,
);
reload();
+ updateTeamResource();
}
+ function updateTeamResource() {
+ /* Get team dependencies */
+ fetchTeamResource({}).then((res) => {
+ teamResource.value = res;
+ });
+ }
+
+ onMounted(async () => {
+ updateTeamResource();
+ });
+
</script>
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/resource/components/Resource.vue b/streampark-console/streampark-console-webapp/src/views/flink/resource/components/Resource.vue
index 540b0e5..a8a918b 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/resource/components/Resource.vue
+++ b/streampark-console/streampark-console-webapp/src/views/flink/resource/components/Resource.vue
@@ -32,6 +32,7 @@
import { useMessage } from '/@/hooks/web/useMessage';
import { fetchUpload } from '/@/api/flink/app/app';
import UploadJobJar from '/@/views/flink/app/components/UploadJobJar.vue';
+ import {onMounted, unref} from "vue";
interface DependencyType {
artifactId: string;
@@ -201,6 +202,10 @@
emit('update:value', data);
});
+ onMounted(async () => {
+ setDefaultValue(JSON.parse(props?.formModel?.dependency || '{}'));
+ });
+
defineExpose({
setDefaultValue,
dependency,
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/resource/components/ResourceDrawer.vue b/streampark-console/streampark-console-webapp/src/views/flink/resource/components/ResourceDrawer.vue
index 75e16ac..8f2d333 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/resource/components/ResourceDrawer.vue
+++ b/streampark-console/streampark-console-webapp/src/views/flink/resource/components/ResourceDrawer.vue
@@ -52,11 +52,21 @@
import Resource from '/@/views/flink/resource/components/Resource.vue';
import { fetchAddResource, fetchUpdateResource } from "/@/api/flink/resource";
import { EngineTypeEnum } from "/@/views/flink/resource/resource.data";
- import { renderResourceType } from "/@/views/flink/resource/useResourceRender";
+ import {
+ renderResourceType,
+ renderStreamParkResourceGroup
+ } from "/@/views/flink/resource/useResourceRender";
import { useMessage } from "/@/hooks/web/useMessage";
const emit = defineEmits(['success', 'register']);
+ const props = defineProps({
+ teamResource: {
+ type: Object as Array<any>,
+ required: true,
+ },
+ });
+
const { t } = useI18n();
const { Swal } = useMessage();
@@ -89,10 +99,27 @@
rules: [{ required: true, message: t('flink.resource.form.engineTypeIsRequiredMessage') }],
},
{
+ field: 'resourceName',
+ label: t('flink.resource.groupName'),
+ component: 'Input',
+ componentProps: { placeholder: t('flink.resource.groupNamePlaceholder') },
+ ifShow: ({ values }) => values?.resourceType == 'GROUP',
+ rules: [{ required: true, message: t('flink.resource.groupNameIsRequiredMessage') }],
+ },
+ {
+ field: 'resourceGroup',
+ label: t('flink.resource.resourceGroup'),
+ component: 'Select',
+ render: ({ model }) =>
+ renderStreamParkResourceGroup( { model, resources: unref(props.teamResource) }, ),
+ ifShow: ({ values }) => values?.resourceType == 'GROUP',
+ },
+ {
field: 'dependency',
label: t('flink.resource.addResource'),
component: 'Input',
slot: 'resource',
+ ifShow: ({ values }) => values?.resourceType !== 'GROUP',
},
{
field: 'mainClass',
@@ -123,13 +150,21 @@
const [registerDrawer, { setDrawerProps, changeLoading, closeDrawer }] = useDrawerInner(
async (data: Recordable) => {
+ unref(resourceRef)?.setDefaultValue({});
resetFields();
setDrawerProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
resourceId.value = data.record.id;
setFieldsValue(data.record);
- unref(resourceRef)?.setDefaultValue(JSON.parse(data.record.resource || '{}'));
+
+ if (data.record?.resourceType == 'GROUP') {
+ setFieldsValue({ resourceGroup: JSON.parse(data.record.resource || '[]')} );
+ } else {
+ setFieldsValue({ dependency: data.record.resource});
+ unref(resourceRef)?.setDefaultValue(JSON.parse(data.record.resource || '{}'));
+ }
+
}
},
);
@@ -140,43 +175,51 @@
// form submit
async function handleSubmit() {
- const resource: { pom?: string; jar?: string } = {};
- unref(resourceRef).handleApplyPom();
- const dependencyRecords = unref(resourceRef)?.dependencyRecords;
- const uploadJars = unref(resourceRef)?.uploadJars;
-
- if (unref(dependencyRecords) && unref(dependencyRecords).length > 0) {
- if (unref(dependencyRecords).length > 1) {
- Swal.fire('Failed', t('flink.resource.multiPomTip'), 'error');
- return;
- }
- Object.assign(resource, {
- pom: unref(dependencyRecords),
- });
- }
-
- if (uploadJars && unref(uploadJars).length > 0) {
- Object.assign(resource, {
- jar: unref(uploadJars),
- });
- }
-
- if (resource.pom === undefined && resource.jar === undefined) {
- Swal.fire('Failed', t('flink.resource.addResourceTip'), 'error');
- return;
- }
-
- if (resource.pom?.length > 0 && resource.jar?.length > 0) {
- Swal.fire('Failed', t('flink.resource.multiPomTip'), 'error');
- return;
- }
-
try {
const values = await validate();
+ let resourceJson: string = '';
+
+ if (values.resourceType == 'GROUP') {
+ resourceJson = JSON.stringify(values.resourceGroup);
+ } else {
+ const resource: { pom?: string; jar?: string } = {};
+ unref(resourceRef).handleApplyPom();
+ const dependencyRecords = unref(resourceRef)?.dependencyRecords;
+ const uploadJars = unref(resourceRef)?.uploadJars;
+
+ if (unref(dependencyRecords) && unref(dependencyRecords).length > 0) {
+ if (unref(dependencyRecords).length > 1) {
+ Swal.fire('Failed', t('flink.resource.multiPomTip'), 'error');
+ return;
+ }
+ Object.assign(resource, {
+ pom: unref(dependencyRecords),
+ });
+ }
+
+ if (uploadJars && unref(uploadJars).length > 0) {
+ Object.assign(resource, {
+ jar: unref(uploadJars),
+ });
+ }
+
+ if (resource.pom === undefined && resource.jar === undefined) {
+ Swal.fire('Failed', t('flink.resource.addResourceTip'), 'error');
+ return;
+ }
+
+ if (resource.pom?.length > 0 && resource.jar?.length > 0) {
+ Swal.fire('Failed', t('flink.resource.multiPomTip'), 'error');
+ return;
+ }
+
+ resourceJson = JSON.stringify(resource);
+ }
+
setDrawerProps({ confirmLoading: true });
await (isUpdate.value
- ? fetchUpdateResource({ id: resourceId.value, resource: JSON.stringify(resource), ...values })
- : fetchAddResource({ resource: JSON.stringify(resource), ...values }));
+ ? fetchUpdateResource({ id: resourceId.value, resource: resourceJson, ...values })
+ : fetchAddResource({ resource: resourceJson, ...values }));
unref(resourceRef)?.setDefaultValue({});
resetFields();
closeDrawer();
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/resource/resource.data.ts b/streampark-console/streampark-console-webapp/src/views/flink/resource/resource.data.ts
index df0c021..cc375d5 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/resource/resource.data.ts
+++ b/streampark-console/streampark-console-webapp/src/views/flink/resource/resource.data.ts
@@ -23,6 +23,7 @@
CONNECTOR = 'CONNECTOR',
UDXF = 'UDXF',
NORMAL_JAR = 'NORMAL_JAR',
+ GROUP = 'GROUP',
}
export enum EngineTypeEnum {
diff --git a/streampark-console/streampark-console-webapp/src/views/flink/resource/useResourceRender.tsx b/streampark-console/streampark-console-webapp/src/views/flink/resource/useResourceRender.tsx
index ba48164..86664de 100644
--- a/streampark-console/streampark-console-webapp/src/views/flink/resource/useResourceRender.tsx
+++ b/streampark-console/streampark-console-webapp/src/views/flink/resource/useResourceRender.tsx
@@ -14,13 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Select } from 'ant-design-vue';
+import {Select, Tag} from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { ResourceTypeEnum } from "/@/views/flink/resource/resource.data";
import flinkAppSvg from '/@/assets/icons/flink2.svg';
import connectorSvg from '/@/assets/icons/connector.svg';
import udxfSvg from '/@/assets/icons/fx.svg';
import normalJarSvg from '/@/assets/icons/jar.svg';
+import groupSvg from '/@/assets/icons/group.svg';
const { t } = useI18n();
@@ -33,6 +34,7 @@
{ label: 'Connector', value: ResourceTypeEnum.CONNECTOR, src: connectorSvg },
{ label: 'UDXF', value: ResourceTypeEnum.UDXF, src: udxfSvg },
{ label: 'Normal Jar', value: ResourceTypeEnum.NORMAL_JAR, src: normalJarSvg },
+ { label: 'Group', value: ResourceTypeEnum.GROUP, src: groupSvg },
];
return options
.map(( {label,value, src} ) => {
@@ -70,3 +72,46 @@
</div>
);
};
+
+export const renderStreamParkResourceGroup = ({ model, resources },) => {
+
+ const renderOptions = () => {
+ console.log('resources', resources);
+ return (resources || [])
+ .filter((item) => item.resourceType !== ResourceTypeEnum.FLINK_APP
+ && item.resourceType !== ResourceTypeEnum.GROUP)
+ .map((resource) => {
+ return (
+ <Select.Option
+ key={resource.id}
+ label={ resource.resourceType + '-' + resource.resourceName}>
+ <div>
+ <Tag color="green" style=";margin-left: 5px;" size="small">
+ {resource.resourceType}
+ </Tag>
+ <span style="color: darkgrey">
+ {resource.resourceName}
+ </span>
+ </div>
+ </Select.Option>
+ );
+ });
+ };
+
+ return (
+ <div>
+ <Select
+ show-search
+ allow-clear
+ optionFilterProp="label"
+ mode="multiple"
+ max-tag-count={3}
+ onChange={(value) => (model.resourceGroup = value)}
+ value={model.resourceGroup}
+ placeholder={t('flink.resource.resourceGroupPlaceholder')}
+ >
+ {renderOptions()}
+ </Select>
+ </div>
+ );
+};