Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
64e54d8
feat: Initial commit for Ivy Ask Statistics project, including core f…
ArtemLazarchuk Mar 28, 2026
c769c3e
refactor: Simplify environment handling in RunApp by removing dynamic…
ArtemLazarchuk Mar 28, 2026
07fd3d4
feat: Implement database connection and question generation features,…
ArtemLazarchuk Mar 29, 2026
c3bf7f5
refactor: Simplify QuestionsApp and RunApp by removing unused CellBui…
ArtemLazarchuk Mar 29, 2026
4278106
feat: Enhance QuestionsApp with widget data management and question g…
ArtemLazarchuk Mar 29, 2026
4cc3f86
refactor: Streamline QuestionsApp by introducing a dedicated method f…
ArtemLazarchuk Mar 29, 2026
7b24b1a
feat: Add WidgetQuestionsDialog for displaying questions related to a…
ArtemLazarchuk Mar 29, 2026
74fb90c
feat: Introduce QuestionEditSheet for editing questions and enhance W…
ArtemLazarchuk Mar 30, 2026
8c35576
refactor: Remove unused parameters from QuestionEditSheet and WidgetQ…
ArtemLazarchuk Mar 30, 2026
e5255a8
feat: Integrate OpenAI API for question generation in QuestionsApp, e…
ArtemLazarchuk Mar 31, 2026
be7c3eb
refactor: Update QuestionsApp to improve state management for widget …
ArtemLazarchuk Mar 31, 2026
bda069c
refactor: Rename RunApp title for clarity, enhance state management w…
ArtemLazarchuk Mar 31, 2026
3099a47
feat: Add DashboardApp for displaying comprehensive statistics with K…
ArtemLazarchuk Mar 31, 2026
3f9602a
refactor: Enhance RunApp by integrating refresh token management duri…
ArtemLazarchuk Mar 31, 2026
3b37f8b
feat: Implement "Generate All Questions" functionality in QuestionsAp…
ArtemLazarchuk Apr 1, 2026
d280b90
feat: Enhance DashboardApp with version selection and improved statis…
ArtemLazarchuk Apr 1, 2026
d07d56d
refactor: Simplify "Generate All Questions" functionality in Question…
ArtemLazarchuk Apr 1, 2026
7383d45
refactor: Improve state management in RunApp by adding runFinished st…
ArtemLazarchuk Apr 1, 2026
d0e653b
refactor: Enhance RunApp by adding concurrency options, improving sta…
ArtemLazarchuk Apr 1, 2026
16403cf
refactor: Revamp DashboardApp and RunApp to enhance data loading, sta…
ArtemLazarchuk Apr 2, 2026
281db29
refactor: Introduce TabLoadingSkeletons for improved loading indicato…
ArtemLazarchuk Apr 2, 2026
f71cd1a
refactor: Update Ivy and Ivy.Analyser package versions to 1.2.34 and …
ArtemLazarchuk Apr 4, 2026
c1bff16
fix: Correct typo in PackageReference for Microsoft.EntityFrameworkCo…
ArtemLazarchuk Apr 5, 2026
cd0d3cc
refactor: Improve DashboardApp's error handling and state management …
ArtemLazarchuk Apr 5, 2026
0f3b3a2
refactor: Add MCP environment selection and dynamic base URL handling…
ArtemLazarchuk Apr 5, 2026
c5afd36
refactor: Enhance IvyAskService and RunApp to support dynamic MCP cli…
ArtemLazarchuk Apr 5, 2026
daa8709
refactor: Update QuestionGeneratorService to include ChatModel config…
ArtemLazarchuk Apr 6, 2026
3602638
refactor: Enhance QuestionsApp and related services to support concur…
ArtemLazarchuk Apr 6, 2026
365d69f
refactor: Integrate TestQuestionsQueryTag into query service revalida…
ArtemLazarchuk Apr 6, 2026
65f6d0e
refactor: Update query keys in DashboardApp, QuestionsApp, and RunApp…
ArtemLazarchuk Apr 6, 2026
842385d
refactor: Enhance RunApp to support fixed parallel Ask requests and i…
ArtemLazarchuk Apr 6, 2026
a8d4bd1
refactor: Update DashboardApp to enhance KPI display with peer compar…
ArtemLazarchuk Apr 6, 2026
629aba4
refactor: Enhance RunApp to synchronize Ivy version with MCP environm…
ArtemLazarchuk Apr 6, 2026
ed96e1d
refactor: Revamp DashboardApp to introduce a detailed Test Runs table…
ArtemLazarchuk Apr 6, 2026
9a5329c
refactor: Revise DashboardApp to implement a dictionary for caching d…
ArtemLazarchuk Apr 6, 2026
96081e8
refactor: Update DashboardApp, QuestionsApp, and related components t…
ArtemLazarchuk Apr 6, 2026
33edcb9
refactor: Simplify RunApp and TabLoadingSkeletons by replacing hardco…
ArtemLazarchuk Apr 6, 2026
050a054
feat: Add BasicAuthConnection class to implement basic authentication…
ArtemLazarchuk Apr 6, 2026
0359215
feat: Add Dockerfile and .dockerignore for IvyAskStatistics project, …
ArtemLazarchuk Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions project-demos/ivy-ask-statistics/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
817 changes: 817 additions & 0 deletions project-demos/ivy-ask-statistics/Apps/DashboardApp.cs

