Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PUI] notifications fix #8392

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 273
INVENTREE_API_VERSION = 274

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """

v274 - 2024-10-29 : https://github.com/inventree/InvenTree/pull/8392
- Add more detailed information to NotificationEntry API serializer

v273 - 2024-10-28 : https://github.com/inventree/InvenTree/pull/8376
- Fixes for the BuildLine API endpoint

Expand Down
9 changes: 8 additions & 1 deletion src/backend/InvenTree/InvenTree/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,14 @@ def get_target(self, obj):
ret = {}
if url_fnc:
ret['link'] = url_fnc()
return {'name': str(item), 'model': str(model_cls._meta.verbose_name), **ret}

return {
'name': str(item),
'model_name': str(model_cls._meta.verbose_name),
'model_type': str(model_cls._meta.model_name),
'model_id': getattr(item, 'pk', None),
**ret,
}


Inheritors_T = TypeVar('Inheritors_T')
Expand Down
175 changes: 111 additions & 64 deletions src/frontend/src/components/nav/NotificationDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,98 @@ import { t } from '@lingui/macro';
import {
ActionIcon,
Alert,
Anchor,
Center,
Divider,
Drawer,
Group,
Loader,
Space,
Paper,
Stack,
Text,
Tooltip
} from '@mantine/core';
import { IconArrowRight, IconBellCheck } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';

import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { Boundary } from '../Boundary';
import { StylishText } from '../items/StylishText';
import { ModelInformationDict } from '../render/ModelType';

/**
* Render a single notification entry in the drawer
*/
function NotificationEntry({
notification,
onRead
}: {
notification: any;
onRead: () => void;
}) {
const navigate = useNavigate();

let link = notification.target?.link;

let model_type = notification.target?.model_type;
let model_id = notification.target?.model_id;

// If a valid model type is provided, that overrides the specified link
if (model_type as ModelType) {
let model_info = ModelInformationDict[model_type as ModelType];
if (model_info?.url_detail && model_id) {
link = getDetailUrl(model_type as ModelType, model_id);
} else if (model_info?.url_overview) {
link = model_info.url_overview;
}
}

return (
<Paper p="xs" shadow="xs">
<Group justify="space-between" wrap="nowrap">
<Tooltip
label={notification.message}
position="bottom-end"
hidden={!notification.message}
>
<Stack gap={2}>
<Anchor
href={link}
underline="hover"
target="_blank"
onClick={(event: any) => {
if (link) {
// Mark the notification as read
onRead();
}

if (link.startsWith('/')) {
navigateToLink(link, navigate, event);
}
}}
>
<Text size="sm">{notification.name}</Text>
</Anchor>
<Text size="xs">{notification.age_human}</Text>
</Stack>
</Tooltip>
<Tooltip label={t`Mark as read`} position="bottom-end">
<ActionIcon variant="transparent" onClick={onRead}>
<IconBellCheck />
</ActionIcon>
</Tooltip>
</Group>
</Paper>
);
}

/**
* Construct a notification drawer.
Expand All @@ -46,7 +117,8 @@ export function NotificationDrawer({
.get(apiUrl(ApiEndpoints.notifications_list), {
params: {
read: false,
limit: 10
limit: 10,
ordering: '-creation'
}
})
.then((response) => response.data)
Expand All @@ -73,6 +145,19 @@ export function NotificationDrawer({
});
}, []);

const markAsRead = useCallback((notification: any) => {
api
.patch(apiUrl(ApiEndpoints.notifications_list, notification.pk), {
read: true
})
.then(() => {
notificationQuery.refetch();
})
.catch(() => {
notificationQuery.refetch();
});
}, []);

return (
<Drawer
opened={opened}
Expand Down Expand Up @@ -117,67 +202,29 @@ export function NotificationDrawer({
</Group>
}
>
<Stack gap="xs">
<Divider />
{!hasNotifications && (
<Alert color="green">
<Text size="sm">{t`You have no unread notifications.`}</Text>
</Alert>
)}
{hasNotifications &&
notificationQuery.data?.results?.map((notification: any) => (
<Group justify="space-between" key={notification.pk}>
<Stack gap="3">
{notification?.target?.link ? (
<Text
size="sm"
component={Link}
to={notification?.target?.link}
target="_blank"
>
{notification.target?.name ??
notification.name ??
t`Notification`}
</Text>
) : (
<Text size="sm">
{notification.target?.name ??
notification.name ??
t`Notification`}
</Text>
)}
<Text size="xs">{notification.age_human ?? ''}</Text>
</Stack>
<Space />
<ActionIcon
color="gray"
variant="hover"
onClick={() => {
let url = apiUrl(
ApiEndpoints.notifications_list,
notification.pk
);
api
.patch(url, {
read: true
})
.then((response) => {
notificationQuery.refetch();
});
}}
>
<Tooltip label={t`Mark as read`}>
<IconBellCheck />
</Tooltip>
</ActionIcon>
</Group>
))}
{notificationQuery.isFetching && (
<Center>
<Loader size="sm" />
</Center>
)}
</Stack>
<Boundary label="NotificationDrawer">
<Stack gap="xs">
<Divider />
{!hasNotifications && (
<Alert color="green">
<Text size="sm">{t`You have no unread notifications.`}</Text>
</Alert>
)}
{hasNotifications &&
notificationQuery.data?.results?.map((notification: any) => (
<NotificationEntry
key={`notification-${notification.pk}`}
notification={notification}
onRead={() => markAsRead(notification)}
/>
))}
{notificationQuery.isFetching && (
<Center>
<Loader size="sm" />
</Center>
)}
</Stack>
</Boundary>
</Drawer>
);
}
13 changes: 10 additions & 3 deletions src/frontend/src/components/plugins/PluginDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { t } from '@lingui/macro';
import { Accordion, Alert, Card, Stack, Text } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';

import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { useInstance } from '../../hooks/UseInstance';
Expand All @@ -18,12 +19,18 @@ export default function PluginDrawer({
pluginKey,
pluginInstance
}: {
pluginKey: string;
pluginKey?: string;
pluginInstance: PluginInterface;
}) {
const { id } = useParams();

const pluginPrimaryKey: string = useMemo(() => {
return pluginKey || id || '';
}, [pluginKey, id]);

const { instance: pluginAdmin } = useInstance({
endpoint: ApiEndpoints.plugin_admin,
pathParams: { key: pluginKey },
pathParams: { key: pluginPrimaryKey },
defaultValue: {},
hasPrimaryKey: false,
refetchOnMount: true
Expand Down Expand Up @@ -128,7 +135,7 @@ export default function PluginDrawer({
</Accordion.Control>
<Accordion.Panel>
<Card withBorder>
<PluginSettingList pluginKey={pluginKey} />
<PluginSettingList pluginKey={pluginPrimaryKey} />
</Card>
</Accordion.Panel>
</Accordion.Item>
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/src/components/render/Generic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export function RenderContentType({
return instance && <RenderInlineModel primary={instance.app_labeled_name} />;
}

export function RenderError({
instance
}: Readonly<InstanceRenderInterface>): ReactNode {
return instance && <RenderInlineModel primary={instance.name} />;
}

export function RenderImportSession({
instance
}: {
Expand Down
4 changes: 3 additions & 1 deletion src/frontend/src/components/render/Instance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './Company';
import {
RenderContentType,
RenderError,
RenderImportSession,
RenderProjectCode
} from './Generic';
Expand Down Expand Up @@ -92,7 +93,8 @@ const RendererLookup: EnumDictionary<
[ModelType.reporttemplate]: RenderReportTemplate,
[ModelType.labeltemplate]: RenderLabelTemplate,
[ModelType.pluginconfig]: RenderPlugin,
[ModelType.contenttype]: RenderContentType
[ModelType.contenttype]: RenderContentType,
[ModelType.error]: RenderError
};

export type RenderInstanceProps = {
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/src/components/render/ModelType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ export const ModelInformationDict: ModelDict = {
label: () => t`Content Type`,
label_multiple: () => t`Content Types`,
api_endpoint: ApiEndpoints.content_type_list
},
error: {
label: () => t`Error`,
label_multiple: () => t`Errors`,
api_endpoint: ApiEndpoints.error_report_list,
url_overview: '/settings/admin/errors',
url_detail: '/settings/admin/errors/:pk/'
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/enums/ModelType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ export enum ModelType {
reporttemplate = 'reporttemplate',
labeltemplate = 'labeltemplate',
pluginconfig = 'pluginconfig',
contenttype = 'contenttype'
contenttype = 'contenttype',
error = 'error'
}
4 changes: 2 additions & 2 deletions src/frontend/src/pages/Notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
IconBellCheck,
IconBellExclamation,
IconCircleCheck,
IconCircleX,
IconMail,
IconMailOpened,
IconTrash
} from '@tabler/icons-react';
Expand Down Expand Up @@ -106,7 +106,7 @@ export default function NotificationsPage() {
actions={(record) => [
{
title: t`Mark as unread`,
icon: <IconCircleX />,
icon: <IconMail />,
onClick: () => {
let url = apiUrl(ApiEndpoints.notifications_list, record.pk);

Expand Down
Loading
Loading