From 3bcc908bdfca7f4b32e3e4399e395d3cb3c7d3d0 Mon Sep 17 00:00:00 2001
From: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
Date: Wed, 3 Apr 2024 12:30:10 +0530
Subject: [PATCH] Applications minor fixes (#15771)
* change button icon
* hide uninstall for system apps
* hide popover on clicking on avatar
* show badge count always
* cleanup
* unit tests
---
.../resources/ui/src/assets/svg/ic-exit.svg | 11 ++
.../AppDetails/AppDetails.component.tsx | 40 +++---
.../SuggestionsAlert/suggestions-alert.less | 4 +
.../SuggestionsSlider/SuggestionsSlider.tsx | 7 +-
.../AvatarCarousel/AvatarCarousel.test.tsx | 55 ++++----
.../common/AvatarCarousel/AvatarCarousel.tsx | 56 ++++-----
.../AvatarCarousel/avatar-carousel.less | 2 +-
.../AvatarCarouselItem.test.tsx | 119 ++++++++++++++++++
.../AvatarCarouselItem/AvatarCarouselItem.tsx | 68 ++++++++++
9 files changed, 281 insertions(+), 81 deletions(-)
create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-exit.svg
create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.test.tsx
create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.tsx
diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-exit.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-exit.svg
new file mode 100644
index 00000000000..058c8c553d0
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-exit.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
index 57a138ffe6c..380ba61870c 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx
@@ -205,24 +205,28 @@ const AppDetails = () => {
},
},
]),
- {
- label: (
-
- ),
- key: 'uninstall-button',
- onClick: () => {
- setShowDeleteModel(true);
- setShowActions(false);
- setAction(AppAction.UNINSTALL);
- },
- },
+ ...(appData?.system
+ ? []
+ : [
+ {
+ label: (
+
+ ),
+ key: 'uninstall-button',
+ onClick: () => {
+ setShowDeleteModel(true);
+ setShowActions(false);
+ setAction(AppAction.UNINSTALL);
+ },
+ },
+ ]),
];
const onConfigSave = async (data: IChangeEvent) => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsAlert/suggestions-alert.less b/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsAlert/suggestions-alert.less
index 8c0d0ad0901..99afb2ab186 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsAlert/suggestions-alert.less
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsAlert/suggestions-alert.less
@@ -42,3 +42,7 @@
td .suggested-alert-footer {
padding: 4px 12px;
}
+
+.close-suggestion-btn {
+ gap: 6px;
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsSlider/SuggestionsSlider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsSlider/SuggestionsSlider.tsx
index 810b49e81df..d7b07573218 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsSlider/SuggestionsSlider.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Suggestions/SuggestionsSlider/SuggestionsSlider.tsx
@@ -14,6 +14,7 @@ import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Space, Typography } from 'antd';
import { t } from 'i18next';
import React from 'react';
+import { ReactComponent as ExitIcon } from '../../../assets/svg/ic-exit.svg';
import { SuggestionType } from '../../../generated/entity/feed/suggestion';
import AvatarCarousel from '../../common/AvatarCarousel/AvatarCarousel';
import { useSuggestionsContext } from '../SuggestionsProvider/SuggestionsProvider';
@@ -35,7 +36,7 @@ const SuggestionsSlider = () => {
{selectedUserSuggestions.length > 0 && (
-
+
}
type="primary"
onClick={() => onUpdateActiveUser()}>
+
{t('label.close')}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.test.tsx
index 9edf8baf92e..ebdddcf238c 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.test.tsx
@@ -14,22 +14,30 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import AvatarCarousel from './AvatarCarousel';
+const suggestions = [
+ {
+ id: '1',
+ description: 'Test suggestion',
+ createdBy: { id: '1', name: 'Avatar 1', type: 'user' },
+ entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
+ },
+ {
+ id: '2',
+ description: 'Test suggestion',
+ createdBy: { id: '2', name: 'Avatar 2', type: 'user' },
+ entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
+ },
+];
+
+const suggByUser = new Map([
+ ['Avatar 1', [suggestions[0]]],
+ ['Avatar 2', [suggestions[1]]],
+]);
+
jest.mock('../../Suggestions/SuggestionsProvider/SuggestionsProvider', () => ({
useSuggestionsContext: jest.fn().mockImplementation(() => ({
- suggestions: [
- {
- id: '1',
- description: 'Test suggestion',
- createdBy: { id: '1', name: 'Avatar 1', type: 'user' },
- entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
- },
- {
- id: '2',
- description: 'Test suggestion',
- createdBy: { id: '2', name: 'Avatar 2', type: 'user' },
- entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
- },
- ],
+ suggestions: suggestions,
+ suggestionsByUser: suggByUser,
allSuggestionsUsers: [
{ id: '1', name: 'Avatar 1', type: 'user' },
{ id: '2', name: 'Avatar 2', type: 'user' },
@@ -51,22 +59,9 @@ jest.mock('../ProfilePicture/ProfilePicture', () =>
);
jest.mock('../../../rest/suggestionsAPI', () => ({
- getSuggestionsList: jest.fn().mockImplementation(() =>
- Promise.resolve([
- {
- id: '1',
- description: 'Test suggestion',
- createdBy: { id: '1', name: 'Avatar 1', type: 'user' },
- entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
- },
- {
- id: '2',
- description: 'Test suggestion',
- createdBy: { id: '1', name: 'Avatar 2', type: 'user' },
- entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
- },
- ])
- ),
+ getSuggestionsList: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve(suggestions)),
}));
describe('AvatarCarousel', () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.tsx
index 58e52d48f4b..21e95fd74ad 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/AvatarCarousel.tsx
@@ -11,12 +11,16 @@
* limitations under the License.
*/
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
-import { Badge, Button, Carousel } from 'antd';
-import classNames from 'classnames';
-import React, { useCallback, useEffect, useState } from 'react';
+import { Button, Carousel } from 'antd';
+import React, {
+ RefObject,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import { useSuggestionsContext } from '../../Suggestions/SuggestionsProvider/SuggestionsProvider';
-import UserPopOverCard from '../PopOverCard/UserPopOverCard';
-import ProfilePicture from '../ProfilePicture/ProfilePicture';
+import AvatarCarouselItem from '../AvatarCarouselItem/AvatarCarouselItem';
import './avatar-carousel.less';
interface AvatarCarouselProps {
@@ -30,6 +34,7 @@ const AvatarCarousel = ({ showArrows = false }: AvatarCarouselProps) => {
selectedUserSuggestions,
} = useSuggestionsContext();
const [currentSlide, setCurrentSlide] = useState(-1);
+ const avatarBtnRefs = useRef[]>([]);
const prevSlide = useCallback(() => {
setCurrentSlide((prev) => (prev === 0 ? avatarList.length - 1 : prev - 1));
@@ -39,10 +44,17 @@ const AvatarCarousel = ({ showArrows = false }: AvatarCarouselProps) => {
setCurrentSlide((prev) => (prev === avatarList.length - 1 ? 0 : prev + 1));
}, [avatarList]);
+ const handleMouseOut = useCallback(() => {
+ avatarBtnRefs.current.forEach((ref: any) => {
+ ref.current?.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }));
+ });
+ }, [avatarBtnRefs]);
+
const onProfileClick = useCallback(
(index: number) => {
const activeUser = avatarList[index];
onUpdateActiveUser(activeUser);
+ handleMouseOut();
},
[avatarList]
);
@@ -75,30 +87,16 @@ const AvatarCarousel = ({ showArrows = false }: AvatarCarouselProps) => {
afterChange={(current) => setCurrentSlide(current)}
dots={false}
slidesToShow={avatarList.length < 3 ? avatarList.length : 3}>
- {avatarList.map((avatar, index) => {
- const isActive = currentSlide === index;
-
- const button = (
-
- );
-
- return (
-
- {isActive ? ( // Show Badge only for active item
- {button}
- ) : (
- button
- )}
-
- );
- })}
+ {avatarList.map((avatar, index) => (
+
+ ))}
{showArrows && (
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/avatar-carousel.less b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/avatar-carousel.less
index a3059609a85..52be99a10d6 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/avatar-carousel.less
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarousel/avatar-carousel.less
@@ -29,7 +29,7 @@
.avatar-carousel-container {
.slick-slide {
- width: 32px !important;
+ width: 28px !important;
}
.slick-list {
overflow: visible !important;
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.test.tsx
new file mode 100644
index 00000000000..d0eefcd2203
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.test.tsx
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2024 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 { render } from '@testing-library/react';
+import React from 'react';
+import { EntityReference } from '../../../generated/entity/type';
+import AvatarCarouselItem from './AvatarCarouselItem';
+
+const suggestions = [
+ {
+ id: '1',
+ description: 'Test suggestion',
+ createdBy: { id: '1', name: 'Avatar 1', type: 'user' },
+ entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
+ },
+ {
+ id: '2',
+ description: 'Test suggestion',
+ createdBy: { id: '2', name: 'Avatar 2', type: 'user' },
+ entityLink: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
+ },
+];
+
+const suggByUser = new Map([
+ ['Avatar 1', [suggestions[0]]],
+ ['Avatar 2', [suggestions[1]]],
+]);
+
+jest.mock('../../Suggestions/SuggestionsProvider/SuggestionsProvider', () => ({
+ useSuggestionsContext: jest.fn().mockImplementation(() => ({
+ suggestions: suggestions,
+ suggestionsByUser: suggByUser,
+ allSuggestionsUsers: [
+ { id: '1', name: 'Avatar 1', type: 'user' },
+ { id: '2', name: 'Avatar 2', type: 'user' },
+ ],
+ acceptRejectSuggestion: jest.fn(),
+ selectedUserSuggestions: [],
+ onUpdateActiveUser: jest.fn(),
+ })),
+ __esModule: true,
+ default: 'SuggestionsProvider',
+}));
+
+jest.mock('../../../rest/suggestionsAPI', () => ({
+ getSuggestionsList: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve(suggestions)),
+}));
+
+describe('AvatarCarouselItem', () => {
+ const avatar: EntityReference = {
+ id: '1',
+ name: 'Test Avatar',
+ type: 'user',
+ };
+ const index = 0;
+ const onAvatarClick = jest.fn();
+ const avatarBtnRefs = { current: [] };
+ const isActive = false;
+
+ it('renders AvatarCarouselItem with ProfilePicture component', () => {
+ const { getByTestId } = render(
+
+ );
+
+ expect(
+ getByTestId(`avatar-carousel-item-${avatar.id}`)
+ ).toBeInTheDocument();
+ });
+
+ it('calls onAvatarClick function when clicked', () => {
+ const { getByTestId } = render(
+
+ );
+
+ const button = getByTestId(`avatar-carousel-item-${avatar.id}`);
+ button.click();
+
+ expect(onAvatarClick).toHaveBeenCalledWith(index);
+ });
+
+ it('sets isActive class when isActive is true', () => {
+ const { getByTestId } = render(
+
+ );
+
+ expect(getByTestId(`avatar-carousel-item-${avatar.id}`)).toHaveClass(
+ 'active'
+ );
+ });
+});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.tsx
new file mode 100644
index 00000000000..0863b84970f
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AvatarCarouselItem/AvatarCarouselItem.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 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 { Badge, Button } from 'antd';
+import classNames from 'classnames';
+import React, { RefObject, useCallback, useRef } from 'react';
+import { EntityReference } from '../../../generated/entity/type';
+import { useSuggestionsContext } from '../../Suggestions/SuggestionsProvider/SuggestionsProvider';
+import UserPopOverCard from '../PopOverCard/UserPopOverCard';
+import ProfilePicture from '../ProfilePicture/ProfilePicture';
+
+interface AvatarCarouselItemProps {
+ avatar: EntityReference;
+ index: number;
+ onAvatarClick: (index: number) => void;
+ avatarBtnRefs: React.MutableRefObject[]>;
+ isActive: boolean;
+}
+
+const AvatarCarouselItem = ({
+ avatar,
+ index,
+ avatarBtnRefs,
+ onAvatarClick,
+ isActive,
+}: AvatarCarouselItemProps) => {
+ const { suggestionsByUser } = useSuggestionsContext();
+ const buttonRef = useRef(null);
+ avatarBtnRefs.current[index] = buttonRef;
+ const getUserSuggestionsCount = useCallback(
+ (userName: string) => {
+ return suggestionsByUser.get(userName) ?? [];
+ },
+ [suggestionsByUser]
+ );
+
+ const button = (
+
+ );
+
+ return (
+
+
+ {button}
+
+
+ );
+};
+
+export default AvatarCarouselItem;