Large diffs are not rendered by default.

204 changes: 204 additions & 0 deletions project-demos/ivy-ask-statistics/Apps/QuestionEditSheet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
namespace IvyAskStatistics.Apps;

internal sealed class QuestionEditSheet(
IState<bool> isOpen,
Guid questionId,
IState<Guid?> previewResultId) : ViewBase
{
private record EditRequest
{
[Required]
public string QuestionText { get; init; } = "";

[Required]
public string Difficulty { get; init; } = "";

public string Category { get; init; } = "";

public bool IsActive { get; init; } = true;
}

/// <summary>
/// Question row plus optional response preview: either the specific <see cref="TestResultEntity"/> row
/// (when opened from a run results table) or the latest answer across all runs (elsewhere).
/// </summary>
private sealed record QuestionEditPayload(
QuestionEntity Question,
string? AnswerText,
AnswerPreviewSource PreviewSource);

private enum AnswerPreviewSource
{
/// <summary>This run's result row (may be empty).</summary>
ThisResultRow,

/// <summary>Latest successful, else latest with text — any run.</summary>
GlobalHistory,
}

public override object? Build()
{
var factory = UseService<AppDbContextFactory>();
var queryService = UseService<IQueryService>();
var isSaving = UseState(false);

var questionQuery = UseQuery<QuestionEditPayload?, (Guid QuestionId, Guid? PreviewResultId)>(
key: (questionId, previewResultId.Value),
fetcher: async (key, ct) =>
{
var (id, focusResult) = key;
await using var ctx = factory.CreateDbContext();
var q = await ctx.Questions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct);
if (q == null)
return null;

if (focusResult is Guid fr && fr != Guid.Empty)
{
var text = await ctx.TestResults.AsNoTracking()
.Where(r => r.Id == fr && r.QuestionId == id)
.Select(r => r.ResponseText)
.FirstOrDefaultAsync(ct);
var trimmed = string.IsNullOrWhiteSpace(text) ? null : text.Trim();
return new QuestionEditPayload(q, trimmed, AnswerPreviewSource.ThisResultRow);
}

var lastSuccess = await ctx.TestResults.AsNoTracking()
.Where(r => r.QuestionId == id && r.IsSuccess)
.OrderByDescending(r => r.CreatedAt)
.Select(r => r.ResponseText)
.FirstOrDefaultAsync(ct);

string? answer = null;
if (!string.IsNullOrWhiteSpace(lastSuccess))
answer = lastSuccess.Trim();
else
{
var lastAny = await ctx.TestResults.AsNoTracking()
.Where(r => r.QuestionId == id)
.OrderByDescending(r => r.CreatedAt)
.Select(r => r.ResponseText)
.FirstOrDefaultAsync(ct);
if (!string.IsNullOrWhiteSpace(lastAny))
answer = lastAny.Trim();
}

return new QuestionEditPayload(q, answer, AnswerPreviewSource.GlobalHistory);
});

if (questionQuery.Loading || questionQuery.Value == null)
return new Sheet(
_ =>
{
isOpen.Set(false);
previewResultId.Set(null);
},
Skeleton.Form(),
title: "Edit Question",
description: "Loading question…")
.Width(Size.Fraction(1f / 3f))
.Height(Size.Full());

var payload = questionQuery.Value;
var q = payload.Question;
var answer = payload.AnswerText;
var preview = payload.PreviewSource;

var form = new EditRequest
{
QuestionText = q.QuestionText ?? "",
Difficulty = q.Difficulty,
Category = q.Category,
IsActive = q.IsActive,
};

var difficulties = new[] { "easy", "medium", "hard" }.ToOptions();

var formBuilder = form
.ToForm()
.Builder(f => f.QuestionText, f => f.ToTextareaInput())
.Builder(f => f.Difficulty, f => f.ToSelectInput(difficulties))
.Builder(f => f.Category, f => f.ToTextInput())
.Builder(f => f.IsActive, f => f.ToSwitchInput())
.OnSubmit(OnSubmit);

var (onSubmit, formView, validationView, loading) = formBuilder.UseForm(Context);

object answerPreview = preview switch
{
AnswerPreviewSource.ThisResultRow when string.IsNullOrWhiteSpace(answer)
=> new Callout(
"No response text for this row in this test run (e.g. 404 / no answer).",
variant: CalloutVariant.Info),
AnswerPreviewSource.ThisResultRow
=> new Card(
Layout.Vertical().Gap(2)
| Text.Block("Response for the result row you opened (read-only).").Muted()
| Text.Markdown(answer!))
.Title("This run")
.Icon(Icons.FileText),
AnswerPreviewSource.GlobalHistory when string.IsNullOrWhiteSpace(answer)
=> new Empty(),
AnswerPreviewSource.GlobalHistory
=> new Card(
Layout.Vertical().Gap(2)
| Text.Block("Latest recorded response across all test runs (read-only).").Muted()
| Text.Markdown(answer!))
.Title("Answer")
.Icon(Icons.FileText),
_ => new Empty()
};

var scrollBody = Layout.Vertical().Gap(4)
| formView
| answerPreview;

var footer = Layout.Horizontal().Gap(2)
| new Button("Save")
.Variant(ButtonVariant.Primary)
.Loading(loading || isSaving.Value)
.Disabled(loading || isSaving.Value)
.OnClick(async _ =>
{
isSaving.Set(true);
try
{
if (await onSubmit())
isOpen.Set(false);
}
finally
{
isSaving.Set(false);
}
})
| validationView;

var sheetBody = new FooterLayout(footer, scrollBody);

return new Sheet(
_ =>
{
isOpen.Set(false);
previewResultId.Set(null);
},
sheetBody,
title: "Edit Question")
.Width(Size.Fraction(1f / 3f))
.Height(Size.Full());

async Task OnSubmit(EditRequest? request)
{
if (request == null) return;
await using var ctx = factory.CreateDbContext();
var entity = await ctx.Questions.FirstOrDefaultAsync(e => e.Id == questionId);
if (entity == null) return;
entity.QuestionText = request.QuestionText.Trim();
entity.Difficulty = request.Difficulty;
entity.Category = request.Category.Trim();
entity.IsActive = request.IsActive;
await ctx.SaveChangesAsync();
queryService.RevalidateByTag(("widget-questions", entity.Widget));
queryService.RevalidateByTag("widget-summary");
queryService.RevalidateByTag(RunApp.TestQuestionsQueryTag);
}
}
}
Loading
Loading