Skip to content

Analytics tracking enhancements - #259

Open
tombogle wants to merge 33 commits into
mainfrom
analytics-tracking-enhancements
Open

Analytics tracking enhancements#259
tombogle wants to merge 33 commits into
mainfrom
analytics-tracking-enhancements

Conversation

@tombogle

@tombogle tombogle commented Jan 6, 2026

Copy link
Copy Markdown
Collaborator

This change is Reviewable

@tombogle
tombogle requested a review from andrew-polk January 6, 2026 14:35
@tombogle tombogle self-assigned this Jan 6, 2026
Also, added missing DLL to Installer
Could not load file or assembly 'System.Text.Json, Version=9.0.0.0'
…by tests

Added explicit method to call to report project progress.
Included advances in completed stages in project progress analytics

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew-polk reviewed 20 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @tombogle).


src/SayMore/Model/Project.cs line 208 at r1 (raw file):

					if (personDelta > 0)
						properties["PersonsAdded"] = personDelta.ToString();

I guess you don't care about removal of sessions or persons? (assuming that is possible)

Same for media duration below

Deal with race condition to be able to track progress reliably.
Remove unnecessary tracking of intermediate updates.

@tombogle tombogle left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tombogle made 1 comment.
Reviewable status: 15 of 20 files reviewed, 1 unresolved discussion (waiting on @andrew-polk).


src/SayMore/Model/Project.cs line 208 at r1 (raw file):

Previously, andrew-polk wrote…

I guess you don't care about removal of sessions or persons? (assuming that is possible)

Same for media duration below

I'm thinking of "progress" as advancing toward some (nebulous) "completion point." While getting rid of cruft can also be a form of progress, it's not the main focus. There are several other things that I could take into account, such as archiving, but that is already a concretely trackable event. My hope is to have an "event" that more-or-less answers the question, did the user make measurable progress toward creation of a finished work during this SayMore session, as opposed to just going in and poking around.

@andrew-polk

Copy link
Copy Markdown
Contributor

src/SayMore/Model/Project.cs line 208 at r1 (raw file):

Previously, tombogle (Tom Bogle) wrote…

I'm thinking of "progress" as advancing toward some (nebulous) "completion point." While getting rid of cruft can also be a form of progress, it's not the main focus. There are several other things that I could take into account, such as archiving, but that is already a concretely trackable event. My hope is to have an "event" that more-or-less answers the question, did the user make measurable progress toward creation of a finished work during this SayMore session, as opposed to just going in and poking around.

👍

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew-polk reviewed 5 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @tombogle).


src/SayMore/Model/Project.cs line 680 at r2 (raw file):

SetAdditionalMetsData

typo
SetAdditionalMetaData

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment on lines +953 to +975
public void TrackStatistics(StatisticsViewModel statisticsViewModel)
{
if (_statisticsViewModel != null)
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles -= FinishedGatheringStatistics;

_statisticsViewModel = statisticsViewModel;
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles += FinishedGatheringStatistics;
if (_statisticsViewModel.IsDataUpToDate)
{
// It either finished before we could hook the event, or we hit the race condition.
FinishedGatheringStatistics(_statisticsViewModel, null);
}
}

private void FinishedGatheringStatistics(object sender, EventArgs e)
{
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles -= FinishedGatheringStatistics;
// Check for race condition (see above).
if (_progressStats != null)
return;

_progressStats = new ProgressStats(_statisticsViewModel);
}

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new analytics tracking functionality (TrackStatistics, ReportProgressIfAny, and ProgressStats class) lacks test coverage. Since the repository has comprehensive test coverage for both Project and StatisticsViewModel classes, similar test coverage should be added for these new analytics-related methods to verify correct tracking behavior and edge cases.

Copilot uses AI. Check for mistakes.
Comment thread src/SayMore/Model/Project.cs Outdated
Comment on lines +970 to +974
// Check for race condition (see above).
if (_progressStats != null)
return;

_progressStats = new ProgressStats(_statisticsViewModel);

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential race condition: _progressStats is accessed from multiple contexts (FinishedGatheringStatistics callback, ReportProgressIfAny, and Dispose) without synchronization. While the null check at line 971 helps, a more robust approach would be to use locking or Interlocked operations to ensure thread-safe access to this field, especially since the statistics gatherer runs on a background thread.

Suggested change
// Check for race condition (see above).
if (_progressStats != null)
return;
_progressStats = new ProgressStats(_statisticsViewModel);
// Use atomic initialization to avoid race conditions when multiple threads
// access _progressStats concurrently.
var newProgressStats = new ProgressStats(_statisticsViewModel);
if (Interlocked.CompareExchange(ref _progressStats, newProgressStats, null) != null)
{
// Another thread has already initialized _progressStats; discard this instance.
// If ProgressStats implements IDisposable, it should be disposed here.
}

