262 lines
7.9 KiB
TypeScript
262 lines
7.9 KiB
TypeScript
/*
|
|
* Copyright 2025 coze-dev Authors
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
import { useRequest } from 'ahooks';
|
|
import {
|
|
ConnectorPublishStatus,
|
|
PublishRecordStatus,
|
|
type PublishRecordDetail,
|
|
} from '@coze-arch/idl/intelligence_api';
|
|
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
|
import {
|
|
IconCozCheckMarkCircle,
|
|
IconCozClock,
|
|
IconCozCrossCircle,
|
|
} from '@coze-arch/coze-design/icons';
|
|
import { Modal, Select, Tag, type TagProps } from '@coze-arch/coze-design';
|
|
import { type OptionProps } from '@coze-arch/bot-semi/Select';
|
|
import { intelligenceApi } from '@coze-arch/bot-api';
|
|
import { EProjectPermission, useProjectAuth } from '@coze-common/auth';
|
|
|
|
import { isPublishFinish } from '../utils/is-publish-finish';
|
|
import { ProjectPublishProgress } from '../publish-progress';
|
|
|
|
enum PublishStatus {
|
|
Publishing,
|
|
Failed,
|
|
Success,
|
|
}
|
|
|
|
const PublishStatusMap: Record<
|
|
PublishStatus,
|
|
Pick<TagProps, 'prefixIcon' | 'color'> & { text: I18nKeysNoOptionsType }
|
|
> = {
|
|
[PublishStatus.Publishing]: {
|
|
prefixIcon: <IconCozClock />,
|
|
color: 'brand',
|
|
text: 'project_releasing',
|
|
},
|
|
[PublishStatus.Failed]: {
|
|
prefixIcon: <IconCozCrossCircle />,
|
|
color: 'red',
|
|
text: 'project_release_failed',
|
|
},
|
|
[PublishStatus.Success]: {
|
|
prefixIcon: <IconCozCheckMarkCircle />,
|
|
color: 'green',
|
|
text: 'project_release_success',
|
|
},
|
|
};
|
|
|
|
function toPublishStatus(record: PublishRecordDetail) {
|
|
const projectFailed =
|
|
record.publish_status === PublishRecordStatus.PackFailed ||
|
|
record.publish_status === PublishRecordStatus.AuditNotPass;
|
|
const connectorsFailed =
|
|
record.connector_publish_result?.some(
|
|
item => item.connector_publish_status === ConnectorPublishStatus.Failed,
|
|
) ?? false;
|
|
// The project itself failed, or some channels failed to publish - > overall failed
|
|
if (projectFailed || connectorsFailed) {
|
|
return PublishStatus.Failed;
|
|
}
|
|
const projectPublishing =
|
|
record.publish_status === PublishRecordStatus.Packing ||
|
|
record.publish_status === PublishRecordStatus.Auditing ||
|
|
record.publish_status === PublishRecordStatus.ConnectorPublishing;
|
|
const connectorsPublishing =
|
|
record.connector_publish_result?.some(
|
|
item =>
|
|
item.connector_publish_status === ConnectorPublishStatus.Default ||
|
|
item.connector_publish_status === ConnectorPublishStatus.Auditing,
|
|
) ?? false;
|
|
// The project itself is being released, or some channels are being released - > the overall release is in progress
|
|
if (projectPublishing || connectorsPublishing) {
|
|
return PublishStatus.Publishing;
|
|
}
|
|
return PublishStatus.Success;
|
|
}
|
|
|
|
export interface ProjectPublishStatusProps {
|
|
spaceId: string;
|
|
projectId: string;
|
|
defaultRecordID?: string;
|
|
}
|
|
|
|
/* eslint @coze-arch/max-line-per-function: ["error", {"max": 300}] */
|
|
export function usePublishStatus({
|
|
spaceId,
|
|
projectId,
|
|
defaultRecordID,
|
|
}: ProjectPublishStatusProps) {
|
|
const [status, setStatus] = useState<PublishStatus | undefined>();
|
|
const [latestRecord, setLatestRecord] = useState<PublishRecordDetail>();
|
|
const [recordList, setRecordList] = useState<OptionProps[]>([]);
|
|
const [selectedVersion, setSelectedVersion] = useState(defaultRecordID);
|
|
const [selectedRecord, setSelectedRecord] = useState<PublishRecordDetail>();
|
|
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
|
|
// Polling the latest release history until it stops when it is not in the "In Release" state
|
|
const latestRecordRequest = useRequest(
|
|
() => intelligenceApi.GetPublishRecordDetail({ project_id: projectId }),
|
|
{
|
|
manual: true,
|
|
pollingInterval: 5000,
|
|
pollingWhenHidden: false,
|
|
pollingErrorRetryCount: 3,
|
|
onSuccess: res => {
|
|
const record = res.data;
|
|
// Stop polling when no record is published
|
|
if (!record || typeof record.publish_status !== 'number') {
|
|
latestRecordRequest.cancel();
|
|
return;
|
|
}
|
|
setStatus(toPublishStatus(record));
|
|
setLatestRecord(record);
|
|
// After first requesting the latest release record, its version number is selected by default
|
|
if (!selectedVersion) {
|
|
setRecordList([
|
|
{ value: record.publish_record_id, label: record.version_number },
|
|
]);
|
|
setSelectedVersion(record.publish_record_id ?? '');
|
|
} else if (selectedVersion === record.publish_record_id) {
|
|
setSelectedRecord(record);
|
|
}
|
|
if (isPublishFinish(record)) {
|
|
latestRecordRequest.cancel();
|
|
}
|
|
},
|
|
},
|
|
);
|
|
|
|
// Get a list of publication records
|
|
const recordListRequest = useRequest(
|
|
() => intelligenceApi.GetPublishRecordList({ project_id: projectId }),
|
|
{
|
|
manual: true,
|
|
onSuccess: res => {
|
|
setRecordList(
|
|
res.data?.map(item => ({
|
|
value: item.publish_record_id,
|
|
label: item.version_number,
|
|
})) ?? [],
|
|
);
|
|
},
|
|
},
|
|
);
|
|
|
|
const hasPermission = useProjectAuth(
|
|
EProjectPermission.PUBLISH,
|
|
projectId,
|
|
spaceId,
|
|
);
|
|
|
|
// When the user has "publish" permission, start polling
|
|
useEffect(() => {
|
|
if (!hasPermission || defaultRecordID) {
|
|
return;
|
|
}
|
|
latestRecordRequest.run();
|
|
}, [hasPermission, defaultRecordID]);
|
|
|
|
// Manually request selected release records
|
|
const recordDetailRequest = useRequest(
|
|
(recordId: string) =>
|
|
intelligenceApi.GetPublishRecordDetail({
|
|
project_id: projectId,
|
|
publish_record_id: recordId,
|
|
}),
|
|
{
|
|
manual: true,
|
|
onSuccess: res => {
|
|
const record = res.data;
|
|
setSelectedRecord(record);
|
|
if (record?.publish_record_id === latestRecord?.publish_record_id) {
|
|
setLatestRecord(record);
|
|
}
|
|
},
|
|
},
|
|
);
|
|
|
|
const tagConfig = PublishStatusMap[status ?? PublishStatus.Failed];
|
|
const showingRecord = selectedRecord ?? latestRecord;
|
|
|
|
const open = async () => {
|
|
await recordListRequest.runAsync();
|
|
if (defaultRecordID) {
|
|
await changeVersion(defaultRecordID);
|
|
}
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const close = () => {
|
|
setModalVisible(false);
|
|
};
|
|
|
|
const changeVersion = async (version: string) => {
|
|
setSelectedVersion(version);
|
|
await recordDetailRequest.run(version);
|
|
};
|
|
|
|
return {
|
|
latestVersion: latestRecord,
|
|
currentVersion: recordList.find(item => item.value === selectedVersion),
|
|
|
|
open,
|
|
|
|
close,
|
|
|
|
modal: (
|
|
<Modal
|
|
title={I18n.t('project_release_stage')}
|
|
visible={modalVisible}
|
|
footer={null}
|
|
onCancel={() => setModalVisible(false)}
|
|
>
|
|
<div className="flex flex-col gap-[16px]">
|
|
<Select
|
|
className="w-full"
|
|
optionList={recordList}
|
|
value={selectedVersion}
|
|
onSelect={version => {
|
|
if (typeof version === 'string') {
|
|
changeVersion(version);
|
|
}
|
|
}}
|
|
/>
|
|
{showingRecord ? (
|
|
<ProjectPublishProgress record={showingRecord} />
|
|
) : null}
|
|
</div>
|
|
</Modal>
|
|
),
|
|
tag: (
|
|
<Tag
|
|
size="mini"
|
|
prefixIcon={tagConfig.prefixIcon}
|
|
color={tagConfig.color}
|
|
// The Tag component defaults to display: inline-flex, and the outer span line-height > 1 will cause its height to be larger than the Tag itself
|
|
className="flex !px-[3px] font-medium"
|
|
>
|
|
{I18n.t(tagConfig.text)}
|
|
</Tag>
|
|
),
|
|
};
|
|
}
|