mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-28 10:56:02 +00:00
Query extend (#15595)
* initial commit * add profile pic for bot * logo configurable for collate app * add query extras * fix tests * add tests
This commit is contained in:
parent
d6c7465b19
commit
bd208371a9
@ -38,6 +38,7 @@ import { useClipboard } from '../../../hooks/useClipBoard';
|
||||
import { useFqn } from '../../../hooks/useFqn';
|
||||
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
||||
import { parseSearchParams } from '../../../utils/Query/QueryUtils';
|
||||
import queryClassBase from '../../../utils/QueryClassBase';
|
||||
import { getQueryPath } from '../../../utils/RouterUtils';
|
||||
import SchemaEditor from '../SchemaEditor/SchemaEditor';
|
||||
import QueryCardExtraOption from './QueryCardExtraOption/QueryCardExtraOption.component';
|
||||
@ -59,6 +60,7 @@ const QueryCard: FC<QueryCardProp> = ({
|
||||
afterDeleteAction,
|
||||
}: QueryCardProp) => {
|
||||
const { t } = useTranslation();
|
||||
const QueryExtras = queryClassBase.getQueryExtras();
|
||||
const { fqn: datasetFQN } = useFqn();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
@ -169,7 +171,7 @@ const QueryCard: FC<QueryCardProp> = ({
|
||||
|
||||
return (
|
||||
<Row gutter={[0, 8]}>
|
||||
<Col span={24}>
|
||||
<Col span={isExpanded && QueryExtras ? 12 : 24}>
|
||||
<Card
|
||||
bordered={false}
|
||||
className={classNames(
|
||||
@ -247,8 +249,8 @@ const QueryCard: FC<QueryCardProp> = ({
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
<Row align="middle" className="p-y-xs border-top">
|
||||
<Col className="p-y-0.5 p-l-md" span={16}>
|
||||
<Row align="middle" className="p-y-md border-top">
|
||||
<Col className="p-l-md" span={16}>
|
||||
<QueryUsedByOtherTable
|
||||
isEditMode={isEditMode}
|
||||
query={query}
|
||||
@ -284,6 +286,7 @@ const QueryCard: FC<QueryCardProp> = ({
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{isExpanded && QueryExtras && <QueryExtras />}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
import { Button, Dropdown, MenuProps, Space, Tag, Tooltip } from 'antd';
|
||||
import { isUndefined, split } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
|
||||
import { ReactComponent as DeleteIcon } from '../../../../assets/svg/ic-delete.svg';
|
||||
@ -25,8 +25,13 @@ import { QueryVoteType } from '../TableQueries.interface';
|
||||
import { QueryCardExtraOptionProps } from './QueryCardExtraOption.interface';
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import Qs from 'qs';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
|
||||
import { useFqn } from '../../../../hooks/useFqn';
|
||||
import { deleteQuery } from '../../../../rest/queryAPI';
|
||||
import queryClassBase from '../../../../utils/QueryClassBase';
|
||||
import { getQueryPath } from '../../../../utils/RouterUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal';
|
||||
import './query-card-extra-option.style.less';
|
||||
@ -39,6 +44,9 @@ const QueryCardExtraOption = ({
|
||||
afterDeleteAction,
|
||||
}: QueryCardExtraOptionProps) => {
|
||||
const { EditAll, EditQueries, Delete } = permission;
|
||||
const { fqn: datasetFQN } = useFqn();
|
||||
const history = useHistory();
|
||||
const QueryHeaderButton = queryClassBase.getQueryHeaderActionsButtons();
|
||||
const { currentUser } = useApplicationStore();
|
||||
const { t } = useTranslation();
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
@ -57,6 +65,13 @@ const QueryCardExtraOption = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onExpandClick = useCallback(() => {
|
||||
history.push({
|
||||
search: Qs.stringify({ query: query.id }),
|
||||
pathname: getQueryPath(datasetFQN, query.id ?? ''),
|
||||
});
|
||||
}, [query]);
|
||||
|
||||
const dropdownItems = useMemo(() => {
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
@ -130,9 +145,14 @@ const QueryCardExtraOption = ({
|
||||
className="query-card-extra-option"
|
||||
data-testid="extra-option-container"
|
||||
size={8}>
|
||||
{QueryHeaderButton && (
|
||||
<QueryHeaderButton onClickHandler={onExpandClick} />
|
||||
)}
|
||||
|
||||
<Tag className="query-lines" data-testid="query-line">
|
||||
{queryLine}
|
||||
</Tag>
|
||||
|
||||
<Tooltip title={t('label.up-vote')}>
|
||||
<Button
|
||||
className="vote-button"
|
||||
|
@ -46,6 +46,10 @@ jest.mock('../../../../rest/queryAPI', () => ({
|
||||
deleteQuery: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../hooks/useFqn', () => ({
|
||||
useFqn: jest.fn().mockImplementation(() => ({ fqn: 'testFqn' })),
|
||||
}));
|
||||
|
||||
describe('QueryCardExtraOption component test', () => {
|
||||
it('Component should render', async () => {
|
||||
render(<QueryCardExtraOption {...mockProps} />);
|
||||
|
@ -18,6 +18,9 @@ class ApplicationSchemaClassBase {
|
||||
public getJSONUISchema() {
|
||||
return {};
|
||||
}
|
||||
public importAppLogo(appName: string) {
|
||||
return import(`../../../../assets/svg/${appName}.svg`);
|
||||
}
|
||||
}
|
||||
|
||||
const applicationSchemaClassBase = new ApplicationSchemaClassBase();
|
||||
|
@ -11,7 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Avatar } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import applicationSchemaClassBase from '../AppDetails/ApplicationSchemaClassBase';
|
||||
|
||||
const AppLogo = ({
|
||||
logo,
|
||||
@ -22,18 +23,21 @@ const AppLogo = ({
|
||||
}) => {
|
||||
const [appLogo, setAppLogo] = useState<JSX.Element | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLogo = useCallback(async () => {
|
||||
if (!logo) {
|
||||
import(`../../../../assets/svg/${appName}.svg`).then((data) => {
|
||||
const Icon = data.ReactComponent as React.ComponentType<
|
||||
JSX.IntrinsicElements['svg']
|
||||
>;
|
||||
setAppLogo(<Icon height={55} width={55} />);
|
||||
});
|
||||
const data = await applicationSchemaClassBase.importAppLogo(appName);
|
||||
const Icon = data.ReactComponent as React.ComponentType<
|
||||
JSX.IntrinsicElements['svg']
|
||||
>;
|
||||
setAppLogo(<Icon />);
|
||||
} else {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
}, [appName, logo]);
|
||||
}, [logo, appName]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLogo();
|
||||
}, [appName]);
|
||||
|
||||
return <Avatar className="flex-center bg-grey-1" icon={appLogo} size={100} />;
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
getImageWithResolutionAndFallback,
|
||||
ImageQuality,
|
||||
} from '../../utils/ProfilerUtils';
|
||||
import userClassBase from '../../utils/UserClassBase';
|
||||
import { useApplicationStore } from '../useApplicationStore';
|
||||
|
||||
let userProfilePicsLoading: string[] = [];
|
||||
@ -80,7 +81,11 @@ export const useUserProfile = ({
|
||||
});
|
||||
userProfilePicsLoading = userProfilePicsLoading.filter((p) => p !== name);
|
||||
|
||||
setProfilePic(profile);
|
||||
if (user.isBot) {
|
||||
setProfilePic(userClassBase.getBotLogo(user.name) ?? '');
|
||||
} else {
|
||||
setProfilePic(profile);
|
||||
}
|
||||
} catch (error) {
|
||||
// Error
|
||||
userProfilePicsLoading = userProfilePicsLoading.filter((p) => p !== name);
|
||||
|
@ -47,6 +47,7 @@
|
||||
@blue-1: #ebf6fe;
|
||||
@blue-2: #3ca2f4;
|
||||
@blue-3: #0950c5;
|
||||
@blue-4: #f1f9ff;
|
||||
@black: #000000;
|
||||
@aborted-color: #efae2f;
|
||||
@info-color: #2196f3;
|
||||
|
@ -10,8 +10,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { FC } from 'react';
|
||||
import DataProductsPage from '../components/DataProducts/DataProductsPage/DataProductsPage.component';
|
||||
import {
|
||||
getEditWebhookPath,
|
||||
@ -284,6 +286,10 @@ class EntityUtilClassBase {
|
||||
}
|
||||
}
|
||||
|
||||
public getEntityFloatingButton(_: EntityType): FC | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getManageExtraOptions(
|
||||
_entityType?: EntityType,
|
||||
_fqn?: string
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 queryClassBase from './QueryClassBase';
|
||||
|
||||
describe('QueryClassBase', () => {
|
||||
it('should return null from getQueryExtras', () => {
|
||||
const result = queryClassBase.getQueryExtras();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null from getQueryHeaderActionsButtons', () => {
|
||||
const result = queryClassBase.getQueryHeaderActionsButtons();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { FC } from 'react';
|
||||
|
||||
class QueryClassBase {
|
||||
public getQueryExtras(): FC | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getQueryHeaderActionsButtons(): FC<{
|
||||
onClickHandler: () => void;
|
||||
}> | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const queryClassBase = new QueryClassBase();
|
||||
|
||||
export default queryClassBase;
|
||||
export { QueryClassBase };
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 userClassBase from './UserClassBase';
|
||||
|
||||
describe('UserClassBase', () => {
|
||||
it('should return empty string from getBotLogo when botName is empty', () => {
|
||||
let result = userClassBase.getBotLogo('');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
result = userClassBase.getBotLogo('unknown');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
class UserClassBase {
|
||||
protected botLogos: Record<string, string> = {};
|
||||
public getBotLogo(botName: string) {
|
||||
return this.botLogos[botName];
|
||||
}
|
||||
}
|
||||
|
||||
const userClassBase = new UserClassBase();
|
||||
|
||||
export default userClassBase;
|
||||
export { UserClassBase };
|
Loading…
x
Reference in New Issue
Block a user