= [
{
@@ -285,13 +431,29 @@ const GlossaryTermTab = ({
}),
render: (_, record) => {
const status = record.status ?? Status.Approved;
+ const termFQN = record.fullyQualifiedName ?? '';
+ const { permission, taskId } = permissionForApproveOrReject(
+ record,
+ currentUser as User,
+ termTaskThreads
+ );
return (
-
+
+ {status === Status.InReview && permission ? (
+ handleApproveGlossaryTerm(taskId, termFQN)}
+ onReject={() => handleRejectGlossaryTerm(taskId, termFQN)}
+ />
+ ) : (
+
+ )}
+
);
},
onFilter: (value, record) => record.status === value,
@@ -348,7 +510,13 @@ const GlossaryTermTab = ({
}
return data;
- }, [permissions, tableColumnsWidth]);
+ }, [
+ permissions,
+ tableColumnsWidth,
+ termTaskThreads,
+ handleApproveGlossaryTerm,
+ handleRejectGlossaryTerm,
+ ]);
const handleCheckboxChange = useCallback(
(key: string, checked: boolean) => {
@@ -485,22 +653,6 @@ const GlossaryTermTab = ({
const extraTableFilters = useMemo(() => {
return (
<>
-
{
@@ -523,6 +675,22 @@ const GlossaryTermTab = ({
+
>
);
}, [isAllExpanded, isStatusDropdownVisible, statusDropdownMenu]);
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/StatusAction.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/StatusAction.tsx
new file mode 100644
index 00000000000..0292ecf5b84
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/StatusAction.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2025 Collate.
+ * 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 Icon from '@ant-design/icons';
+import { Button } from 'antd';
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { ReactComponent as CloseCircleIcon } from '../../../assets/svg/close-circle-white.svg';
+import { ReactComponent as TickCircleIcon } from '../../../assets/svg/tick-circle-white.svg';
+import './status-action.less';
+
+interface StatusActionProps {
+ onApprove: () => void;
+ onReject: () => void;
+ dataTestId?: string;
+}
+
+const StatusAction = ({
+ onApprove,
+ onReject,
+ dataTestId,
+}: StatusActionProps) => {
+ const { t } = useTranslation();
+ const [isRejectHovered, setIsRejectHovered] = useState(false);
+
+ return (
+
+ }
+ onClick={onApprove}>
+ {!isRejectHovered && (
+ {t('label.approve')}
+ )}
+
+ }
+ onClick={onReject}
+ onMouseEnter={() => setIsRejectHovered(true)}
+ onMouseLeave={() => setIsRejectHovered(false)}>
+ {isRejectHovered && (
+ {t('label.reject')}
+ )}
+
+
+ );
+};
+
+export default StatusAction;
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/status-action.less b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/status-action.less
new file mode 100644
index 00000000000..ec303da75ab
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusAction/status-action.less
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2025 Collate.
+ * 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 url('../../../styles/variables.less');
+
+.approve-btn,
+.reject-btn {
+ &.ant-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ transition: width 0.3s ease;
+ border-radius: 8px;
+ padding: 4px 12px;
+ font-size: 12px;
+ font-weight: 500;
+ height: 32px;
+
+ .anticon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .btn-text {
+ margin-left: 4px;
+ white-space: nowrap;
+ transition: opacity 0.3s ease;
+ }
+ }
+}
+
+.approve-btn {
+ &.ant-btn {
+ background-color: @blue-18;
+ color: @white;
+ border: 1px solid @blue-19;
+ width: 100px;
+ padding: 4px 12px;
+
+ &.icon-only {
+ width: 32px;
+ padding: 4px;
+
+ .btn-text {
+ opacity: 0;
+ width: 0;
+ margin-left: 0;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: @blue-18;
+ border-color: @blue-19;
+ color: @white;
+ }
+ }
+}
+
+.reject-btn {
+ &.ant-btn {
+ background-color: @red-15;
+ color: @white;
+ border: 1px solid @red-4;
+ width: 32px;
+ padding: 4px;
+
+ .btn-text {
+ opacity: 0;
+ width: 0;
+ margin-left: 0;
+ }
+
+ &.show-text {
+ width: 80px;
+ padding: 4px 12px;
+
+ .btn-text {
+ opacity: 1;
+ width: auto;
+ margin-left: 4px;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: @red-15;
+ border-color: @red-4;
+ color: @white;
+ }
+ }
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/StatusBadge.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/StatusBadge.component.tsx
index 5cafeed6ff7..9f035efad09 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/StatusBadge.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/StatusBadge.component.tsx
@@ -10,17 +10,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import Icon from '@ant-design/icons';
import classNames from 'classnames';
import React from 'react';
+import { ReactComponent as DeprecatedIcon } from '../../../assets/svg/arrow-down-colored.svg';
+import { ReactComponent as ApprovedIcon } from '../../../assets/svg/check-colored.svg';
+import { ReactComponent as DraftIcon } from '../../../assets/svg/clipboard-colored.svg';
+import { ReactComponent as InReviewIcon } from '../../../assets/svg/eye-colored.svg';
+import { ReactComponent as RejectedIcon } from '../../../assets/svg/x-colored.svg';
+import { Status } from '../../../generated/entity/data/glossaryTerm';
import './status-badge.less';
import { StatusBadgeProps } from './StatusBadge.interface';
+const icons = {
+ [Status.Approved]: ApprovedIcon,
+ [Status.Rejected]: RejectedIcon,
+ [Status.InReview]: InReviewIcon,
+ [Status.Draft]: DraftIcon,
+ [Status.Deprecated]: DeprecatedIcon,
+} as const;
+
const StatusBadge = ({ label, status, dataTestId }: StatusBadgeProps) => {
+ const StatusIcon = icons[label as Status];
+
return (
- {label}
+ {StatusIcon && }
+ {label}
);
};
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/status-badge.less b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/status-badge.less
index 56561cc19ee..038919e645a 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/status-badge.less
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/StatusBadge/status-badge.less
@@ -14,37 +14,46 @@
@import (reference) url('../../../styles/variables.less');
.status-badge {
- font-weight: 500;
- font-size: 10px;
- border: 1px solid;
- border-radius: 4px;
- padding: 4px 8px;
+ border-radius: 16px;
+ padding: 6px 12px;
text-align: center;
max-width: fit-content;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex-wrap: wrap;
&.success {
- color: @green-3;
- border-color: @green-3;
- background-color: @green-4;
+ color: @green-10;
+ background-color: @green-9;
}
&.failure {
- color: @red-3;
- border-color: @red-3;
- background-color: @red-4;
+ color: @red-10;
+ background-color: @red-9;
}
&.warning {
- color: @yellow-2;
- border-color: @yellow-2;
- background-color: @yellow-3;
+ color: @yellow-11;
+ background-color: @yellow-10;
}
&.started,
&.running {
- color: @purple-3;
- background-color: @purple-1;
- border: 1px solid @purple-3;
+ color: @orange-2;
+ background-color: @orange-1;
}
&.stopped {
- color: @grey-3;
- background-color: @grey-2;
- border: 1px solid @grey-3;
+ color: @grey-19;
+ background-color: @grey-9;
}
}
+
+.status-badge {
+ svg {
+ margin-top: 2px;
+ margin-bottom: -3px;
+ }
+}
+
+.status-badge-label {
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 20px;
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less
index f374d4df536..0ab8be5a1de 100644
--- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less
+++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less
@@ -61,6 +61,9 @@
@red-12: #f31260;
@red-13: #fef3f2;
@red-14: #fda29b;
+@red-15: #e52315;
+@orange-1: #fff6ed;
+@orange-2: #c4320a;
@purple-1: #f2edfd;
@purple-2: #7147e8;
@@ -82,6 +85,8 @@
@blue-15: #eaecf5;
@blue-16: #84caff;
@blue-17: #eff8ff;
+@blue-18: #0968da;
+@blue-19: #e3e3e3;
@partial-success-1: #06a4a4;
@partial-success-2: #bdeeee;
@@ -105,6 +110,10 @@
@grey-14: #a4a7ae;
@grey-15: #eaecf5;
@grey-16: #f4f5f7;
+@grey-17: #fafafa;
+@grey-18: #afb5d9;
+@grey-19: #363f72;
+
@text-grey-muted: @grey-4;
@font-size-base: 14px;
@box-shadow-base: 0px 2px 10px rgba(0, 0, 0, 0.12);
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.test.ts
index c3a3b5cebd5..7ab99e6d65e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.test.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.test.ts
@@ -364,7 +364,7 @@ describe('Glossary Utils - glossaryTermTableColumnsWidth', () => {
name: 400,
owners: 170,
reviewers: 330,
- status: 120,
+ status: 330,
synonyms: 330,
});
});
@@ -377,7 +377,7 @@ describe('Glossary Utils - glossaryTermTableColumnsWidth', () => {
name: 400,
owners: 170,
reviewers: 330,
- status: 120,
+ status: 330,
synonyms: 330,
});
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.tsx
index a64101a854b..febe56a7d03 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.tsx
@@ -36,6 +36,7 @@ import {
TermReference,
} from '../generated/entity/data/glossaryTerm';
import { Domain } from '../generated/entity/domains/domain';
+import { User } from '../generated/entity/teams/user';
import { calculatePercentageFromValue } from './CommonUtils';
import { getEntityName } from './EntityUtils';
import { VersionStatus } from './EntityVersionUtils.interface';
@@ -450,5 +451,28 @@ export const glossaryTermTableColumnsWidth = (
reviewers: calculatePercentageFromValue(tableWidth, 33),
synonyms: calculatePercentageFromValue(tableWidth, 33),
owners: calculatePercentageFromValue(tableWidth, 17),
- status: calculatePercentageFromValue(tableWidth, 12),
+ status: calculatePercentageFromValue(tableWidth, 33),
});
+
+export const getGlossaryEntityLink = (glossaryTermFQN: string) =>
+ `<#E::${EntityType.GLOSSARY_TERM}::${glossaryTermFQN}>`;
+
+export const permissionForApproveOrReject = (
+ record: ModifiedGlossaryTerm,
+ currentUser: User,
+ termTaskThreads: Record>
+) => {
+ const entityLink = getGlossaryEntityLink(record.fullyQualifiedName ?? '');
+ const taskThread = termTaskThreads[entityLink]?.find(
+ (thread) => thread.about === entityLink
+ );
+
+ const isReviewer = record.reviewers?.some(
+ (reviewer) => reviewer.id === currentUser?.id
+ );
+
+ return {
+ permission: taskThread && isReviewer,
+ taskId: taskThread?.task?.id,
+ };
+};