Skip to content

Commit

Permalink
task/DSAPP-46, task/WP-611: Tools & Apps Workspace Changes; Notificat…
Browse files Browse the repository at this point in the history
…ions Enhancements (#1442)

* support .log preview

* change "Account Profile" to "Manage Account

* add workspace links to account dropdown

* change /rw/workspace to /workspace; backwards compatible

* add "Job Status" label to notification bell

* enable docker ngrok

* remove notifications link; adjust dropdown header

* formatting

* sync job notification states

* update m1 docker-compose; linting

* adjust toast UI to look like secondary button

* unify ngrok webhook settings, and move to conf/env_files/ngrok.env

* Revert "unify ngrok webhook settings, and move to conf/env_files/ngrok.env"

This reverts commit 940be99.

* Revert "enable docker ngrok"

This reverts commit 793b6d0.
  • Loading branch information
rstijerina authored Oct 2, 2024
1 parent 06a068a commit 1295c87
Show file tree
Hide file tree
Showing 25 changed files with 176 additions and 91 deletions.
6 changes: 5 additions & 1 deletion client/modules/_hooks/src/notifications/useNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
} from '@tanstack/react-query';
import apiClient from '../apiClient';

type TPortalEventType = 'data_depot' | 'job' | 'interactive_session_ready';
type TPortalEventType =
| 'data_depot'
| 'job'
| 'interactive_session_ready'
| 'markAllNotificationsAsRead';

export type TJobStatusNotification = {
action_link: string;
Expand Down
5 changes: 5 additions & 0 deletions client/modules/workspace/src/JobsListing/JobsListing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useMemo, useState, useEffect } from 'react';
import useWebSocket from 'react-use-websocket';
import { TableProps, Row, Flex, Button as AntButton } from 'antd';
import type { ButtonSize } from 'antd/es/button';
import { useQueryClient } from '@tanstack/react-query';
Expand Down Expand Up @@ -96,12 +97,16 @@ export const JobsListing: React.FC<Omit<TableProps, 'columns'>> = ({
markRead: false,
});
const { mutate: readNotifications } = useReadNotifications();
const { sendMessage } = useWebSocket(
`wss://${window.location.host}/ws/websockets/`
);

// mark all as read on component mount
useEffect(() => {
readNotifications({
eventTypes: ['interactive_session_ready', 'job'],
});
sendMessage('markAllNotificationsAsRead');

// update unread count state
queryClient.setQueryData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ export const JobsListingTable: React.FC<
isLoading,
]);

const lastNotificationJobUUID = lastMessage
? (JSON.parse(lastMessage.data) as TJobStatusNotification).extra.uuid
: '';
const lastMessageJSON = lastMessage?.data
? (JSON.parse(lastMessage.data) as TJobStatusNotification)
: null;
const lastNotificationJobUUID =
lastMessageJSON?.event_type === 'job' ? lastMessageJSON.extra.uuid : '';
const unreadJobUUIDs = unreadNotifs?.notifs.map((x) => x.extra.uuid) ?? [];

/* RENDER THE TABLE */
Expand Down
10 changes: 10 additions & 0 deletions client/modules/workspace/src/Toast/Notifications.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
.root {
cursor: pointer;
background: #f4f4f4;
border: 1px solid #222222;
&:hover {
border-color: #5695c4;
background: #aac7ff;
}
}

