Skip to content

Commit

Permalink
Unread notifications support
Browse files Browse the repository at this point in the history
  • Loading branch information
insmac committed Oct 3, 2023
1 parent ea08173 commit 9d85c09
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 82 deletions.
43 changes: 43 additions & 0 deletions packages/web-console/src/components/UnreadItemsIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react"
import styled from "styled-components"

const Root = styled.span`
position: relative;
`

const Tick = styled.span`
position: absolute;
right: -0.5rem;
top: 0;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: ${({ theme }) => theme.color.red};
`

const Count = styled.span`
position: absolute;
left: 100%;
top: -50%;
font-size: ${({ theme }) => theme.fontSize.ms};
border-radius: 6px;
background-color: #c64242;
color: ${({ theme }) => theme.color.white};
padding: 0.2rem 0.4rem;
`

type Props = {
icon?: React.ReactNode
label?: string
tick?: boolean
count?: number
}

export const UnreadItemsIcon = ({ icon, label, tick, count }: Props) => (
<Root>
{tick && <Tick />}
{typeof count === "number" && count > 0 && <Count>{count}</Count>}
{icon}
{label && <span>{label}</span>}
</Root>
)
30 changes: 1 addition & 29 deletions packages/web-console/src/scenes/Editor/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
Chat3,
Close as _CloseIcon,
Command,
Notification2,
Play,
Stop,
Question,
Expand All @@ -40,9 +39,7 @@ import { Menu as _MenuIcon } from "styled-icons/remix-fill"
import { Slack } from "styled-icons/boxicons-logos"