Copilot uses AI. Check for mistakes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • src/SayMore/UI/SplashScreenForm.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/SayMore/Model/Project.cs
Comment on lines +953 to +965
public void TrackStatistics(StatisticsViewModel statisticsViewModel)
{
if (_statisticsViewModel != null)
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles -= FinishedGatheringStatistics;

_statisticsViewModel = statisticsViewModel;
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles += FinishedGatheringStatistics;
if (_statisticsViewModel.IsDataUpToDate)
{
// It either finished before we could hook the event, or we hit the race condition.
FinishedGatheringStatistics(_statisticsViewModel, null);
}
}

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new TrackStatistics method and analytics tracking logic lack test coverage. Consider adding tests to verify: 1) TrackStatistics is called when StatisticsViewModel is created, 2) ReportProgressIfAny correctly tracks deltas, 3) Analytics.Track is called with expected properties.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't generally test Analytics code. Given the asyncronous nature of the statistics collection, it's probably not worth the trouble to introduce potentially flaky tests.

@tombogle tombogle left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tombogle made 3 comments and resolved 1 discussion.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @andrew-polk).


src/SayMore/Model/Project.cs line 680 at r2 (raw file):

Previously, andrew-polk wrote…

SetAdditionalMetsData

typo
SetAdditionalMetaData

It really is METS. It's an acronym for Metadata Encoding and Transmission Standard (https://www.loc.gov/standards/mets/)

Comment thread src/SayMore/Model/Project.cs
Comment on lines +953 to +965
public void TrackStatistics(StatisticsViewModel statisticsViewModel)
{
if (_statisticsViewModel != null)
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles -= FinishedGatheringStatistics;

_statisticsViewModel = statisticsViewModel;
_statisticsViewModel.FinishedGatheringStatisticsForAllFiles += FinishedGatheringStatistics;
if (_statisticsViewModel.IsDataUpToDate)
{
// It either finished before we could hook the event, or we hit the race condition.
FinishedGatheringStatistics(_statisticsViewModel, null);
}
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't generally test Analytics code. Given the asyncronous nature of the statistics collection, it's probably not worth the trouble to introduce potentially flaky tests.

@andrew-polk andrew-polk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew-polk resolved 1 discussion and dismissed @copilot[bot] from a discussion.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @tombogle).

Copilot AI commented Jan 13, 2026

Copy link
Copy Markdown

@andrew-polk I've opened a new pull request, #261, to work on those changes. Once the pull request is ready, I'll request review from you.

@andrew-polk

Copy link
Copy Markdown
Contributor

Sigh. I don't know why copilot did that. Closing it.

Updated copyright date in license file
Added Crowdin to acknowledgments in aboutBox.htm
WIP: Getting everything to compile...
tombogle and others added 20 commits June 5, 2026 11:09
Exposes the SayMore-specific ILocalizationManager as a static so
EditorBase and other classes can subscribe to UiLanguageChanged
without needing constructor injection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… constructors

- Replace `EditorBase(ILocalizationManager)` with `protected EditorBase()` that
  reads from `ApplicationContainer.SayMoreLocalizationManager` directly
- Replace 4-arg `EditorBase(ComponentFile, string, string, ILocalizationManager)`
  with 3-arg version delegating to the no-arg constructor
- Use null-guard `if (_localizationManager != null)` for event subscription/
  unsubscription to support unit tests where ApplicationContainer is not initialized
- Fixes "does not contain a constructor that takes 0 arguments" errors in
  MediaComponentEditor, DiagnosticsFileInfoControl, and related subclasses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ager static

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… static

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop ILocalizationManager constructor params and field storage from
FileType base, AnnotationFileWithMissingMediaFileType,
AudioVideoFileTypeBase, AudioFileType, VideoFileType,
OralAnnotationFileType, and ImageFileType.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ubclasses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…call and lm guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… in 5 EditorBase subclasses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ojectDocsScreen

Drop lm constructor params from ProjectMetadataScreen,
ProjectDocsScreen, ProjectDescriptionDocsScreen, and
ProjectOtherDocsScreen. Remove _localizationManager field and
clean up HandleStringsLocalized and HandleAfterComponentFileSelected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ShowLocalizationDialogBox with Crowdin URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ContributorsEditor)

These were not in the original plan but had the same 4-arg base call
and lm guard patterns as Task 8 files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nstructor to OnLoad

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…from EditorBase

tabText was always null at every call site; Initialize only set ImageKey (after
a no-op TabText assignment). Replace the 3-param EditorBase constructor with a
2-param one that sets ImageKey directly, remove Initialize from both the
IEditorProvider interface and the class body, and update all 18 subclasses
(including MediaComponentEditor and its two subclasses) to drop the null arg.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	build/TestInstallerBuild.bat
# Conflicts:
#	src/SayMore/Model/Project.cs
#	src/SayMore/UI/ProjectChoosingAndCreating/WelcomeDialog.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants