Skip to content

Commit

Permalink
feat/ contact noisiness (#577)
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardZaydler authored Feb 24, 2025
1 parent 81c32bf commit d012546
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 54 deletions.
7 changes: 7 additions & 0 deletions local_modules/styles/mixins.less
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@
.dropdown-checkbox-item {
padding: 6px 8px;
}

.noisinessSpinner {
display: flex;
align-items: center;
justify-content: center;
height: calc(100vh - 335px);
}
9 changes: 7 additions & 2 deletions src/Components/ContactEditModal/ContactEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Contact } from "../../Domain/Contact";
import { omitContact } from "../../helpers/omitTypes";
import ContactEditForm from "../ContactEditForm/ContactEditForm";
import FileExport from "../FileExport/FileExport";
import { ResourceIDBadge } from "../ResourceIDBadge/ResourceIDBadge";
import { ResourceBadge } from "../ResourceBadge/ResourceBadge";
import ModalError from "../ModalError/ModalError";
import { useParams } from "react-router";
import { useUpdateContact } from "../../hooks/useUpdateContact";
Expand All @@ -17,12 +17,14 @@ import { Hint } from "@skbkontur/react-ui";
interface IContactEditModalProps {
contactInfo: Contact | null;
isDeleteContactButtonDisabled?: boolean;
showOwner?: boolean;
onCancel: () => void;
}

