UX: Improve rough edges of AI usage page (#1014)

* UX: Improve rough edges of AI usage page

* Ensure all text uses I18n
* Change from <button> usage to <DButton>
* Use <AdminConfigAreaCard> in place of custom card styles
* Format numbers nicely using our number format helper,
  show full values on hover using title attr
* Ensure 0 is always shown for counters, instead of being blank

* FEATURE: Load usage data after page load

Use ConditionalLoadingSpinner to hide load of usage
data, this prevents us hanging on page load with a white
screen.

* UX: Split users table, and add empty placeholders and page subheader

* DEV: Test fix
This commit is contained in:
Martin Brennan 2024-12-12 07:55:24 +10:00 committed by GitHub
parent a4440c507b
commit ae80494448
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 395 additions and 249 deletions

View File

@ -6,6 +6,9 @@ module DiscourseAi
requires_plugin "discourse-ai"
def show
end
def report
render json: AiUsageSerializer.new(create_report, root: false)
end

View File

@ -1,15 +1,22 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { eq } from "truth-helpers";
import { eq, gt, lt } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import DateTimeInputRange from "discourse/components/date-time-input-range";
import avatar from "discourse/helpers/avatar";
import concatClass from "discourse/helpers/concat-class";
import { ajax } from "discourse/lib/ajax";
import { number } from "discourse/lib/formatter";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators";
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
import AdminPageSubheader from "admin/components/admin-page-subheader";
import Chart from "admin/components/chart";
import ComboBox from "select-kit/components/combo-box";
@ -22,18 +29,30 @@ export default class AiUsage extends Component {
@tracked selectedModel;
@tracked selectedPeriod = "month";
@tracked isCustomDateActive = false;
@tracked loadingData = true;
constructor() {
super(...arguments);
this.fetchData();
}
@action
async fetchData() {
const response = await ajax("/admin/plugins/discourse-ai/ai-usage.json", {
data: {
start_date: moment(this.startDate).format("YYYY-MM-DD"),
end_date: moment(this.endDate).format("YYYY-MM-DD"),
feature: this.selectedFeature,
model: this.selectedModel,
},
});
const response = await ajax(
"/admin/plugins/discourse-ai/ai-usage-report.json",
{
data: {
start_date: moment(this.startDate).format("YYYY-MM-DD"),
end_date: moment(this.endDate).format("YYYY-MM-DD"),
feature: this.selectedFeature,
model: this.selectedModel,
},
}
);
this.data = response;
this.loadingData = false;
this._cachedFeatures = null;
this._cachedModels = null;
}
@action
@ -53,6 +72,11 @@ export default class AiUsage extends Component {
this.onFilterChange();
}
@bind
takeUsers(start, end) {
return this.data.users.slice(start, end);
}
normalizeTimeSeriesData(data) {
if (!data?.length) {
return [];
@ -79,16 +103,16 @@ export default class AiUsage extends Component {
);
for (
let m = moment(startDate);
m.isSameOrBefore(endDate);
m.add(1, interval)
let currentMoment = moment(startDate);
currentMoment.isSameOrBefore(endDate);
currentMoment.add(1, interval)
) {
const dateKey = m.format(format);
const dateKey = currentMoment.format(format);
const existingData = dataMap.get(dateKey);
normalized.push(
existingData || {
period: m.format(),
period: currentMoment.format(),
total_tokens: 0,
total_cached_tokens: 0,
total_request_tokens: 0,
@ -131,19 +155,19 @@ export default class AiUsage extends Component {
}),
datasets: [
{
label: "Response Tokens",
label: i18n("discourse_ai.usage.response_tokens"),
data: normalizedData.map((row) => row.total_response_tokens),
backgroundColor: colors.response,
},
{
label: "Net Request Tokens",
label: i18n("discourse_ai.usage.net_request_tokens"),
data: normalizedData.map(
(row) => row.total_request_tokens - row.total_cached_tokens
),
backgroundColor: colors.request,
},
{
label: "Cached Request Tokens",
label: i18n("discourse_ai.usage.cached_request_tokens"),
data: normalizedData.map((row) => row.total_cached_tokens),
backgroundColor: colors.cached,
},
@ -190,9 +214,9 @@ export default class AiUsage extends Component {
get periodOptions() {
return [
{ id: "day", name: "Last 24 Hours" },
{ id: "week", name: "Last Week" },
{ id: "month", name: "Last Month" },
{ id: "day", name: i18n("discourse_ai.usage.periods.last_day") },
{ id: "week", name: i18n("discourse_ai.usage.periods.last_week") },
{ id: "month", name: i18n("discourse_ai.usage.periods.last_month") },
];
}
@ -253,33 +277,31 @@ export default class AiUsage extends Component {
}
<template>
<div class="ai-usage">
<div class="ai-usage admin-detail">
<AdminPageSubheader
@titleLabel="discourse_ai.usage.short_title"
@learnMoreUrl="https://meta.discourse.org/t/estimating-costs-of-using-llms-for-discourse-ai/307243"
@descriptionLabel="discourse_ai.usage.subheader_description"
/>
<div class="ai-usage__filters">
<div class="ai-usage__filters-dates">
<div class="ai-usage__period-buttons">
{{#each this.periodOptions as |option|}}
<button
type="button"
class="btn
{{if
(eq this.selectedPeriod option.id)
'btn-primary'
'btn-default'
}}"
{{on "click" (fn this.onPeriodSelect option.id)}}
>
{{option.name}}
</button>
<DButton
class={{if
(eq this.selectedPeriod option.id)
"btn-primary"
"btn-default"
}}
@action={{fn this.onPeriodSelect option.id}}
@translatedLabel={{option.name}}
/>
{{/each}}
<button
type="button"
class="btn
{{if this.isCustomDateActive 'btn-primary' 'btn-default'}}"
{{on "click" this.onCustomDateClick}}
>
Custom...
</button>
<DButton
class={{if this.isCustomDateActive "btn-primary" "btn-default"}}
@action={{this.onCustomDateClick}}
@label="discourse_ai.usage.periods.custom"
/>
</div>
{{#if this.isCustomDateActive}}
@ -293,13 +315,7 @@ export default class AiUsage extends Component {
@showToTime={{false}}
/>
<button
type="button"
class="btn btn-default"
{{on "click" this.onRefreshDateRange}}
>
{{i18n "refresh"}}
</button>
<DButton @action={{this.onRefreshDateRange}} @label="refresh" />
</div>
{{/if}}
</div>
@ -322,162 +338,256 @@ export default class AiUsage extends Component {
/>
</div>
{{#if this.data}}
<div class="ai-usage__summary">
<h3 class="ai-usage__summary-title">
{{i18n "discourse_ai.usage.summary"}}
</h3>
<div class="ai-usage__summary-stats">
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.total_requests"
}}</span>
<span class="value">{{this.data.summary.total_requests}}</span>
<ConditionalLoadingSpinner @condition={{this.loadingData}}>
<AdminConfigAreaCard
@heading="discourse_ai.usage.summary"
class="ai-usage__summary"
>
<:content>
<div class="ai-usage__summary-stats">
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.total_requests"
}}</span>
<span
class="value"
title={{this.data.summary.total_requests}}
>{{number this.data.summary.total_requests}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.total_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_tokens}}
>{{number this.data.summary.total_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.request_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_request_tokens}}
>{{number this.data.summary.total_request_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.response_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_response_tokens}}
>{{number this.data.summary.total_response_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.cached_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_cached_tokens}}
>{{number this.data.summary.total_cached_tokens}}</span>
</div>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.total_tokens"
}}</span>
<span class="value">{{this.data.summary.total_tokens}}</span>
</:content>
</AdminConfigAreaCard>
<AdminConfigAreaCard
class="ai-usage__charts"
@heading="discourse_ai.usage.tokens_over_time"
>
<:content>
<div class="ai-usage__chart-container">
<Chart
@chartConfig={{this.chartConfig}}
class="ai-usage__chart"
/>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.request_tokens"
}}</span>
<span
class="value"
>{{this.data.summary.total_request_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.response_tokens"
}}</span>
<span
class="value"
>{{this.data.summary.total_response_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.cached_tokens"
}}</span>
<span
class="value"
>{{this.data.summary.total_cached_tokens}}</span>
</div>
</div>
</:content>
</AdminConfigAreaCard>
<div class="ai-usage__breakdowns">
<AdminConfigAreaCard
class="ai-usage__features"
@heading="discourse_ai.usage.features_breakdown"
>
<:content>
{{#unless this.data.features.length}}
<AdminConfigAreaEmptyList
@emptyLabel="discourse_ai.usage.no_features"
/>
{{/unless}}
{{#if this.data.features.length}}
<table class="ai-usage__features-table">
<thead>
<tr>
<th>{{i18n "discourse_ai.usage.feature"}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each this.data.features as |feature|}}
<tr class="ai-usage__features-row">
<td
class="ai-usage__features-cell"
>{{feature.feature_name}}</td>
<td
class="ai-usage__features-cell"
title={{feature.usage_count}}
>{{number feature.usage_count}}</td>
<td
class="ai-usage__features-cell"
title={{feature.total_tokens}}
>{{number feature.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</:content>
</AdminConfigAreaCard>
<AdminConfigAreaCard
class="ai-usage__models"
@heading="discourse_ai.usage.models_breakdown"
>
<:content>
{{#unless this.data.models.length}}
<AdminConfigAreaEmptyList
@emptyLabel="discourse_ai.usage.no_models"
/>
{{/unless}}
{{#if this.data.models.length}}
<table class="ai-usage__models-table">
<thead>
<tr>
<th>{{i18n "discourse_ai.usage.model"}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each this.data.models as |model|}}
<tr class="ai-usage__models-row">
<td class="ai-usage__models-cell">{{model.llm}}</td>
<td
class="ai-usage__models-cell"
title={{model.usage_count}}
>{{number model.usage_count}}</td>
<td
class="ai-usage__models-cell"
title={{model.total_tokens}}
>{{number model.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</:content>
</AdminConfigAreaCard>
<AdminConfigAreaCard
class="ai-usage__users"
@heading="discourse_ai.usage.users_breakdown"
>
<:content>
{{#unless this.data.users.length}}
<AdminConfigAreaEmptyList
@emptyLabel="discourse_ai.usage.no_users"
/>
{{/unless}}
{{#if this.data.users.length}}
<table
class={{concatClass
"ai-usage__users-table"
(if (lt this.data.users.length 25) "-double-width")
}}
>
<thead>
<tr>
<th class="ai-usage__users-username">{{i18n
"discourse_ai.usage.username"
}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each (this.takeUsers 0 24) as |user|}}
<tr class="ai-usage__users-row">
<td class="ai-usage__users-cell">
<div class="user-info">
<LinkTo
@route="user"
@model={{user.username}}
class="username"
>
{{avatar user imageSize="tiny"}}
{{user.username}}
</LinkTo>
</div></td>
<td
class="ai-usage__users-cell"
title={{user.usage_count}}
>{{number user.usage_count}}</td>
<td
class="ai-usage__users-cell"
title={{user.total_tokens}}
>{{number user.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{#if (gt this.data.users.length 25)}}
<table class="ai-usage__users-table">
<thead>
<tr>
<th class="ai-usage__users-username">{{i18n
"discourse_ai.usage.username"
}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each (this.takeUsers 25 49) as |user|}}
<tr class="ai-usage__users-row">
<td class="ai-usage__users-cell">
<div class="user-info">
<LinkTo
@route="user"
@model={{user.username}}
class="username"
>
{{avatar user imageSize="tiny"}}
{{user.username}}
</LinkTo>
</div></td>
<td
class="ai-usage__users-cell"
title={{user.usage_count}}
>{{number user.usage_count}}</td>
<td
class="ai-usage__users-cell"
title={{user.total_tokens}}
>{{number user.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
{{/if}}
</:content>
</AdminConfigAreaCard>
</div>
<div class="ai-usage__charts">
<div class="ai-usage__chart-container">
<h3 class="ai-usage__chart-title">
{{i18n "discourse_ai.usage.tokens_over_time"}}
</h3>
<Chart
@chartConfig={{this.chartConfig}}
class="ai-usage__chart"
/>
</div>
<div class="ai-usage__breakdowns">
<div class="ai-usage__users">
<h3 class="ai-usage__users-title">
{{i18n "discourse_ai.usage.users_breakdown"}}
</h3>
<table class="ai-usage__users-table">
<thead>
<tr>
<th>{{i18n "discourse_ai.usage.username"}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each this.data.users as |user|}}
<tr class="ai-usage__users-row">
<td class="ai-usage__users-cell">
<div class="user-info">
<LinkTo
@route="user"
@model={{user.username}}
class="username"
>
{{avatar user imageSize="tiny"}}
{{user.username}}
</LinkTo>
</div></td>
<td
class="ai-usage__users-cell"
>{{user.usage_count}}</td>
<td
class="ai-usage__users-cell"
>{{user.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<div class="ai-usage__features">
<h3 class="ai-usage__features-title">
{{i18n "discourse_ai.usage.features_breakdown"}}
</h3>
<table class="ai-usage__features-table">
<thead>
<tr>
<th>{{i18n "discourse_ai.usage.feature"}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each this.data.features as |feature|}}
<tr class="ai-usage__features-row">
<td
class="ai-usage__features-cell"
>{{feature.feature_name}}</td>
<td
class="ai-usage__features-cell"
>{{feature.usage_count}}</td>
<td
class="ai-usage__features-cell"
>{{feature.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<div class="ai-usage__models">
<h3 class="ai-usage__models-title">
{{i18n "discourse_ai.usage.models_breakdown"}}
</h3>
<table class="ai-usage__models-table">
<thead>
<tr>
<th>{{i18n "discourse_ai.usage.model"}}</th>
<th>{{i18n "discourse_ai.usage.usage_count"}}</th>
<th>{{i18n "discourse_ai.usage.total_tokens"}}</th>
</tr>
</thead>
<tbody>
{{#each this.data.models as |model|}}
<tr class="ai-usage__models-row">
<td class="ai-usage__models-cell">{{model.llm}}</td>
<td
class="ai-usage__models-cell"
>{{model.usage_count}}</td>
<td
class="ai-usage__models-cell"
>{{model.total_tokens}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
{{/if}}
</ConditionalLoadingSpinner>
</div>
</div>
</template>

View File

@ -60,9 +60,6 @@
&__summary {
margin: 2em 0;
padding: 1.5em;
background: var(--primary-very-low);
border-radius: 0.5em;
}
&__summary-title {
@ -81,7 +78,7 @@
display: flex;
flex-direction: column;
padding: 1em;
background: var(--secondary);
background: var(--primary-very-low);
border-radius: 0.25em;
.label {
@ -120,22 +117,38 @@
margin-top: 2em;
@media (max-width: 768px) {
grid-template-columns: 1fr;
grid-template-columns: none;
display: flex;
flex-direction: column;
}
}
&__features,
&__users,
&__models {
background: var(--primary-very-low);
padding: 1em;
border-radius: 0.5em;
}
&__users {
grid-column: span 2;
&__features-title,
&__users-title,
&__models-title {
margin-bottom: 1em;
.admin-config-area-card__content {
display: flex;
.ai-usage__users-table {
&:first-child {
margin-right: 2em;
&.-double-width {
margin-right: 0;
}
}
&.-double-width {
.ai-usage__users-username {
width: auto;
}
}
.ai-usage__users-username {
width: 50px;
}
}
}
}
&__features-table,

View File

@ -146,7 +146,18 @@ en:
total_requests: "Total requests"
request_tokens: "Request tokens"
response_tokens: "Response tokens"
net_request_tokens: "Net request tokens"
cached_tokens: "Cached tokens"
cached_request_tokens: "Cached request tokens"
no_users: "No user usage data found"
no_models: "No model usage data found"
no_features: "No feature usage data found"
subheader_description: "Tokens are the basic units that LLMs use to understand and generate text, usage data may affect costs."
periods:
last_day: "Last 24 hours"
last_week: "Last week"
last_month: "Last month"
custom: "Custom..."
ai_persona:
tool_strategies:

View File

@ -79,6 +79,7 @@ Discourse::Application.routes.draw do
to: "discourse_ai/admin/rag_document_fragments#indexing_status_check"
get "/ai-usage", to: "discourse_ai/admin/ai_usage#show"
get "/ai-usage-report", to: "discourse_ai/admin/ai_usage#report"
resources :ai_llms,
only: %i[index create show update destroy],

View File

@ -14,33 +14,33 @@ module DiscourseAi
end
def total_tokens
stats.total_tokens
stats.total_tokens || 0
end
def total_cached_tokens
stats.total_cached_tokens
stats.total_cached_tokens || 0
end
def total_request_tokens
stats.total_request_tokens
stats.total_request_tokens || 0
end
def total_response_tokens
stats.total_response_tokens
stats.total_response_tokens || 0
end
def total_requests
stats.total_requests
stats.total_requests || 0
end
def stats
@stats ||=
base_query.select(
"COUNT(*) as total_requests",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)[
0
]
@ -66,10 +66,10 @@ module DiscourseAi
.order("DATE_TRUNC('#{period}', created_at)")
.select(
"DATE_TRUNC('#{period}', created_at) as period",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@ -83,10 +83,10 @@ module DiscourseAi
"users.username",
"users.uploaded_avatar_id",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@ -97,10 +97,10 @@ module DiscourseAi
.select(
"case when coalesce(feature_name, '') = '' then '#{UNKNOWN_FEATURE}' else feature_name end as feature_name",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@ -111,10 +111,10 @@ module DiscourseAi
.select(
"language_model as llm",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end

View File

@ -5,7 +5,7 @@ require "rails_helper"
RSpec.describe DiscourseAi::Admin::AiUsageController do
fab!(:admin)
fab!(:user)
let(:usage_path) { "/admin/plugins/discourse-ai/ai-usage.json" }
let(:usage_report_path) { "/admin/plugins/discourse-ai/ai-usage-report.json" }
before { SiteSetting.discourse_ai_enabled = true }
@ -36,7 +36,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns correct data structure" do
get usage_path
get usage_report_path
expect(response.status).to eq(200)
@ -48,14 +48,18 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "respects date filters" do
get usage_path, params: { start_date: 3.days.ago.to_date, end_date: 1.day.ago.to_date }
get usage_report_path,
params: {
start_date: 3.days.ago.to_date,
end_date: 1.day.ago.to_date,
}
json = response.parsed_body
expect(json["summary"]["total_tokens"]).to eq(450) # sum of all tokens
end
it "filters by feature" do
get usage_path, params: { feature: "summarize" }
get usage_report_path, params: { feature: "summarize" }
json = response.parsed_body
@ -66,7 +70,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "filters by model" do
get usage_path, params: { model: "gpt-3.5" }
get usage_report_path, params: { model: "gpt-3.5" }
json = response.parsed_body
models = json["models"]
@ -76,10 +80,10 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "handles different period groupings" do
get usage_path, params: { period: "hour" }
get usage_report_path, params: { period: "hour" }
expect(response.status).to eq(200)
get usage_path, params: { period: "month" }
get usage_report_path, params: { period: "month" }
expect(response.status).to eq(200)
end
end
@ -102,7 +106,11 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns hourly data when period is day" do
get usage_path, params: { start_date: 1.day.ago.to_date, end_date: Time.current.to_date }
get usage_report_path,
params: {
start_date: 1.day.ago.to_date,
end_date: Time.current.to_date,
}
expect(response.status).to eq(200)
json = response.parsed_body
@ -121,7 +129,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
before { sign_in(user) }
it "blocks access" do
get usage_path
get usage_report_path
expect(response.status).to eq(404)
end
end
@ -133,7 +141,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns error" do
get usage_path
get usage_report_path
expect(response.status).to eq(404)
end
end