import {
Drawer,
ErrorButton,
IconWithTooltip,
Link,
PaneMenu,
PopperToggle,
Expand All @@ -58,7 +55,6 @@ import {
import { Button, DropdownMenu, FeedbackDialog } from "@questdb/react-components"
import { actions, selectors } from "../../../store"
import { color } from "../../../utils"

import QueryPicker from "../QueryPicker"
import { Shortcuts } from "../Shortcuts"
import { useLocalStorage } from "../../../providers/LocalStorageProvider"
Expand Down Expand Up @@ -171,7 +167,6 @@ const Menu = () => {
const telemetryConfig = useSelector(selectors.telemetry.getConfig)
const { sm } = useScreenSize()
const { exampleQueriesVisited, updateSettings } = useLocalStorage()
const [newsOpened, setNewsOpened] = useState(false)

const handleClick = useCallback(() => {
dispatch(actions.query.toggleRunning())
Expand Down Expand Up @@ -317,30 +312,7 @@ const Menu = () => {
</DropdownMenu.Content>
</DropdownMenu.Root>

<Drawer
mode="side"
title="QuestDB News"
withCloseButton
onOpenChange={(newsOpened) => {
setNewsOpened(newsOpened)
dispatch(
actions.console.setActivePanel(newsOpened ? "news" : "console"),
)
}}
trigger={
<IconWithTooltip
icon={
<Button skin={newsOpened ? "primary" : "secondary"}>
<Notification2 size="18px" />
</Button>
}
placement="bottom"
tooltip="QuestDB News"
/>
}
>
<News />
</Drawer>
<News />
</MenuItems>

<PopperToggle
Expand Down
178 changes: 125 additions & 53 deletions packages/web-console/src/scenes/News/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from "react"
import { Text } from "../../components"
import { Text, Drawer, IconWithTooltip } from "../../components"
import styled from "styled-components"
import { useEffect, useState, useContext } from "react"
import React, { useEffect, useState, useContext } from "react"
import { QuestContext } from "../../providers"
import { NewsItem } from "../../utils/questdb"
import { useSelector } from "react-redux"
import { selectors } from "../../store"
import { useDispatch, useSelector } from "react-redux"
import { selectors, actions } from "../../store"
import ReactMarkdown from "react-markdown"
import { Loader } from "@questdb/react-components"
import { Loader, Button } from "@questdb/react-components"
import { Notification2 } from "styled-icons/remix-line"
import { db } from "../../store/db"
import { UnreadItemsIcon } from "../../components/UnreadItemsIcon"

const Loading = styled.div`
display: grid;
Expand Down Expand Up @@ -59,63 +61,133 @@ const Thumbnail = styled.img`
`

const News = () => {
const dispatch = useDispatch()
const { quest } = useContext(QuestContext)
const telemetryConfig = useSelector(selectors.telemetry.getConfig)
const [enterpriseNews, setEnterpriseNews] = useState<NewsItem[] | undefined>(
undefined,
)
const [newsOpened, setNewsOpened] = useState(false)
const [hasUnreadNews, setHasUnreadNews] = useState(false)

const getEnterpriseNews = async () => {
const news = await quest.getNews({
category: "enterprise",
telemetryConfig,
})
setEnterpriseNews(news)
}

const getUnreadNews = async () => {
if (enterpriseNews) {
const readNews = await db.read_notifications.toArray()
const newsIds = enterpriseNews.map((newsItem) => newsItem.id)
const unreadNews = newsIds.filter(
(newsId) =>
!readNews.find((readNewsItem) => readNewsItem.newsId === newsId),
)
setHasUnreadNews(unreadNews?.length > 0 ? true : false)
}
}

const clearUnreadNews = async () => {
const news = await quest.getNews({
category: "enterprise",
telemetryConfig,
})
const newsIds = news.map((newsItem) => newsItem.id)
await db.read_notifications.bulkAdd(newsIds.map((newsId) => ({ newsId })))
setHasUnreadNews(false)
}

// Get Enterprise News on render
useEffect(() => {
void quest
.getNews({ category: "enterprise", telemetryConfig })
.then((news: NewsItem[]) => {
setEnterpriseNews(news)
})
getEnterpriseNews()
}, [])

// Compute unread news
useEffect(() => {
if (enterpriseNews) {
void getUnreadNews()
}
}, [enterpriseNews])

// Clear unread news when news are opened
useEffect(() => {
if (newsOpened && enterpriseNews) {
void clearUnreadNews()
}
}, [newsOpened, enterpriseNews])

return (
<Items>
{enterpriseNews === undefined && (
<Loading>
<Text color="foreground">Loading news...</Text>
<Loader />
</Loading>
)}
{enterpriseNews &&
enterpriseNews.map((newsItem, index) => (
<Item key={`${index}-${newsItem.title}`}>
<Title>{newsItem.title}</Title>
<Text color="gray2">{newsItem.date}</Text>
{newsItem.thumbnail &&
newsItem.thumbnail.length > 0 &&
newsItem.thumbnail[0].thumbnails.large && (
<Thumbnail
src={newsItem.thumbnail[0].thumbnails.large.url}
alt={`${newsItem.title} thumbnail`}
/>
)}

<NewsText>
<ReactMarkdown
components={{
a: ({ node, children, ...props }) => (
<a
{...(props.href?.startsWith("http")
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
{...props}
>
{children}
</a>
),
}}
>
{newsItem.body}
</ReactMarkdown>
</NewsText>
</Item>
))}
</Items>
<Drawer
mode="side"
title="QuestDB News"
withCloseButton
onOpenChange={async (newsOpened) => {
setNewsOpened(newsOpened)
dispatch(
actions.console.setActivePanel(newsOpened ? "news" : "console"),
)
}}
trigger={
<IconWithTooltip
icon={
<Button skin={newsOpened ? "primary" : "secondary"}>
<UnreadItemsIcon
icon={<Notification2 size="18px" />}
tick={hasUnreadNews}
/>
</Button>
}
placement="bottom"
tooltip="QuestDB News"
/>
}
>
<Items>
{enterpriseNews === undefined && (
<Loading>
<Text color="foreground">Loading news...</Text>
<Loader />
</Loading>
)}
{enterpriseNews &&
enterpriseNews.map((newsItem, index) => (
<Item key={`${index}-${newsItem.title}`}>
<Title>{newsItem.title}</Title>
<Text color="gray2">{newsItem.date}</Text>
{newsItem.thumbnail &&
newsItem.thumbnail.length > 0 &&
newsItem.thumbnail[0].thumbnails.large && (
<Thumbnail
src={newsItem.thumbnail[0].thumbnails.large.url}
alt={`${newsItem.title} thumbnail`}
/>
)}

<NewsText>
<ReactMarkdown
components={{
a: ({ node, children, ...props }) => (
<a
{...(props.href?.startsWith("http")
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
{...props}
>
{children}
</a>
),
}}
>
{newsItem.body}
</ReactMarkdown>
</NewsText>
</Item>
))}
</Items>
</Drawer>
)
}

Expand Down
2 changes: 2 additions & 0 deletions packages/web-console/src/store/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ type EditorSettings = {
export class Storage extends Dexie {
buffers!: Table<Buffer, number>
editor_settings!: Table<EditorSettings, number>
read_notifications!: Table<{ newsId: string }, number>

constructor() {
super("web-console")
this.version(1).stores({
buffers: "++id, label",
editor_settings: "++id, key",
read_notifications: "++id, newsId",
})

// add initial buffer on db creation
Expand Down
1 change: 1 addition & 0 deletions packages/web-console/src/utils/questdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export type NewsThumbnail = {
}

export type NewsItem = {
id: string
title: string
body: string
thumbnail?: NewsThumbnail[]
Expand Down

0 comments on commit 9d85c09

Please sign in to comment.