mirror of https://github.com/coder/coder.git
refactor: Show template versions as timeline (#4800)
This commit is contained in:
parent
cc655672eb
commit
46e0953876
|
@ -94,7 +94,7 @@ func displayTemplateVersions(activeVersionID uuid.UUID, templateVersions ...code
|
|||
rows[i] = templateVersionRow{
|
||||
Name: templateVersion.Name,
|
||||
CreatedAt: templateVersion.CreatedAt,
|
||||
CreatedBy: templateVersion.CreatedByName,
|
||||
CreatedBy: templateVersion.CreatedBy.Username,
|
||||
Status: strings.Title(string(templateVersion.Job.Status)),
|
||||
Active: activeStatus,
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestTemplateVersions(t *testing.T) {
|
|||
require.NoError(t, <-errC)
|
||||
|
||||
pty.ExpectMatch(version.Name)
|
||||
pty.ExpectMatch(version.CreatedByName)
|
||||
pty.ExpectMatch(version.CreatedBy.Username)
|
||||
pty.ExpectMatch("Active")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -327,7 +327,7 @@ CREATE TABLE template_versions (
|
|||
name character varying(64) NOT NULL,
|
||||
readme character varying(1048576) NOT NULL,
|
||||
job_id uuid NOT NULL,
|
||||
created_by uuid
|
||||
created_by uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE templates (
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE template_versions ALTER COLUMN created_by DROP NOT NULL;
|
|
@ -0,0 +1,4 @@
|
|||
BEGIN;
|
||||
ALTER TABLE template_versions ALTER COLUMN created_by SET NOT NULL;
|
||||
UPDATE template_versions SET created_by = '00000000-0000-0000-0000-000000000000'::uuid WHERE created_by IS NULL;
|
||||
COMMIT;
|
|
@ -600,7 +600,7 @@ type TemplateVersion struct {
|
|||
Name string `db:"name" json:"name"`
|
||||
Readme string `db:"readme" json:"readme"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
|
|
@ -3634,7 +3634,7 @@ type InsertTemplateVersionParams struct {
|
|||
Name string `db:"name" json:"name"`
|
||||
Readme string `db:"readme" json:"readme"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.NullUUID `db:"created_by" json:"created_by"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) (TemplateVersion, error) {
|
||||
|
|
|
@ -658,10 +658,7 @@ func (api *API) autoImportTemplate(ctx context.Context, opts autoImportTemplateO
|
|||
Name: namesgenerator.GetRandomName(1),
|
||||
Readme: "",
|
||||
JobID: job.ID,
|
||||
CreatedBy: uuid.NullUUID{
|
||||
UUID: opts.userID,
|
||||
Valid: true,
|
||||
},
|
||||
CreatedBy: opts.userID,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert template version: %w", err)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -43,16 +42,16 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy)
|
||||
user, err := api.Database.GetUserByID(ctx, templateVersion.CreatedBy)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching creator name.",
|
||||
Message: "Internal error on fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user))
|
||||
}
|
||||
|
||||
func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -523,15 +522,15 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque
|
|||
})
|
||||
return err
|
||||
}
|
||||
createdByName, err := getUsernameByUserID(ctx, store, version.CreatedBy)
|
||||
user, err := store.GetUserByID(ctx, version.CreatedBy)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching creator name.",
|
||||
Message: "Internal error on fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job), createdByName))
|
||||
apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job), user))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -581,16 +580,16 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy)
|
||||
user, err := api.Database.GetUserByID(ctx, templateVersion.CreatedBy)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching creator name.",
|
||||
Message: "Internal error on fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), user))
|
||||
}
|
||||
|
||||
func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -841,10 +840,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
|
|||
Name: req.Name,
|
||||
Readme: "",
|
||||
JobID: provisionerJob.ID,
|
||||
CreatedBy: uuid.NullUUID{
|
||||
UUID: apiKey.UserID,
|
||||
Valid: true,
|
||||
},
|
||||
CreatedBy: apiKey.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert template version: %w", err)
|
||||
|
@ -859,16 +855,16 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
|
|||
}
|
||||
aReq.New = templateVersion
|
||||
|
||||
createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy)
|
||||
user, err := api.Database.GetUserByID(ctx, templateVersion.CreatedBy)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching creator name.",
|
||||
Message: "Internal error on fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), createdByName))
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), user))
|
||||
}
|
||||
|
||||
// templateVersionResources returns the workspace agent resources associated
|
||||
|
@ -926,18 +922,17 @@ func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) {
|
|||
api.provisionerJobLogs(rw, r, job)
|
||||
}
|
||||
|
||||
func getUsernameByUserID(ctx context.Context, db database.Store, userID uuid.NullUUID) (string, error) {
|
||||
if !userID.Valid {
|
||||
return "", nil
|
||||
func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob, user database.User) codersdk.TemplateVersion {
|
||||
createdBy := codersdk.User{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Status: codersdk.UserStatus(user.Status),
|
||||
Roles: []codersdk.Role{},
|
||||
AvatarURL: user.AvatarURL.String,
|
||||
}
|
||||
user, err := db.GetUserByID(ctx, userID.UUID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user.Username, nil
|
||||
}
|
||||
|
||||
func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob, createdByName string) codersdk.TemplateVersion {
|
||||
return codersdk.TemplateVersion{
|
||||
ID: version.ID,
|
||||
TemplateID: &version.TemplateID.UUID,
|
||||
|
@ -947,7 +942,6 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi
|
|||
Name: version.Name,
|
||||
Job: job,
|
||||
Readme: version.Readme,
|
||||
CreatedByID: version.CreatedBy.UUID,
|
||||
CreatedByName: createdByName,
|
||||
CreatedBy: createdBy,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@ type TemplateVersion struct {
|
|||
Name string `json:"name"`
|
||||
Job ProvisionerJob `json:"job"`
|
||||
Readme string `json:"readme"`
|
||||
CreatedByID uuid.UUID `json:"created_by_id"`
|
||||
CreatedByName string `json:"created_by_name"`
|
||||
CreatedBy User `json:"created_by"`
|
||||
}
|
||||
|
||||
// TemplateVersion returns a template version by ID.
|
||||
|
|
|
@ -277,7 +277,7 @@ func Test_diff(t *testing.T) {
|
|||
UpdatedAt: time.Now(),
|
||||
OrganizationID: uuid.UUID{3},
|
||||
Name: "rust",
|
||||
CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
|
||||
CreatedBy: uuid.UUID{4},
|
||||
},
|
||||
exp: audit.Map{
|
||||
"id": audit.OldNew{Old: "", New: uuid.UUID{1}.String()},
|
||||
|
@ -296,11 +296,11 @@ func Test_diff(t *testing.T) {
|
|||
UpdatedAt: time.Now(),
|
||||
OrganizationID: uuid.UUID{3},
|
||||
Name: "rust",
|
||||
CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
|
||||
CreatedBy: uuid.UUID{4},
|
||||
},
|
||||
exp: audit.Map{
|
||||
"id": audit.OldNew{Old: "", New: uuid.UUID{1}.String()},
|
||||
"created_by": audit.OldNew{Old: "null", New: uuid.UUID{4}.String()},
|
||||
"created_by": audit.OldNew{Old: "", New: uuid.UUID{4}.String()},
|
||||
"name": audit.OldNew{Old: "", New: "rust"},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -656,8 +656,7 @@ export interface TemplateVersion {
|
|||
readonly name: string
|
||||
readonly job: ProvisionerJob
|
||||
readonly readme: string
|
||||
readonly created_by_id: string
|
||||
readonly created_by_name: string
|
||||
readonly created_by: User
|
||||
}
|
||||
|
||||
// From codersdk/templates.go
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import formatRelative from "date-fns/formatRelative"
|
||||
import { FC } from "react"
|
||||
|
||||
export interface BuildDateRow {
|
||||
date: Date
|
||||
}
|
||||
|
||||
export const BuildDateRow: FC<BuildDateRow> = ({ date }) => {
|
||||
const styles = useStyles()
|
||||
// We only want the message related to the date since the time is displayed
|
||||
// inside of the build row
|
||||
const displayDate = formatRelative(date, new Date()).split("at")[0]
|
||||
|
||||
return (
|
||||
<TableRow className={styles.buildDateRow}>
|
||||
<TableCell
|
||||
className={styles.buildDateCell}
|
||||
title={date.toLocaleDateString()}
|
||||
>
|
||||
{displayDate}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
buildDateRow: {
|
||||
background: theme.palette.background.paper,
|
||||
|
||||
"&:not(:first-child) td": {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
},
|
||||
|
||||
buildDateCell: {
|
||||
padding: `${theme.spacing(1, 4)} !important`,
|
||||
background: `${theme.palette.background.paperLight} !important`,
|
||||
fontSize: 12,
|
||||
position: "relative",
|
||||
color: theme.palette.text.secondary,
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
}))
|
|
@ -4,73 +4,37 @@ import TableBody from "@material-ui/core/TableBody"
|
|||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableContainer from "@material-ui/core/TableContainer"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import { FC, Fragment } from "react"
|
||||
import { Timeline } from "components/Timeline/Timeline"
|
||||
import { FC } from "react"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { EmptyState } from "../EmptyState/EmptyState"
|
||||
import { TableLoader } from "../TableLoader/TableLoader"
|
||||
import { BuildDateRow } from "./BuildDateRow"
|
||||
import { BuildRow } from "./BuildRow"
|
||||
|
||||
export const Language = {
|
||||
emptyMessage: "No builds found",
|
||||
inProgressLabel: "In progress",
|
||||
actionLabel: "Action",
|
||||
durationLabel: "Duration",
|
||||
startedAtLabel: "Started at",
|
||||
statusLabel: "Status",
|
||||
}
|
||||
|
||||
export interface BuildsTableProps {
|
||||
builds?: TypesGen.WorkspaceBuild[]
|
||||
}
|
||||
|
||||
const groupBuildsByDate = (builds?: TypesGen.WorkspaceBuild[]) => {
|
||||
const buildsByDate: Record<string, TypesGen.WorkspaceBuild[]> = {}
|
||||
|
||||
if (!builds) {
|
||||
return
|
||||
}
|
||||
|
||||
builds.forEach((build) => {
|
||||
const dateKey = new Date(build.created_at).toDateString()
|
||||
|
||||
// Unsure why this is here but we probably need to fix it.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- see above
|
||||
if (buildsByDate[dateKey]) {
|
||||
buildsByDate[dateKey].push(build)
|
||||
} else {
|
||||
buildsByDate[dateKey] = [build]
|
||||
}
|
||||
})
|
||||
|
||||
return buildsByDate
|
||||
}
|
||||
|
||||
export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
|
||||
builds,
|
||||
}) => {
|
||||
const isLoading = !builds
|
||||
const buildsByDate = groupBuildsByDate(builds)
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table data-testid="builds-table" aria-describedby="builds table">
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
|
||||
{buildsByDate &&
|
||||
Object.keys(buildsByDate).map((dateStr) => {
|
||||
const builds = buildsByDate[dateStr]
|
||||
|
||||
return (
|
||||
<Fragment key={dateStr}>
|
||||
<BuildDateRow date={new Date(dateStr)} />
|
||||
{builds.map((build) => (
|
||||
<BuildRow key={build.id} build={build} />
|
||||
))}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{builds ? (
|
||||
<Timeline
|
||||
items={builds}
|
||||
getDate={(build) => new Date(build.created_at)}
|
||||
row={(build) => <BuildRow key={build.id} build={build} />}
|
||||
/>
|
||||
) : (
|
||||
<TableLoader />
|
||||
)}
|
||||
|
||||
{builds && builds.length === 0 && (
|
||||
<TableRow>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import { TimelineDateRow } from "components/Timeline/TimelineDateRow"
|
||||
import { Fragment } from "react"
|
||||
|
||||
type GetDateFn<TData> = (data: TData) => Date
|
||||
|
||||
const groupByDate = <TData,>(
|
||||
items: TData[],
|
||||
getDate: GetDateFn<TData>,
|
||||
): Record<string, TData[]> => {
|
||||
const itemsByDate: Record<string, TData[]> = {}
|
||||
|
||||
items.forEach((item) => {
|
||||
const dateKey = getDate(item).toDateString()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Is not a guarantee a value is defined when access it dynamically
|
||||
if (itemsByDate[dateKey]) {
|
||||
itemsByDate[dateKey].push(item)
|
||||
} else {
|
||||
itemsByDate[dateKey] = [item]
|
||||
}
|
||||
})
|
||||
|
||||
return itemsByDate
|
||||
}
|
||||
|
||||
export interface TimelineProps<TData> {
|
||||
items: TData[]
|
||||
getDate: GetDateFn<TData>
|
||||
row: (item: TData) => JSX.Element
|
||||
}
|
||||
|
||||
export const Timeline = <TData,>({
|
||||
items,
|
||||
getDate,
|
||||
row,
|
||||
}: TimelineProps<TData>): JSX.Element => {
|
||||
const itemsByDate = groupByDate(items, getDate)
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(itemsByDate).map((dateStr) => {
|
||||
const items = itemsByDate[dateStr]
|
||||
|
||||
return (
|
||||
<Fragment key={dateStr}>
|
||||
<TimelineDateRow date={new Date(dateStr)} />
|
||||
{items.map(row)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -4,11 +4,11 @@ import TableRow from "@material-ui/core/TableRow"
|
|||
import formatRelative from "date-fns/formatRelative"
|
||||
import { FC } from "react"
|
||||
|
||||
export interface TableDateRow {
|
||||
export interface TimelineDateRow {
|
||||
date: Date
|
||||
}
|
||||
|
||||
export const TableDateRow: FC<TableDateRow> = ({ date }) => {
|
||||
export const TimelineDateRow: FC<TimelineDateRow> = ({ date }) => {
|
||||
const styles = useStyles()
|
||||
// We only want the message related to the date since the time is displayed
|
||||
// inside of the build row
|
|
@ -0,0 +1,88 @@
|
|||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import { TemplateVersion } from "api/typesGenerated"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export interface VersionRowProps {
|
||||
version: TemplateVersion
|
||||
}
|
||||
|
||||
export const VersionRow: React.FC<VersionRowProps> = ({ version }) => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("templatePage")
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={styles.versionRow}
|
||||
data-testid={`version-${version.id}`}
|
||||
>
|
||||
<TableCell className={styles.versionCell}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
className={styles.versionWrapper}
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<UserAvatar
|
||||
username={version.created_by.username}
|
||||
avatarURL={version.created_by.avatar_url}
|
||||
/>
|
||||
<Stack
|
||||
className={styles.versionSummary}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
>
|
||||
<span>
|
||||
<strong>{version.created_by.username}</strong>{" "}
|
||||
{t("createdVersion")} <strong>{version.name}</strong>
|
||||
</span>
|
||||
|
||||
<span className={styles.versionTime}>
|
||||
{new Date(version.created_at).toLocaleTimeString()}
|
||||
</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
versionRow: {
|
||||
"&:not(:last-child) td:before": {
|
||||
position: "absolute",
|
||||
top: 20,
|
||||
left: 50,
|
||||
display: "block",
|
||||
content: "''",
|
||||
height: "100%",
|
||||
width: 2,
|
||||
background: theme.palette.divider,
|
||||
},
|
||||
},
|
||||
|
||||
versionWrapper: {
|
||||
padding: theme.spacing(2, 4),
|
||||
},
|
||||
|
||||
versionCell: {
|
||||
padding: "0 !important",
|
||||
position: "relative",
|
||||
borderBottom: 0,
|
||||
},
|
||||
|
||||
versionSummary: {
|
||||
...theme.typography.body1,
|
||||
fontFamily: "inherit",
|
||||
},
|
||||
|
||||
versionTime: {
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
}))
|
|
@ -1,16 +1,15 @@
|
|||
import Box from "@material-ui/core/Box"
|
||||
import { Theme } from "@material-ui/core/styles"
|
||||
import Table from "@material-ui/core/Table"
|
||||
import TableBody from "@material-ui/core/TableBody"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableContainer from "@material-ui/core/TableContainer"
|
||||
import TableHead from "@material-ui/core/TableHead"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import useTheme from "@material-ui/styles/useTheme"
|
||||
import { Timeline } from "components/Timeline/Timeline"
|
||||
import { FC } from "react"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { EmptyState } from "../EmptyState/EmptyState"
|
||||
import { TableLoader } from "../TableLoader/TableLoader"
|
||||
import { VersionRow } from "./VersionRow"
|
||||
|
||||
export const Language = {
|
||||
emptyMessage: "No versions found",
|
||||
|
@ -26,45 +25,21 @@ export interface VersionsTableProps {
|
|||
export const VersionsTable: FC<React.PropsWithChildren<VersionsTableProps>> = ({
|
||||
versions,
|
||||
}) => {
|
||||
const isLoading = !versions
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table data-testid="versions-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="30%">{Language.nameLabel}</TableCell>
|
||||
<TableCell width="30%">{Language.createdAtLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.createdByLabel}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{versions &&
|
||||
versions
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((version) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={version.id}
|
||||
data-testid={`version-${version.id}`}
|
||||
>
|
||||
<TableCell>{version.name}</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(version.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{version.created_by_name}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
{versions ? (
|
||||
<Timeline
|
||||
items={versions.slice().reverse()}
|
||||
getDate={(version) => new Date(version.created_at)}
|
||||
row={(version) => (
|
||||
<VersionRow version={version} key={version.id} />
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<TableLoader />
|
||||
)}
|
||||
|
||||
{versions && versions.length === 0 && (
|
||||
<TableRow>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"deleteSuccess": "Template successfully deleted."
|
||||
"deleteSuccess": "Template successfully deleted.",
|
||||
"createdVersion": "created the version"
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ import {
|
|||
import { PaginationWidget } from "components/PaginationWidget/PaginationWidget"
|
||||
import { SearchBarWithFilter } from "components/SearchBarWithFilter/SearchBarWithFilter"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { TableDateRow } from "components/TableDateRow/TableDateRow"
|
||||
import { TableLoader } from "components/TableLoader/TableLoader"
|
||||
import { Timeline } from "components/Timeline/Timeline"
|
||||
import { AuditHelpTooltip } from "components/Tooltips"
|
||||
import { FC, Fragment } from "react"
|
||||
import { FC } from "react"
|
||||
import { PaginationMachineRef } from "xServices/pagination/paginationXService"
|
||||
|
||||
export const Language = {
|
||||
|
@ -37,27 +37,6 @@ const presetFilters = [
|
|||
{ query: "resource_type:user action:delete", name: "Deleted users" },
|
||||
]
|
||||
|
||||
const groupAuditLogsByDate = (auditLogs?: AuditLog[]) => {
|
||||
const auditLogsByDate: Record<string, AuditLog[]> = {}
|
||||
|
||||
if (!auditLogs) {
|
||||
return
|
||||
}
|
||||
|
||||
auditLogs.forEach((auditLog) => {
|
||||
const dateKey = new Date(auditLog.time).toDateString()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- TODO look into this
|
||||
if (auditLogsByDate[dateKey]) {
|
||||
auditLogsByDate[dateKey].push(auditLog)
|
||||
} else {
|
||||
auditLogsByDate[dateKey] = [auditLog]
|
||||
}
|
||||
})
|
||||
|
||||
return auditLogsByDate
|
||||
}
|
||||
|
||||
export interface AuditPageViewProps {
|
||||
auditLogs?: AuditLog[]
|
||||
count?: number
|
||||
|
@ -75,7 +54,6 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
|
|||
}) => {
|
||||
const isLoading = auditLogs === undefined || count === undefined
|
||||
const isEmpty = !isLoading && auditLogs.length === 0
|
||||
const auditLogsByDate = groupAuditLogsByDate(auditLogs)
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
|
@ -101,19 +79,13 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
|
|||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
|
||||
{auditLogsByDate &&
|
||||
Object.keys(auditLogsByDate).map((dateStr) => {
|
||||
const auditLogs = auditLogsByDate[dateStr]
|
||||
|
||||
return (
|
||||
<Fragment key={dateStr}>
|
||||
<TableDateRow date={new Date(dateStr)} />
|
||||
{auditLogs.map((log) => (
|
||||
<AuditLogRow key={log.id} auditLog={log} />
|
||||
))}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{auditLogs && (
|
||||
<Timeline
|
||||
items={auditLogs}
|
||||
getDate={(log) => new Date(log.time)}
|
||||
row={(log) => <AuditLogRow key={log.id} auditLog={log} />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isEmpty && (
|
||||
<TableRow>
|
||||
|
|
|
@ -175,8 +175,7 @@ name:Template test
|
|||
You can add instructions here
|
||||
|
||||
[Some link info](https://coder.com)`,
|
||||
created_by_id: "test-creator-id",
|
||||
created_by_name: "test_creator",
|
||||
created_by: MockUser,
|
||||
}
|
||||
|
||||
export const MockTemplate: TypesGen.Template = {
|
||||
|
|
Loading…
Reference in New Issue