diff --git a/changes/issue-2224-query-author-avatar b/changes/issue-2224-query-author-avatar new file mode 100644 index 0000000000..dbac0038e6 --- /dev/null +++ b/changes/issue-2224-query-author-avatar @@ -0,0 +1,2 @@ +* Add author avatars to manage queries page and edit query form +* Format query last modified time in words \ No newline at end of file diff --git a/frontend/components/Avatar/Avatar.tsx b/frontend/components/Avatar/Avatar.tsx index cc93699b40..ff6e94808c 100644 --- a/frontend/components/Avatar/Avatar.tsx +++ b/frontend/components/Avatar/Avatar.tsx @@ -1,6 +1,8 @@ import React, { useState, useCallback } from "react"; import classnames from "classnames"; +import { DEFAULT_GRAVATAR_LINK } from "utilities/constants"; + interface IAvatarUserInterface { gravatarURL: string; } @@ -24,9 +26,8 @@ const Avatar = ({ className, size, user }: IAvatarInterface): JSX.Element => { setIsError(true); }, []); - const isSmall = size !== undefined && size.toLowerCase() === "small"; const avatarClasses = classnames(baseClass, className, { - [`${baseClass}--${size}`]: isSmall, + [`${baseClass}--${size?.toLowerCase()}`]: !!size, }); const { gravatarURL } = user; @@ -35,7 +36,7 @@ const Avatar = ({ className, size, user }: IAvatarInterface): JSX.Element => { {!isLoading diff --git a/frontend/components/Avatar/_styles.scss b/frontend/components/Avatar/_styles.scss index 831ed346ed..e06fb5dcd4 100644 --- a/frontend/components/Avatar/_styles.scss +++ b/frontend/components/Avatar/_styles.scss @@ -8,6 +8,10 @@ @include size(32px); } + &--xsmall { + @include size(16px); + } + img { &.default { display: none; diff --git a/frontend/components/TableContainer/DataTable/DataTable.tsx b/frontend/components/TableContainer/DataTable/DataTable.tsx index 160cf2b13e..a0c96691e5 100644 --- a/frontend/components/TableContainer/DataTable/DataTable.tsx +++ b/frontend/components/TableContainer/DataTable/DataTable.tsx @@ -435,7 +435,14 @@ const DataTable = ({ > {row.cells.map((cell: any) => { return ( - {cell.render("Cell")} + + {cell.render("Cell")} + ); })} diff --git a/frontend/interfaces/query.ts b/frontend/interfaces/query.ts index dde2291637..a0626ae88a 100644 --- a/frontend/interfaces/query.ts +++ b/frontend/interfaces/query.ts @@ -36,6 +36,7 @@ export interface IQuery { saved: boolean; author_id: number; author_name: string; + author_email: string; observer_can_run: boolean; packs: IPack[]; stats?: IScheduledQueryStats; diff --git a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx index 973ed31503..46cd312db3 100644 --- a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx +++ b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx @@ -109,7 +109,7 @@ const ManageQueriesPage = (): JSX.Element => { { // refetchOnMount: false, // refetchOnReconnect: false, - // refetchOnWindowFocus: false, + refetchOnWindowFocus: false, select: (data: IFleetQueriesResponse) => data.queries, } ); diff --git a/frontend/pages/queries/ManageQueriesPage/_styles.scss b/frontend/pages/queries/ManageQueriesPage/_styles.scss index a96a5bd6ab..b00c27d968 100644 --- a/frontend/pages/queries/ManageQueriesPage/_styles.scss +++ b/frontend/pages/queries/ManageQueriesPage/_styles.scss @@ -182,4 +182,15 @@ } } } + .author_name__cell { + img, + div, + span { + display: flex; + align-items: center; + } + div { + padding-right: $pad-small; + } + } } diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx index 487147c577..aff83910e2 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx @@ -3,11 +3,12 @@ // definitions for the selection row for some reason when we dont really need it. import React from "react"; import ReactTooltip from "react-tooltip"; -import format from "date-fns/format"; +import formatDistanceToNow from "date-fns/formatDistanceToNow"; import permissionsUtils from "utilities/permissions"; // @ts-ignore +import Avatar from "components/Avatar"; import Checkbox from "components/forms/fields/Checkbox"; import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; @@ -19,6 +20,7 @@ import PATHS from "router/paths"; import { IQuery } from "interfaces/query"; import { IUser } from "interfaces/user"; +import { addGravatarUrlToResource } from "fleet/helpers"; interface IQueryRow { id: string; @@ -112,9 +114,19 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => { /> ), accessor: "author_name", - Cell: (cellProps: ICellProps): JSX.Element => ( - - ), + Cell: (cellProps: ICellProps): JSX.Element => { + const { author_name, author_email } = cellProps.row.original; + const author = author_name === currentUser.name ? "You" : author_name; + return ( + + + {author} + + ); + }, sortType: "caseInsensitive", }, { @@ -127,7 +139,12 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => { ), accessor: "updated_at", Cell: (cellProps: ICellProps): JSX.Element => ( - + ), }, ]; diff --git a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx index a83a151f43..f7d63d9efd 100644 --- a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx +++ b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx @@ -4,6 +4,7 @@ import ReactTooltip from "react-tooltip"; import { size } from "lodash"; import { useDebouncedCallback } from "use-debounce/lib"; +import { addGravatarUrlToResource } from "fleet/helpers"; // @ts-ignore import { listCompatiblePlatforms, parseSqlTables } from "utilities/sql_tools"; @@ -11,6 +12,7 @@ import { AppContext } from "context/app"; import { QueryContext } from "context/query"; import { IQuery, IQueryFormData } from "interfaces/query"; +import Avatar from "components/Avatar"; import FleetAce from "components/FleetAce"; // @ts-ignore import validateQuery from "components/forms/validators/validate_query"; import Button from "components/buttons/Button"; @@ -169,6 +171,27 @@ const QueryForm = ({ } }; + const renderAuthor = (): JSX.Element | null => { + return storedQuery ? ( + <> + Author +
+ + + {storedQuery.author_name === currentUser?.name + ? "You" + : storedQuery.author_name} + +
+ + ) : null; + }; + const renderLabelComponent = (): JSX.Element | null => { if (!showOpenSchemaActionText) { return null; @@ -261,12 +284,17 @@ const QueryForm = ({ const renderRunForObserver = (
-

- {lastEditedQueryName} -

-

- {lastEditedQueryDescription} -

+
+
+

+ {lastEditedQueryName} +

+

+ {lastEditedQueryDescription} +

+
+
{renderAuthor()}
+