const ContactEditModal: FC<IContactEditModalProps> = ({
contactInfo,
isDeleteContactButtonDisabled,
showOwner,
onCancel,
}) => {
const [contact, setContact] = useState<Contact | null>(contactInfo);
Expand Down Expand Up @@ -50,7 +52,10 @@ const ContactEditModal: FC<IContactEditModalProps> = ({
<Modal onClose={onCancel}>
<Modal.Header sticky={false}>Delivery channel editing</Modal.Header>
<Modal.Body>
<ResourceIDBadge title="Channel id:" id={contact.id} />
<ResourceBadge title="Channel id:" id={contact.id} />
{showOwner && (contact.user || contact.team_id) && (
<ResourceBadge title="Owner:" id={contact.user ?? contact.team_id ?? ""} />
)}
<ValidationContainer ref={validationContainer}>
<ContactEditForm
contactInfo={contact}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export const TriggerEventsChart: React.FC<ITriggerEventsBarChartProps> = ({ even
data={data}
plugins={[createHtmlLegendPlugin(true, (triggerId) => `/trigger/${triggerId}`)]}
options={
triggerEventsChartOptions(theme.chartGridLinesColor) as ChartOptions<"bar">
triggerEventsChartOptions(
"Triggers",
"Number of Events",
theme.chartGridLinesColor
) as ChartOptions<"bar">
}
/>
</Flexbox>
Expand Down
40 changes: 31 additions & 9 deletions src/Components/ContactEventStats/Components/htmlLegendPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,19 @@ const LegendItemComponent: React.FC<{
chart: Chart;
showLinks: boolean;
updateLegendStyles: () => void;
getLink?: (label: string) => string;
}> = ({ item, index, chart, showLinks, updateLegendStyles, getLink }) => {
getLink?: ((label: string) => string) | null;
propsLinkIcon?: React.ElementType;
onLinkClick?: (label: string) => void;
}> = ({
item,
index,
chart,
showLinks,
updateLegendStyles,
getLink,
propsLinkIcon,
onLinkClick,
}) => {
if (!item.text || item.text.trim() === "") {
return null;
}
Expand Down Expand Up @@ -64,13 +75,16 @@ const LegendItemComponent: React.FC<{
<span className={cn("legend-text")} style={{ color: item.fontColor as string }}>
{item.text}
</span>
{showLinks && getLink && (
{showLinks && (
<Link
href={getLink(item.text)}
href={getLink ? getLink(item.text) : ""}
target="_blank"
className={cn("legend-link")}
onClick={(e) => e.stopPropagation()}
icon={<LinkIcon />}
onClick={(e) => {
onLinkClick && onLinkClick(item.text);
e.stopPropagation();
}}
icon={propsLinkIcon ? React.createElement(propsLinkIcon) : <LinkIcon />}
/>
)}
</li>
Expand All @@ -82,8 +96,10 @@ const Legend: React.FC<{
items: LegendItem[];
showLinks: boolean;
updateLegendStyles: () => void;
getLink?: (label: string) => string;
}> = ({ chart, items, showLinks, updateLegendStyles, getLink }) => {
getLink?: ((label: string) => string) | null;
propsLinkIcon?: React.ElementType;
onLinkClick?: (label: string) => void;
}> = ({ chart, items, showLinks, updateLegendStyles, getLink, propsLinkIcon, onLinkClick }) => {
const IconComponent = isExpanded ? ArrowUpIcon : ArrowDownIcon;

const toggleExpand = () => {
Expand All @@ -102,6 +118,8 @@ const Legend: React.FC<{
showLinks={showLinks}
updateLegendStyles={updateLegendStyles}
getLink={getLink}
propsLinkIcon={propsLinkIcon}
onLinkClick={onLinkClick}
/>
))}
{items.length > maxVisibleItems && (
Expand All @@ -113,7 +131,9 @@ const Legend: React.FC<{

export const createHtmlLegendPlugin = (
showLinks: boolean,
getLink?: (label: string) => string
getLink?: ((label: string) => string) | null,
propsLinkIcon?: React.ElementType,
onLinkClick?: (label: string) => void
): Plugin<"bar"> => ({
id: "htmlLegend",
afterUpdate(chart) {
Expand Down Expand Up @@ -153,6 +173,8 @@ export const createHtmlLegendPlugin = (
items={items}
showLinks={showLinks}
updateLegendStyles={updateLegendStyles}
propsLinkIcon={propsLinkIcon}
onLinkClick={onLinkClick}
/>,
legendContainer
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import { useTheme } from "../../../Themes";
import { createHtmlLegendPlugin } from "../../ContactEventStats/Components/htmlLegendPlugin";
import { NoisinessDataset } from "../../TriggerNoisiness/TiggerNoisinessChart";
import { Bar } from "react-chartjs-2";
import { triggerEventsChartOptions } from "../../../helpers/getChartOptions";
import { ChartOptions } from "chart.js";
import EditIcon from "@skbkontur/react-icons/Edit";

export const ContactNoisinessChartView: React.FC<{
datasets: NoisinessDataset[];
onEditClick: (label: string) => void;
}> = ({ datasets, onEditClick }) => {
const theme = useTheme();

const htmlLegendPlugin = createHtmlLegendPlugin(true, null, EditIcon, onEditClick);

const data = {
labels: [""],
datasets,
};

return (
<>
<div id="trigger-events-legend-container" />
<Bar
data={data}
plugins={[htmlLegendPlugin]}
options={
triggerEventsChartOptions(
"Contacts",
"Number of Events",
theme.chartGridLinesColor
) as ChartOptions<"bar">
}
style={{ maxHeight: "305px" }}
/>
</>
);
};
96 changes: 96 additions & 0 deletions src/Components/ContactNosiness/ContactNoisinessChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useEffect, FC, useState } from "react";
import { useLazyGetContactNoisinessQuery } from "../../services/ContactApi";
import { getUnixTime, subDays, subHours } from "date-fns";
import transformPageFromHumanToProgrammer from "../../logic/transformPageFromHumanToProgrammer";
import { NoisinessDataset } from "../TriggerNoisiness/TiggerNoisinessChart";
import { getColor } from "../Tag/Tag";
import { Paging } from "@skbkontur/react-ui/components/Paging";
import { Flexbox } from "../Flexbox/FlexBox";
import { TimeRangeSelector } from "../TriggerNoisiness/Components/TimeRangeSelector";
import { Spinner } from "@skbkontur/react-ui/components/Spinner";
import { Contact } from "../../Domain/Contact";
import { useModal } from "../../hooks/useModal";
import { ContactNoisinessChartView } from "./Components/ContactNoisinessChartView";
import ContactEditModal from "../ContactEditModal/ContactEditModal";
import classNames from "classnames/bind";

import styles from "../../../local_modules/styles/mixins.less";

const cn = classNames.bind(styles);

export const ContactNoisinessChart: FC = () => {
const [page, setPage] = useState(1);
const maxDate = new Date();
const minDate = subDays(new Date(), 7);
const [fromTime, setFromTime] = useState<Date | null>(subHours(maxDate, 1));
const [untilTime, setUntilTime] = useState<Date | null>(maxDate);
const [trigger, result] = useLazyGetContactNoisinessQuery();
const { data: contacts, isLoading, isFetching } = result;
const [datasets, setDatasets] = useState<NoisinessDataset[]>([]);
const [contact, setContact] = useState<Contact | null>(null);
const { isModalOpen, closeModal, openModal } = useModal();
const pageCount = Math.ceil((contacts?.total ?? 0) / (contacts?.size ?? 1));

const handleEditClick = (label: string) => {
const foundContact = contacts?.list.find((c) => c.name === label || c.value === label);
if (foundContact) {
setContact(foundContact);
openModal();
}
};

const fetchEvents = () =>
trigger({
from: fromTime && getUnixTime(fromTime),
to: untilTime && getUnixTime(untilTime),
page: transformPageFromHumanToProgrammer(page),
});

useEffect(() => {
if (contacts?.list) {
setDatasets(
contacts.list.map(({ name, events_count, id, value }) => ({
label: name ?? value,
data: [events_count],
backgroundColor: getColor(id).backgroundColor,
}))
);
}
}, [contacts]);

useEffect(() => {
fetchEvents();
}, [page]);

return (
<>
{isLoading || isFetching ? (
<Spinner className={cn("noisinessSpinner")} />
) : (
<Flexbox direction="column" gap={18}>
{isModalOpen && (
<ContactEditModal showOwner contactInfo={contact} onCancel={closeModal} />
)}
<ContactNoisinessChartView datasets={datasets} onEditClick={handleEditClick} />
<Flexbox direction="row" justify="space-between">
<TimeRangeSelector
fromTime={fromTime}
untilTime={untilTime}
setFromTime={setFromTime}
setUntilTime={setUntilTime}
minDate={minDate}
maxDate={maxDate}
onApply={fetchEvents}
/>
<Paging
activePage={page}
pagesCount={pageCount}
onPageChange={setPage}
withoutNavigationHint
/>
</Flexbox>
</Flexbox>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
@import '~styles/variables.less';
@import "~styles/variables.less";

.wrapper {
margin: 0 0 16px;
}

.title {
color: @colorGrey;
margin-bottom: 4px
margin-bottom: 4px;
}

.icon {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { ReactElement } from "react";
import { Toast } from "@skbkontur/react-ui/components/Toast";
import CopyIcon from "@skbkontur/react-icons/Copy";
import cn from "./ResourceIDBadge.less";
import cn from "./ResourceBadge.less";

interface EditorIdProps {
title: string;
id: string;
}

export function ResourceIDBadge(props: EditorIdProps): ReactElement {
export function ResourceBadge(props: EditorIdProps): ReactElement {
const handleClick = (): void => {
navigator.clipboard.writeText(props.id);
Toast.push("Copied to clipboard");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Contact } from "../../Domain/Contact";
import { omitSubscription } from "../../helpers/omitTypes";
import SubscriptionEditor from "../SubscriptionEditor/SubscriptionEditor";
import FileExport from "../FileExport/FileExport";
import { ResourceIDBadge } from "../ResourceIDBadge/ResourceIDBadge";
import { ResourceBadge } from "../ResourceBadge/ResourceBadge";
import { useParams } from "react-router";
import ModalError from "../ModalError/ModalError";
import { useUpdateSubscription } from "../../hooks/useUpdateSubscription";
Expand Down Expand Up @@ -68,7 +68,7 @@ const SubscriptionEditModal: React.FC<Props> = ({ subscription, tags, contacts,
<Modal onClose={onCancel}>
<Modal.Header sticky={false}>Subscription editing</Modal.Header>
<Modal.Body>
<ResourceIDBadge title={"Subscription id:"} id={subscriptionToEdit.id} />
<ResourceBadge title={"Subscription id:"} id={subscriptionToEdit.id} />
<ValidationContainer ref={validationContainerRef}>
<SubscriptionEditor
subscription={subscriptionToEdit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ export const TriggerNoisinessChartView: React.FC<{
<Bar
data={data}
plugins={[htmlLegendPlugin]}
options={triggerEventsChartOptions(theme.chartGridLinesColor) as ChartOptions<"bar">}
style={{ maxHeight: "350px" }}
options={
triggerEventsChartOptions(
"Triggers",
"Number of Events",
theme.chartGridLinesColor
) as ChartOptions<"bar">
}
style={{ maxHeight: "305px" }}
/>
);
};
Loading

0 comments on commit d012546

Please sign in to comment.