feat(site): Add deployment-wide DAU chart (#5810)

This commit is contained in:
Presley Pizzo
2023-01-25 22:03:47 -03:00
committed by GitHub
parent e7b8318b87
commit 16d8cc4176
26 changed files with 568 additions and 31 deletions
+6
View File
@@ -644,6 +644,12 @@ export const getTemplateDAUs = async (
return response.data
}
export const getDeploymentDAUs =
async (): Promise<TypesGen.DeploymentDAUsResponse> => {
const response = await axios.get(`/api/v2/insights/daus`)
return response.data
}
export const getTemplateACL = async (
templateId: string,
): Promise<TypesGen.TemplateACL> => {
+5
View File
@@ -342,6 +342,11 @@ export interface DeploymentConfigField<T extends Flaggable> {
readonly value: T
}
// From codersdk/insights.go
export interface DeploymentDAUsResponse {
readonly entries: DAUEntry[]
}
// From codersdk/features.go
export interface Entitlements {
readonly features: Record<FeatureName, Feature>
@@ -13,7 +13,7 @@ describe("DAUChart", () => {
it("renders a helpful paragraph on empty state", async () => {
render(
<DAUChart
templateDAUs={{
daus={{
entries: [],
}}
/>,
@@ -24,7 +24,7 @@ describe("DAUChart", () => {
it("renders a graph", async () => {
render(
<DAUChart
templateDAUs={{
daus={{
entries: [{ date: "2020-01-01", amount: 1 }],
}}
/>,
@@ -38,19 +38,17 @@ ChartJS.register(
)
export interface DAUChartProps {
templateDAUs: TypesGen.TemplateDAUsResponse
daus: TypesGen.TemplateDAUsResponse | TypesGen.DeploymentDAUsResponse
}
export const Language = {
loadingText: "DAU stats are loading. Check back later.",
chartTitle: "Daily Active Users",
}
export const DAUChart: FC<DAUChartProps> = ({
templateDAUs: templateMetricsData,
}) => {
export const DAUChart: FC<DAUChartProps> = ({ daus }) => {
const theme: Theme = useTheme()
if (templateMetricsData.entries.length === 0) {
if (daus.entries.length === 0) {
return (
// We generate hidden element to prove this path is taken in the test
// and through site inspection.
@@ -60,11 +58,11 @@ export const DAUChart: FC<DAUChartProps> = ({
)
}
const labels = templateMetricsData.entries.map((val) => {
const labels = daus.entries.map((val) => {
return dayjs(val.date).format("YYYY-MM-DD")
})
const data = templateMetricsData.entries.map((val) => {
const data = daus.entries.map((val) => {
return val.amount
})
@@ -5,13 +5,18 @@ import { Sidebar } from "./Sidebar"
import { createContext, Suspense, useContext, FC } from "react"
import { useMachine } from "@xstate/react"
import { Loader } from "components/Loader/Loader"
import { DeploymentConfig } from "api/typesGenerated"
import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated"
import { deploymentConfigMachine } from "xServices/deploymentConfig/deploymentConfigMachine"
import { RequirePermission } from "components/RequirePermission/RequirePermission"
import { usePermissions } from "hooks/usePermissions"
import { Outlet } from "react-router-dom"
type DeploySettingsContextValue = { deploymentConfig: DeploymentConfig }
type DeploySettingsContextValue = {
deploymentConfig: DeploymentConfig
getDeploymentConfigError: unknown
deploymentDAUs?: DeploymentDAUsResponse
getDeploymentDAUsError: unknown
}
const DeploySettingsContext = createContext<
DeploySettingsContextValue | undefined
@@ -30,7 +35,12 @@ export const useDeploySettings = (): DeploySettingsContextValue => {
export const DeploySettingsLayout: FC = () => {
const [state] = useMachine(deploymentConfigMachine)
const styles = useStyles()
const { deploymentConfig } = state.context
const {
deploymentConfig,
deploymentDAUs,
getDeploymentConfigError,
getDeploymentDAUsError,
} = state.context
const permissions = usePermissions()
return (
@@ -41,7 +51,12 @@ export const DeploySettingsLayout: FC = () => {
<main className={styles.content}>
{deploymentConfig ? (
<DeploySettingsContext.Provider
value={{ deploymentConfig: deploymentConfig }}
value={{
deploymentConfig,
getDeploymentConfigError,
deploymentDAUs,
getDeploymentDAUsError,
}}
>
<Suspense fallback={<Loader />}>
<Outlet />
@@ -5,14 +5,19 @@ import { pageTitle } from "util/page"
import { GeneralSettingsPageView } from "./GeneralSettingsPageView"
const GeneralSettingsPage: FC = () => {
const { deploymentConfig: deploymentConfig } = useDeploySettings()
const { deploymentConfig, deploymentDAUs, getDeploymentDAUsError } =
useDeploySettings()
return (
<>
<Helmet>
<title>{pageTitle("General Settings")}</title>
</Helmet>
<GeneralSettingsPageView deploymentConfig={deploymentConfig} />
<GeneralSettingsPageView
deploymentConfig={deploymentConfig}
deploymentDAUs={deploymentDAUs}
getDeploymentDAUsError={getDeploymentDAUsError}
/>
</>
)
}
@@ -1,4 +1,8 @@
import { ComponentMeta, Story } from "@storybook/react"
import {
makeMockApiError,
MockDeploymentDAUResponse,
} from "testHelpers/entities"
import {
GeneralSettingsPageView,
GeneralSettingsPageViewProps,
@@ -24,6 +28,9 @@ export default {
},
},
},
deploymentDAUs: {
defaultValue: MockDeploymentDAUResponse,
},
},
} as ComponentMeta<typeof GeneralSettingsPageView>
@@ -31,3 +38,14 @@ const Template: Story<GeneralSettingsPageViewProps> = (args) => (
<GeneralSettingsPageView {...args} />
)
export const Page = Template.bind({})
export const NoDAUs = Template.bind({})
NoDAUs.args = {
deploymentDAUs: undefined,
}
export const DAUError = Template.bind({})
DAUError.args = {
deploymentDAUs: undefined,
getDeploymentDAUsError: makeMockApiError({ message: "Error fetching DAUs." }),
}
@@ -1,12 +1,19 @@
import { DeploymentConfig } from "api/typesGenerated"
import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { DAUChart } from "components/DAUChart/DAUChart"
import { Header } from "components/DeploySettingsLayout/Header"
import OptionsTable from "components/DeploySettingsLayout/OptionsTable"
import { Stack } from "components/Stack/Stack"
export type GeneralSettingsPageViewProps = {
deploymentConfig: Pick<DeploymentConfig, "access_url" | "wildcard_access_url">
deploymentDAUs?: DeploymentDAUsResponse
getDeploymentDAUsError: unknown
}
export const GeneralSettingsPageView = ({
deploymentConfig,
deploymentDAUs,
getDeploymentDAUsError,
}: GeneralSettingsPageViewProps): JSX.Element => {
return (
<>
@@ -15,12 +22,18 @@ export const GeneralSettingsPageView = ({
description="Information about your Coder deployment."
docsHref="https://coder.com/docs/coder-oss/latest/admin/configure"
/>
<OptionsTable
options={{
access_url: deploymentConfig.access_url,
wildcard_access_url: deploymentConfig.wildcard_access_url,
}}
/>
<Stack spacing={4}>
{Boolean(getDeploymentDAUsError) && (
<AlertBanner error={getDeploymentDAUsError} severity="error" />
)}
{deploymentDAUs && <DAUChart daus={deploymentDAUs} />}
<OptionsTable
options={{
access_url: deploymentConfig.access_url,
wildcard_access_url: deploymentConfig.wildcard_access_url,
}}
/>
</Stack>
</>
)
}
@@ -12,7 +12,7 @@ import { TemplateStats } from "components/TemplateStats/TemplateStats"
import { VersionsTable } from "components/VersionsTable/VersionsTable"
import frontMatter from "front-matter"
import { FC } from "react"
import { DAUChart } from "./DAUChart"
import { DAUChart } from "../../../components/DAUChart/DAUChart"
export interface TemplateSummaryPageViewProps {
template: Template
@@ -46,7 +46,7 @@ export const TemplateSummaryPageView: FC<
template={template}
activeVersion={activeTemplateVersion}
/>
{templateDAUs && <DAUChart templateDAUs={templateDAUs} />}
{templateDAUs && <DAUChart daus={templateDAUs} />}
<TemplateResourcesTable
resources={getStartedResources(templateResources)}
/>
+7
View File
@@ -13,6 +13,13 @@ export const MockTemplateDAUResponse: TypesGen.TemplateDAUsResponse = {
{ date: "2022-08-30T00:00:00Z", amount: 1 },
],
}
export const MockDeploymentDAUResponse: TypesGen.DeploymentDAUsResponse = {
entries: [
{ date: "2022-08-27T00:00:00Z", amount: 1 },
{ date: "2022-08-29T00:00:00Z", amount: 2 },
{ date: "2022-08-30T00:00:00Z", amount: 1 },
],
}
export const MockSessionToken: TypesGen.LoginWithPasswordResponse = {
session_token: "my-session-token",
}
+4
View File
@@ -10,6 +10,10 @@ export const handlers = [
return res(ctx.status(200), ctx.json(M.MockTemplateDAUResponse))
}),
rest.get("/api/v2/insights/daus", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockDeploymentDAUResponse))
}),
// build info
rest.get("/api/v2/buildinfo", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockBuildInfo))
@@ -1,5 +1,5 @@
import { getDeploymentConfig } from "api/api"
import { DeploymentConfig } from "api/typesGenerated"
import { getDeploymentConfig, getDeploymentDAUs } from "api/api"
import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated"
import { createMachine, assign } from "xstate"
export const deploymentConfigMachine = createMachine(
@@ -11,29 +11,49 @@ export const deploymentConfigMachine = createMachine(
context: {} as {
deploymentConfig?: DeploymentConfig
getDeploymentConfigError?: unknown
deploymentDAUs?: DeploymentDAUsResponse
getDeploymentDAUsError?: unknown
},
events: {} as { type: "LOAD" },
services: {} as {
getDeploymentConfig: {
data: DeploymentConfig
}
getDeploymentDAUs: {
data: DeploymentDAUsResponse
}
},
},
tsTypes: {} as import("./deploymentConfigMachine.typegen").Typegen0,
initial: "loading",
initial: "config",
states: {
loading: {
config: {
invoke: {
src: "getDeploymentConfig",
onDone: {
target: "done",
target: "daus",
actions: ["assignDeploymentConfig"],
},
onError: {
target: "done",
target: "daus",
actions: ["assignGetDeploymentConfigError"],
},
},
tags: "loading",
},
daus: {
invoke: {
src: "getDeploymentDAUs",
onDone: {
target: "done",
actions: ["assignDeploymentDAUs"],
},
onError: {
target: "done",
actions: ["assignGetDeploymentDAUsError"],
},
},
tags: "loading",
},
done: {
type: "final",
@@ -43,6 +63,7 @@ export const deploymentConfigMachine = createMachine(
{
services: {
getDeploymentConfig: getDeploymentConfig,
getDeploymentDAUs: getDeploymentDAUs,
},
actions: {
assignDeploymentConfig: assign({
@@ -51,6 +72,12 @@ export const deploymentConfigMachine = createMachine(
assignGetDeploymentConfigError: assign({
getDeploymentConfigError: (_, { data }) => data,
}),
assignDeploymentDAUs: assign({
deploymentDAUs: (_, { data }) => data,
}),
assignGetDeploymentDAUsError: assign({
getDeploymentDAUsError: (_, { data }) => data,
}),
},
},
)