Skip to content

Commit

Permalink
User and applications suggestions for entities (#15345)
Browse files Browse the repository at this point in the history
* add metapilot app oss side config

* add metapilot app oss side config

* suggestions changes

* locales

* pushing progress

* localisation

* add suggestions count button

* locales

* fix tests

* add tests

* fix sonar issues

* fix tests and cleanup

* fix unit tests

(cherry picked from commit 14f280b)
  • Loading branch information
karanh37 committed Mar 20, 2024
1 parent ff3c73e commit b5f0baa
Show file tree
Hide file tree
Showing 45 changed files with 1,223 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { interceptURL } from '../common';

/**
* Try Performing login with the given username and password.
* Particularly used for testing login.
*
* @param {string} username - The username for login
* @param {string} password - The password for login
* @return {void}
*/
export const performLogin = (username, password) => {
cy.visit('/');
interceptURL('POST', '/api/v1/users/login', 'loginUser');
cy.get('[id="email"]').should('be.visible').clear().type(username);
cy.get('[id="password"]').should('be.visible').clear().type(password);

// Don't want to show any popup in the tests
cy.setCookie(`STAR_OMD_USER_${username.split('@')[0]}`, 'true');

// Get version and set cookie to hide version banner
cy.request({
method: 'GET',
url: `api/v1/system/version`,
}).then((res) => {
const version = res.body.version;
const versionCookie = `VERSION_${version
.split('-')[0]
.replaceAll('.', '_')}`;

cy.setCookie(versionCookie, 'true');
window.localStorage.setItem('loggedInUsers', username.split('@')[0]);
});

cy.get('.ant-btn').contains('Login').should('be.visible').click();
cy.wait('@loginUser');
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { interceptURL, verifyResponseStatusCode } from '../../common/common';
import { SidebarItem } from '../../constants/Entity.interface';

describe('Explore Page', { tags: 'DataAssets' }, () => {
describe.skip('Explore Page', { tags: 'DataAssets' }, () => {
before(() => {
cy.login();
});
Expand Down
10 changes: 10 additions & 0 deletions openmetadata-ui/src/main/resources/ui/cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"rootDir": "./",
"downlevelIteration": true,
"target": "ES5",
"lib": ["dom", "dom.iterable", "ES2020.Promise", "es2021"],
"types": ["cypress", "node", "@cypress/grep"]
},
"include": ["./**/*.ts"]
}
1 change: 1 addition & 0 deletions openmetadata-ui/src/main/resources/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"crypto-random-string-with-promisify-polyfill": "^5.0.0",
"dagre": "^0.8.5",
"diff": "^5.0.0",
"eventemitter3": "^5.0.1",
"fast-json-patch": "^3.1.1",
"history": "4.5.1",
"html-react-parser": "^1.4.14",
Expand Down
13 changes: 8 additions & 5 deletions openmetadata-ui/src/main/resources/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { AuthProvider } from './components/Auth/AuthProviders/AuthProvider';
import ErrorBoundary from './components/common/ErrorBoundary/ErrorBoundary';
import DomainProvider from './components/Domain/DomainProvider/DomainProvider';
import { EntityExportModalProvider } from './components/Entity/EntityExportModalProvider/EntityExportModalProvider.component';
import ApplicationsProvider from './components/Settings/Applications/ApplicationsProvider/ApplicationsProvider';
import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider';
import { TOAST_OPTIONS } from './constants/Toasts.constants';
import DirectionProvider from './context/DirectionProvider/DirectionProvider';
Expand Down Expand Up @@ -80,11 +81,13 @@ const App: FC = () => {
<PermissionProvider>
<WebSocketProvider>
<GlobalSearchProvider>
<DomainProvider>
<EntityExportModalProvider>
<AppRouter />
</EntityExportModalProvider>
</DomainProvider>
<ApplicationsProvider>
<DomainProvider>
<EntityExportModalProvider>
<AppRouter />
</EntityExportModalProvider>
</DomainProvider>
</ApplicationsProvider>
</GlobalSearchProvider>
</WebSocketProvider>
</PermissionProvider>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 React, { FC } from 'react';
import SuggestionsProvider from '../Suggestions/SuggestionsProvider/SuggestionsProvider';

export const withSuggestions =
(Component: FC) =>
(props: JSX.IntrinsicAttributes & { children?: React.ReactNode }) => {
return (
<SuggestionsProvider>
<Component {...props} />
</SuggestionsProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
* limitations under the License.
*/

import React, { Suspense } from 'react';
import React, { FC, Suspense } from 'react';
import Loader from '../common/Loader/Loader';

export default function withSuspenseFallback(Component) {
return function DefaultFallback(props) {
export default function withSuspenseFallback<T extends unknown>(
Component: FC<T>
) {
return function DefaultFallback(
props: JSX.IntrinsicAttributes & { children?: React.ReactNode } & T
) {
return (
<Suspense
fallback={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
* limitations under the License.
*/

import { Button } from 'antd';
import { t } from 'i18next';
import { lowerCase } from 'lodash';
import React, { Fragment, FunctionComponent, useState } from 'react';
import React, { Fragment, FunctionComponent, useMemo, useState } from 'react';
import EntityLink from '../../../utils/EntityLink';
import Searchbar from '../../common/SearchBarComponent/SearchBar.component';
import { useSuggestionsContext } from '../../Suggestions/SuggestionsProvider/SuggestionsProvider';
import SchemaTable from '../SchemaTable/SchemaTable.component';
import { Props } from './SchemaTab.interfaces';

Expand All @@ -32,23 +35,38 @@ const SchemaTab: FunctionComponent<Props> = ({
tablePartitioned,
}: Props) => {
const [searchText, setSearchText] = useState('');
const { selectedUserSuggestions } = useSuggestionsContext();

const handleSearchAction = (searchValue: string) => {
setSearchText(searchValue);
};

const columnSuggestions = useMemo(
() =>
selectedUserSuggestions?.filter(
(item) => EntityLink.getTableColumnName(item.entityLink) !== undefined
) ?? [],
[selectedUserSuggestions]
);

return (
<Fragment>
<div className="w-1/2">
<Searchbar
removeMargin
placeholder={`${t('message.find-in-table')}`}
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
<div className="d-flex items-center justify-between">
<div className="w-1/2">
<Searchbar
removeMargin
placeholder={`${t('message.find-in-table')}`}
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
</div>
{columnSuggestions.length > 0 && (
<Button className="suggestion-pending-btn">
{columnSuggestions.length} {t('label.suggestion-pending')}
</Button>
)}
</div>

<SchemaTable
columnName={columnName}
entityFqn={entityFqn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@
*/

import { Button, Space, Tooltip } from 'antd';
import React from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg';
import { DE_ACTIVE_COLOR, ICON_DIMENSION } from '../../../constants/constants';
import { EntityField } from '../../../constants/Feeds.constants';
import { EntityType } from '../../../enums/entity.enum';
import EntityTasks from '../../../pages/TasksPage/EntityTasks/EntityTasks.component';
import EntityLink from '../../../utils/EntityLink';
import { getEntityFeedLink } from '../../../utils/EntityUtils';
import RichTextEditorPreviewer from '../../common/RichTextEditor/RichTextEditorPreviewer';
import SuggestionsAlert from '../../Suggestions/SuggestionsAlert/SuggestionsAlert';
import { useSuggestionsContext } from '../../Suggestions/SuggestionsProvider/SuggestionsProvider';
import { TableDescriptionProps } from './TableDescription.interface';

const TableDescription = ({
Expand All @@ -32,23 +37,62 @@ const TableDescription = ({
onThreadLinkSelect,
}: TableDescriptionProps) => {
const { t } = useTranslation();
const { selectedUserSuggestions = [] } = useSuggestionsContext();

return (
<Space
className="hover-icon-group"
data-testid="description"
direction="vertical"
id={`field-description-${index}`}>
{columnData.field ? (
<RichTextEditorPreviewer markdown={columnData.field} />
) : (
const entityLink = useMemo(
() =>
entityType === EntityType.TABLE
? EntityLink.getTableEntityLink(
entityFqn,
columnData.record?.name ?? ''
)
: getEntityFeedLink(entityType, columnData.fqn),
[entityType, entityFqn]
);

const suggestionData = useMemo(() => {
const activeSuggestion = selectedUserSuggestions.find(
(suggestion) => suggestion.entityLink === entityLink
);

if (activeSuggestion?.entityLink === entityLink) {
return (
<SuggestionsAlert
hasEditAccess={hasEditPermission}
maxLength={40}
suggestion={activeSuggestion}
/>
);
}

return null;
}, [hasEditPermission, entityLink, selectedUserSuggestions]);

const descriptionContent = useMemo(() => {
if (suggestionData) {
return suggestionData;
} else if (columnData.field) {
return <RichTextEditorPreviewer markdown={columnData.field} />;
} else {
return (
<span className="text-grey-muted">
{t('label.no-entity', {
entity: t('label.description'),
})}
</span>
)}
{!isReadOnly ? (
);
}
}, [columnData, suggestionData]);

return (
<Space
className="hover-icon-group w-full"
data-testid="description"
direction="vertical"
id={`field-description-${index}`}>
{descriptionContent}

{!suggestionData && !isReadOnly ? (
<Space align="baseline" size="middle">
{hasEditPermission && (
<Tooltip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { ReactComponent as IconExternalLink } from '../../../../assets/svg/exter
import { ReactComponent as DeleteIcon } from '../../../../assets/svg/ic-delete.svg';
import { ReactComponent as IconRestore } from '../../../../assets/svg/ic-restore.svg';
import { ReactComponent as IconDropdown } from '../../../../assets/svg/menu.svg';
import { APP_UI_SCHEMA } from '../../../../constants/Applications.constant';

import { DE_ACTIVE_COLOR } from '../../../../constants/constants';
import { GlobalSettingOptions } from '../../../../constants/GlobalSettings.constants';
import { ServiceCategory } from '../../../../enums/service.enum';
Expand Down Expand Up @@ -93,6 +93,7 @@ const AppDetails = () => {
isRunLoading: false,
isSaveLoading: false,
});
const UiSchema = applicationSchemaClassBase.getJSONUISchema();

const fetchAppDetails = useCallback(async () => {
setLoadingState((prev) => ({ ...prev, isFetchLoading: true }));
Expand Down Expand Up @@ -347,7 +348,7 @@ const AppDetails = () => {
okText={t('label.submit')}
schema={jsonSchema}
serviceCategory={ServiceCategory.DASHBOARD_SERVICES}
uiSchema={APP_UI_SCHEMA}
uiSchema={UiSchema}
validator={validator}
onCancel={noop}
onSubmit={onConfigSave}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ jest.mock('../AppSchedule/AppSchedule.component', () =>

jest.mock('./ApplicationSchemaClassBase', () => ({
importSchema: jest.fn().mockReturnValue({ default: ['table'] }),
getJSONUISchema: jest.fn().mockReturnValue({}),
}));

jest.mock('react-router-dom', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class ApplicationSchemaClassBase {
public importSchema(fqn: string) {
return import(`../../../../utils/ApplicationSchemas/${fqn}.json`);
}
public getJSONUISchema() {
return {};
}
}

const applicationSchemaClassBase = new ApplicationSchemaClassBase();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { App } from '../../../../generated/entity/applications/app';

export type ApplicationsContextType = {
applications: App[];
loading: boolean;
};
Loading

0 comments on commit b5f0baa

Please sign in to comment.