.toast-is-error {
color: #eb6e6e;
}
38 changes: 33 additions & 5 deletions client/modules/workspace/src/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React, { useEffect } from 'react';
import useWebSocket from 'react-use-websocket';
import { useQueryClient } from '@tanstack/react-query';
import { notification } from 'antd';
import { notification, Flex } from 'antd';
import { RightOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { Icon } from '@client/common-components';
import { TJobStatusNotification } from '@client/hooks';
import {
TJobStatusNotification,
TGetNotificationsResponse,
} from '@client/hooks';
import { getToastMessage } from '../utils';
import styles from './Notifications.module.css';

Expand All @@ -31,19 +35,43 @@ const Notifications = () => {
queryKey: ['workspace', 'jobsListing'],
});
api.open({
message: getToastMessage(notification),
message: (
<Flex justify="space-between">
{getToastMessage(notification)}
<RightOutlined style={{ marginRight: -5 }} />
</Flex>
),
placement: 'bottomLeft',
icon: <Icon className={`ds-icon-Job-Status`} label="Job-Status" />,
className: `${
notification.extra.status === 'FAILED' && styles['toast-is-error']
}`,
} ${styles.root}`,
closeIcon: false,
duration: 5,
onClick: () => {
navigate('/history');
},
style: { cursor: 'pointer' },
});
} else if (notification.event_type === 'markAllNotificationsAsRead') {
// update unread count state
queryClient.setQueryData(
[
'workspace',
'notifications',
{
eventTypes: ['interactive_session_ready', 'job'],
read: false,
markRead: false,
},
],
(oldData: TGetNotificationsResponse) => {
return {
...oldData,
notifs: [],
unread: 0,
};
}
);
}
};

Expand Down
9 changes: 8 additions & 1 deletion client/src/workspace/workspaceRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { JobsListingLayout } from './layouts/JobsListingLayout';
import { AppsViewLayout } from './layouts/AppsViewLayout';
import { AppsPlaceholderLayout } from './layouts/AppsPlaceholderLayout';

const getBaseName = () => {
if (window.location.pathname.startsWith('/rw/workspace')) {
return '/rw/workspace';
}
return '/workspace';
};

const workspaceRouter = createBrowserRouter(
[
{
Expand Down Expand Up @@ -44,7 +51,7 @@ const workspaceRouter = createBrowserRouter(
],
},
],
{ basename: '/rw/workspace' }
{ basename: getBaseName() }
);

export default workspaceRouter;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h1 class="headline headline-research">{{title}}</h1>
<div class="col-sm-3">
<ul class="nav nav-pills nav-stacked">
{% url 'designsafe_accounts:manage_profile' as item_url %}
<li {% if item_url in request.path %}class="active"{% endif %}><a href="{{item_url}}">Account Profile</a></li>
<li {% if item_url in request.path %}class="active"{% endif %}><a href="{{item_url}}">Manage Account</a></li>

{% url 'designsafe_accounts:manage_authentication' as item_url %}
<li {% if request.path == item_url %}class="active"{% endif %}><a href="{{item_url}}">Authentication</a></li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{% extends "designsafe/apps/accounts/base.html" %}

{% block title %}Account Profile{% endblock %}
{% block title %}Manage Account{% endblock %}

{% block panel_content %}
<div class="panel panel-default">
<div class="panel-body">
<h2>Account Profile</h2>
<h2>Manage Account</h2>
<hr>
<div class="row">
<div class="col-md-6">
Expand Down
2 changes: 1 addition & 1 deletion designsafe/apps/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def menu_items(**kwargs):
if 'type' in kwargs and kwargs['type'] == 'account':
return [
{
'label': _('Account Profile'),
'label': _('Manage Account'),
'url': reverse('designsafe_accounts:index'),
'children': [],
},
Expand Down
4 changes: 2 additions & 2 deletions designsafe/apps/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def manage_profile(request):
logger.info('exception e:{} {}'.format(type(e), e))

context = {
'title': 'Account Profile',
'title': 'Manage Account',
'profile': user_profile,
'ds_profile': ds_profile,
'demographics': demographics,
Expand Down Expand Up @@ -353,7 +353,7 @@ def profile_edit(request):
form = forms.UserProfileForm(initial=tas_user)

context = {
'title': 'Account Profile',
'title': 'Manage Account',
'form': form,
'pro_form': pro_form
}
Expand Down
63 changes: 29 additions & 34 deletions designsafe/apps/api/notifications/receivers.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
"""Signal receivers for notifications"""

import logging
import json
from django.db.models.signals import post_save
from django.dispatch import receiver
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.db.models.signals import post_save
from designsafe.apps.api.notifications.models import Notification, Broadcast
import logging
import json

logger = logging.getLogger(__name__)

WEBSOCKETS_FACILITY = 'websockets'
WEBSOCKETS_FACILITY = "websockets"


@receiver(post_save, sender=Notification, dispatch_uid="notification_msg")
def send_notification_ws(instance, created, **kwargs):
"""Send a websocket message to the user when a new notification is created."""

@receiver(post_save, sender=Notification, dispatch_uid='notification_msg')
def send_notification_ws(sender, instance, created, **kwargs):
#Only send WS message if it's a new notification not if we're updating.
logger.debug('receiver received something.')
if not created:
return
try:
channel_layer = get_channel_layer()
instance_dict = json.dumps(instance.to_dict())
logger.debug(instance_dict)

async_to_sync(channel_layer.group_send)(f"ds_{instance.user}", {"type": "ds.notification", "message": instance_dict})
channel_layer = get_channel_layer()
instance_dict = json.dumps(instance.to_dict())

async_to_sync(channel_layer.group_send)(
f"ds_{instance.user}", {"type": "ds.notification", "message": instance_dict}
)

# logger.debug('WS socket msg sent: {}'.format(instance_dict))
except Exception as e:
# logger.debug('Exception sending websocket message',
# exc_info=True,
# extra = instance.to_dict())
logger.debug('Exception sending websocket message',
exc_info=True)
return

@receiver(post_save, sender=Broadcast, dispatch_uid='broadcast_msg')
def send_broadcast_ws(sender, instance, created, **kwargs):

@receiver(post_save, sender=Broadcast, dispatch_uid="broadcast_msg")
def send_broadcast_ws(instance, created, **kwargs):
"""Send a websocket message to all users when a new broadcast is created."""

if not created:
return
try:
event_type, user, body = decompose_message(instance)
#rp = RedisPublisher(facility = WEBSOCKETS_FACILITY,broadcast=True)
channel_layer = get_channel_layer()
instance_dict = json.dumps(instance.to_dict())

async_to_sync(channel_layer.group_send)("ds_broadcast", {"type": "ds.notification", "message": instance_dict})
logger.debug('WS socket msg sent: {}'.format(instance_dict))
except Exception as e:
logger.debug('Exception sending websocket message',
exc_info=True,
extra = instance.to_dict())

channel_layer = get_channel_layer()
instance_dict = json.dumps(instance.to_dict())

async_to_sync(channel_layer.group_send)(
"ds_broadcast", {"type": "ds.notification", "message": instance_dict}
)

return
33 changes: 21 additions & 12 deletions designsafe/apps/signals/websocket_consumers.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import json
"""Websocket consumers"""

from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.layers import get_channel_layer


class DesignsafeWebsocketConsumer(AsyncWebsocketConsumer):
"""Websocket consumer for DesignSafe notifications"""

async def connect(self):
self.user_channel = f"ds_{self.scope['user']}"
self.broadcast_channel = "ds_broadcast"
await self.channel_layer.group_add(
self.user_channel, self.channel_name
f"ds_{self.scope['user']}", self.channel_name
)
await self.accept()

async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.user_channel, self.channel_name
)
async def disconnect(self, code):
await self.channel_layer.group_discard(
self.broadcast_channel, self.channel_name
f"ds_{self.scope['user']}", self.channel_name
)
await self.channel_layer.group_discard("ds_broadcast", self.channel_name)

async def receive(self, text_data):
pass
async def receive(self, text_data=None, bytes_data=None):
if text_data == "markAllNotificationsAsRead":
channel_layer = get_channel_layer()
await channel_layer.group_send(
f"ds_{self.scope['user']}",
{
"type": "ds_notification",
"message": json.dumps({"event_type": "markAllNotificationsAsRead"}),
},
)

async def ds_notification(self, event):
"""Send notification to user"""
message = event["message"]
await self.send(text_data=message)
4 changes: 2 additions & 2 deletions designsafe/apps/workspace/models/app_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ def href(self):
"""Retrieve the app's URL in the Tools & Applications space"""
if self.external_href:
return self.external_href
app_href = f"/rw/workspace/{self.app_id}"

app_href = f"/workspace/{self.app_id}"
if self.version:
app_href += f"?appVersion={self.version}"
return app_href
Expand Down
1 change: 1 addition & 0 deletions designsafe/apps/workspace/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
from designsafe.apps.workspace import views

urlpatterns = [
re_path('history', views.WorkspaceView.as_view(), name="history"),
re_path('^', views.WorkspaceView.as_view(), name="workspace"),
]
4 changes: 2 additions & 2 deletions designsafe/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class DesignsafeProfileUpdateMiddleware(MiddlewareMixin):

def process_request(self, request):
blocked_path = request.path.startswith(
("/data", "/applications", "/rw/workspace", "/recon-portal", "/dashboard")
("/data", "/applications", "/rw/workspace", "/workspace", "/recon-portal", "/dashboard")
)
if request.user.is_authenticated and request.user.profile.update_required and blocked_path:
messages.warning(
Expand Down Expand Up @@ -65,7 +65,7 @@ def process_request(self, request):
'Use is required for continued use of DesignSafe '
'resources.' % accept_url)
return None


class SiteMessageMiddleware(MiddlewareMixin):
def process_request(self, request):
Expand Down
2 changes: 1 addition & 1 deletion designsafe/settings/common_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@
'.java', '.js', '.less', '.m', '.make', '.md', '.ml', '.mm', '.msg', '.php',
'.pl', '.properties', '.py', '.rb', '.sass', '.scala', '.script', '.sh', '.sml',
'.sql', '.txt', '.vi', '.vim', '.xml', '.xsd', '.xsl', '.yaml', '.yml', '.tcl',
'.json', '.out', '.err', '.geojson', '.do', '.sas', '.hazmapper'
'.json', '.out', '.err', '.geojson', '.do', '.sas', '.hazmapper', ".log"
]

SUPPORTED_OBJECT_PREVIEW_EXTS = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<div> <span> Quick Links </span> </div>
<div><a href="/account"> Manage Account </a> </div>
<div> <a href="/data/browser"> Data Depot </a> </div>
<div> <a href="/rw/workspace"> Tools & Applications </a> </div>
<div> <a href="/workspace"> Tools & Applications </a> </div>
<div> <a href="/recon-portal"> Recon Portal </a> </div>
<div> <a href="/learning-center/overview"> Training </a> </div>
</div>
Expand Down
Loading

0 comments on commit 1295c87

Please sign in to comment.