PresenceIndicators.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { css, cx } from '@emotion/css';
  2. import { isEqual } from 'lodash';
  3. import React, { FC, useState, useEffect } from 'react';
  4. import { connect, ConnectedProps } from 'react-redux';
  5. import { GrafanaTheme } from '@grafana/data';
  6. import { Button, stylesFactory, useTheme } from '@grafana/ui';
  7. import { contextSrv } from 'app/core/services/context_srv';
  8. import { addCustomLeftAction } from 'app/features/dashboard/components/DashNav/DashNav';
  9. import { DashboardModel } from 'app/features/dashboard/state';
  10. import { AnalyticsTab } from '../types';
  11. import { UserIcon, getUserIconStyles } from './UserIcon';
  12. import { getRecentUsers, UserViewDTO } from './api';
  13. import { openDrawer } from './state/actions';
  14. const getPresenceIndicatorsStyles = stylesFactory((theme: GrafanaTheme, tooManyUsers: boolean) => {
  15. return {
  16. container: css`
  17. display: flex;
  18. justify-content: center;
  19. flex-direction: row-reverse;
  20. margin-left: ${theme.spacing.sm};
  21. `,
  22. moreIcon: css`
  23. cursor: pointer;
  24. span {
  25. margin-bottom: ${tooManyUsers ? '3px' : '0px'};
  26. }
  27. `,
  28. };
  29. });
  30. export interface Props {
  31. dashboard?: DashboardModel;
  32. }
  33. const mapDispatchToProps = {
  34. openDrawer,
  35. };
  36. const connector = connect(null, mapDispatchToProps);
  37. export type PresenceIndicatorsProps = ConnectedProps<typeof connector> & Props;
  38. const iconLimit = 4;
  39. const refreshInterval = 60000; // In milliseconds
  40. function fetchRecentUsers(dashboardId: number, setRecentUsers: React.Dispatch<React.SetStateAction<UserViewDTO[]>>) {
  41. const user = contextSrv.user;
  42. getRecentUsers(dashboardId, iconLimit + 10).then((data) => {
  43. const items = data.filter((item: UserViewDTO) => item.user.id !== user.id);
  44. setRecentUsers((recentUsers: UserViewDTO[]) => (isEqual(items, recentUsers) ? recentUsers : items));
  45. });
  46. }
  47. export const PresenceIndicators: FC<PresenceIndicatorsProps> = ({ dashboard, openDrawer }) => {
  48. const dashboardId = dashboard?.id;
  49. const [recentUsers, setRecentUsers] = useState<UserViewDTO[]>([]);
  50. const nbOfUsers = recentUsers.length - iconLimit + 1;
  51. const tooManyUsers = nbOfUsers > 9;
  52. const theme = useTheme();
  53. const mainStyles = getPresenceIndicatorsStyles(theme, tooManyUsers);
  54. const iconStyles = getUserIconStyles(theme, false, true);
  55. useEffect(() => {
  56. if (!dashboardId || !dashboard?.meta.url) {
  57. return undefined;
  58. }
  59. fetchRecentUsers(dashboardId, setRecentUsers);
  60. const interval = setInterval(() => fetchRecentUsers(dashboardId, setRecentUsers), refreshInterval);
  61. return () => {
  62. clearInterval(interval);
  63. };
  64. }, [dashboardId, dashboard]);
  65. const iconLimitReached = recentUsers.length > iconLimit;
  66. return (
  67. <>
  68. {recentUsers.length > 0 && (
  69. <div className={mainStyles.container} aria-label="Presence indicators container">
  70. {iconLimitReached && (
  71. <Button
  72. variant="secondary"
  73. className={cx(iconStyles.textIcon, iconStyles.icon, mainStyles.moreIcon)}
  74. aria-label="More users icon"
  75. onClick={() => openDrawer(AnalyticsTab.Users)}
  76. >
  77. {tooManyUsers ? '...' : `+${nbOfUsers}`}
  78. </Button>
  79. )}
  80. {recentUsers
  81. .slice(0, iconLimitReached ? iconLimit - 1 : iconLimit)
  82. .reverse()
  83. .map((userView) => (
  84. <UserIcon key={userView.user.id} userView={userView} showBorder={true} />
  85. ))}
  86. </div>
  87. )}
  88. </>
  89. );
  90. };
  91. export const initPresenceIndicators = () => {
  92. addCustomLeftAction({
  93. show: () => true,
  94. component: connector(PresenceIndicators),
  95. index: 'end',
  96. });
  97. };