Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce study status and add closing state #634

Merged
merged 24 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5c0f04f
chore: translation
johannesvedder May 25, 2024
3cf8804
feat: deprecate published for study_status
johannesvedder May 25, 2024
29310b7
chore: create migration temporarily
johannesvedder May 26, 2024
9aba30c
fix: improve status transition rules
johannesvedder May 26, 2024
15a72e5
feat: study close button
johannesvedder Jun 7, 2024
881f856
chore: format
johannesvedder Jun 7, 2024
acdbb1c
chore: copy migration to schema
johannesvedder Jun 10, 2024
aed1eff
fix: merge dashboard refactor pr
johannesvedder Jun 11, 2024
051552d
chore: fix seed
johannesvedder Jun 11, 2024
dbd1810
fix: study visibility policy no longer uses published field
johannesvedder Jun 11, 2024
1ce2f78
fix: potential isClosed error
johannesvedder Jun 11, 2024
7dcc928
fix: migrate app to status
johannesvedder Jun 11, 2024
1cb7740
fix: do not show separator if no delete item shown
johannesvedder Jun 11, 2024
b6dad92
fix: only show close button for editors
johannesvedder Jun 11, 2024
547c468
fix: add closed success description
johannesvedder Jun 11, 2024
4beb796
style: format
johannesvedder Jun 11, 2024
9ffc7ce
tests: migrate db test to status
johannesvedder Jun 11, 2024
7c5b8ae
tests: fix workflow trigger
johannesvedder Jun 11, 2024
f190d1f
Merge branch 'dev' into feat/study-closing-studystatus
johannesvedder Jun 12, 2024
e27a580
chore: format
johannesvedder Jun 12, 2024
dad913e
chore: format
johannesvedder Jun 12, 2024
cdcbca5
style: rephrase translation
johannesvedder Jun 13, 2024
9b66d33
feat: new severe close confirmation
johannesvedder Jun 13, 2024
931c4e2
Merge branch 'feat/study-closing' into feat/study-closing-studystatus
johannesvedder Jun 14, 2024
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
Prev Previous commit
Next Next commit
feat: study close button
  • Loading branch information
johannesvedder committed Jun 11, 2024
commit 15a72e5e8ffb9c8a7251fcd4eeefe8b5713d5d49
2 changes: 1 addition & 1 deletion database/migration/20240526_migrate_close_study.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ CREATE POLICY "Editor can control their draft studies" ON public.study
-- FOR UPDATE
-- USING (public.can_edit(auth.uid(), study.*))
-- WITH CHECK ((new.*) IS NOT DISTINCT FROM (old.* EXCEPT registry_published, result_sharing));
-- todo solve with trigger or function
-- t odo solve with trigger or function
-- or create view with only updatable columns and provide permission on view see https://dba.stackexchange.com/questions/298931/allow-users-to-modify-only-some-but-not-all-fields-in-a-postgresql-table-with

-- https://stackoverflow.com/questions/72756376/supabase-solutions-for-column-level-security
Expand Down
3 changes: 0 additions & 3 deletions designer_v2/lib/domain/study.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ enum StudyActionType {
duplicateDraft,
addCollaborator,
export,
close,
delete,
}

Expand All @@ -28,8 +27,6 @@ extension StudyActionTypeFormatted on StudyActionType {
return tr.action_unpin;
case StudyActionType.edit:
return tr.action_edit;
case StudyActionType.close:
return tr.action_close;
case StudyActionType.delete:
return tr.action_delete;
case StudyActionType.duplicate:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:studyu_designer_v2/common_views/dialog.dart';
import 'package:studyu_designer_v2/common_views/form_buttons.dart';
import 'package:studyu_designer_v2/common_views/primary_button.dart';
import 'package:studyu_designer_v2/features/study/settings/study_settings_form_controller.dart';
import 'package:studyu_designer_v2/features/study/study_controller.dart';
import 'package:studyu_designer_v2/features/study/study_page_view.dart';
import 'package:studyu_designer_v2/localization/app_translation.dart';

class CloseConfirmationDialog extends StudyPageWidget {
const CloseConfirmationDialog(super.studyId, {super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.watch(studyControllerProvider(studyId).notifier);
final formViewModel =
ref.watch(studySettingsFormViewModelProvider(studyId));

return ReactiveForm(
formGroup: formViewModel.form,
child: StandardDialog(
titleText: tr.dialog_study_close_title,
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
text: tr.dialog_study_close_description,
),
),
],
)),
],
),
],
),
actionButtons: [
const DismissButton(),
ReactiveFormConsumer(builder: (context, form, child) {
return PrimaryButton(
text: tr.dialog_close,
icon: null,
onPressedFuture: () => controller.closeStudy(),
);
}),
],
maxWidth: 650,
minWidth: 610,
minHeight: 200,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:studyu_designer_v2/common_views/dialog.dart';
import 'package:studyu_designer_v2/common_views/empty_body.dart';
import 'package:studyu_designer_v2/common_views/primary_button.dart';
import 'package:studyu_designer_v2/features/study/study_page_view.dart';
import 'package:studyu_designer_v2/localization/app_translation.dart';
import 'package:studyu_designer_v2/localization/string_hardcoded.dart';

class CloseSuccessDialog extends StudyPageWidget {
const CloseSuccessDialog(super.studyId, {super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);

return StandardDialog(
body: Column(
children: [
const SizedBox(height: 24.0),
EmptyBody(
leading: Text("\u{1f512}".hardcoded,
style: theme.textTheme.displayLarge?.copyWith(
fontSize: (theme.textTheme.displayLarge?.fontSize ?? 48.0) * 1.5,
)),
title: tr.notification_study_closed,
description: '',
),
const SizedBox(height: 8.0),
],
),
actionButtons: [
PrimaryButton(
text: tr.action_button_study_close,
icon: null,
onPressedFuture: () => Navigator.maybePop(context),
),
],
maxWidth: 450,
);
}
}
39 changes: 39 additions & 0 deletions designer_v2/lib/features/dialogs/study_dialogs.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:studyu_designer_v2/domain/study.dart';
import 'package:studyu_designer_v2/features/dialogs/close/study_close_dialog_confirm.dart';
import 'package:studyu_designer_v2/features/dialogs/close/study_close_dialog_success.dart';
import 'package:studyu_designer_v2/features/dialogs/publish/study_publish_dialog_confirm.dart';
import 'package:studyu_designer_v2/features/dialogs/publish/study_publish_dialog_success.dart';
import 'package:studyu_designer_v2/features/study/study_controller.dart';
import 'package:studyu_designer_v2/features/study/study_page_view.dart';
import 'package:studyu_designer_v2/theme.dart';

enum StudyDialogType { publish, close }

class StudyDialog extends StudyPageWidget {
final StudyDialogType dialogType;

const StudyDialog(this.dialogType, super.studyId, {super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(studyControllerProvider(studyId));

switch(dialogType) {
case StudyDialogType.publish:
return state.isPublished ? PublishSuccessDialog(studyId) : PublishConfirmationDialog(studyId);
case StudyDialogType.close:
return state.isClosed ? CloseSuccessDialog(studyId) : CloseConfirmationDialog(studyId);
}
}
}

showStudyDialog(BuildContext context, StudyID studyId, StudyDialogType dialogType) {
final theme = Theme.of(context);
return showDialog(
context: context,
barrierColor: ThemeConfig.modalBarrierColor(theme),
builder: (context) => StudyDialog(dialogType, studyId),
);
}
31 changes: 0 additions & 31 deletions designer_v2/lib/features/publish/study_publish_dialog.dart

This file was deleted.

4 changes: 2 additions & 2 deletions designer_v2/lib/features/recruit/study_recruit_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ class StudyRecruitScreen extends StudyPageWidget {
@override
Widget? banner(BuildContext context, WidgetRef ref) {
final state = ref.watch(studyRecruitControllerProvider(studyId));
final isStudyClosed = state.studyWithMetadata!.model.isClosed;
final isStudyClosed = state.studyWithMetadata?.model.isClosed;

if (isStudyClosed) {
if (isStudyClosed ?? false) {
return BannerBox(
noPrefix: true,
body: Column(
Expand Down
1 change: 0 additions & 1 deletion designer_v2/lib/features/study/study_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ Map<StudyActionType, IconData> studyActionIcons = {
StudyActionType.addCollaborator: Icons.person_add_rounded,
StudyActionType.export: Icons.download_rounded,
StudyActionType.delete: Icons.delete_rounded,
StudyActionType.close: MdiIcons.accountLock,
};
6 changes: 5 additions & 1 deletion designer_v2/lib/features/study/study_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class StudyController extends StudyBaseController<StudyControllerState> {
return studyRepository.launch(study);
}

Future closeStudy() {
final study = state.study.value!;
return studyRepository.close(study);
}

void onChangeStudyParticipation() {
router.dispatch(RoutingIntents.studyEditEnrollment(studyId));
}
Expand Down Expand Up @@ -111,7 +116,6 @@ final studyControllerProvider =
currentUser: ref.watch(authRepositoryProvider).currentUser,
router: ref.watch(routerProvider),
notificationService: ref.watch(notificationServiceProvider),
//ref: ref,
);
controller.addListener((state) {
print("studyController.state updated");
Expand Down
2 changes: 2 additions & 0 deletions designer_v2/lib/features/study/study_controller_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class StudyControllerState extends StudyControllerBaseState implements IStudyApp

bool get isPublished => study.value != null && study.value!.status == StudyStatus.running;

bool get isClosed => study.value != null && study.value!.status == StudyStatus.closed;

// - ISyncIndicatorViewModel

@override
Expand Down
23 changes: 21 additions & 2 deletions designer_v2/lib/features/study/study_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import 'package:studyu_designer_v2/common_views/async_value_widget.dart';
import 'package:studyu_designer_v2/common_views/layout_single_column.dart';
import 'package:studyu_designer_v2/common_views/navbar_tabbed.dart';
import 'package:studyu_designer_v2/common_views/primary_button.dart';
import 'package:studyu_designer_v2/common_views/secondary_button.dart';
import 'package:studyu_designer_v2/common_views/sync_indicator.dart';
import 'package:studyu_designer_v2/common_views/utils.dart';
import 'package:studyu_designer_v2/constants.dart';
import 'package:studyu_designer_v2/features/app_drawer.dart';
import 'package:studyu_designer_v2/features/design/study_form_providers.dart';
import 'package:studyu_designer_v2/features/forms/form_validation.dart';
import 'package:studyu_designer_v2/features/publish/study_publish_dialog.dart';
import 'package:studyu_designer_v2/features/study/study_controller.dart';
import 'package:studyu_designer_v2/features/study/study_controller_state.dart';
import 'package:studyu_designer_v2/features/study/study_navbar.dart';
import 'package:studyu_designer_v2/features/study/study_page_view.dart';
import 'package:studyu_designer_v2/features/study/study_status_badge.dart';
import 'package:studyu_designer_v2/localization/app_translation.dart';
import 'package:studyu_designer_v2/theme.dart';
import 'package:studyu_designer_v2/features/dialogs/study_dialogs.dart';

abstract class IStudyAppBarViewModel implements IStudyStatusBadgeViewModel, IStudyNavViewModel {
bool get isSyncIndicatorVisible;
Expand Down Expand Up @@ -228,14 +229,32 @@ class _StudyScaffoldState extends ConsumerState<StudyScaffold> {
tooltipDisabled: "${tr.form_invalid_prompt}\n\n${form.validationErrorSummary}",
icon: null,
enabled: formViewModel.isValid,
onPressed: () => showPublishDialog(context, widget.studyId),
onPressed: () => showStudyDialog(context, widget.studyId, StudyDialogType.publish),
);
}),
);
actionButtons.add(publishButton);
actionButtons.add(const SizedBox(width: 12.0)); // padding
}

if (state.isPublished) {
final formViewModel = ref.watch(studyPublishValidatorProvider(widget.studyId));
final closeButton = ReactiveForm(
formGroup: formViewModel.form,
child: ReactiveFormConsumer(
// enable re-rendering based on form validation status
builder: (context, form, child) {
return SecondaryButton(
text: tr.action_button_study_close,
icon: null,
onPressed: () => showStudyDialog(context, widget.studyId, StudyDialogType.close),
);
}),
);
actionButtons.add(closeButton);
actionButtons.add(const SizedBox(width: 12.0)); // padding
}

if (state.isSettingsEnabled) {
actionButtons.add(IconButton(
onPressed: controller.onSettingsPressed,
Expand Down
2 changes: 1 addition & 1 deletion designer_v2/lib/localization/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"study_settings_publish_results": "Ergebnisse veröffentlichen",
"study_settings_publish_results_tooltip": "Andere Forscher und Kliniker können auf die anonymisierten Ergebnisdaten einer Studie zugreifen, \nsie exportieren und analysieren (die Analysieren-Unterseite deiner Studie ist zugänglich). Die Studie \nselbst wird dadurch automatisch auch für andere Forscher und Kliniker im Studienregister veröffentlicht.",
"action_button_study_launch": "Studie starten",
"action_button_study_close": "Studie schließen",
"notification_study_deleted": "Die Studie wurde gelöscht",
"notification_study_closed": "Die Studie wurde geschlossen",
"dialog_study_close_title": "Teilnahme schließen?",
Expand Down Expand Up @@ -550,7 +551,6 @@
"action_pin": "Anheften",
"action_unpin": "Nicht mehr anheften",
"action_edit": "Bearbeiten",
"action_close": "Schließen",
"action_delete": "Löschen",
"action_remove": "Entfernen",
"action_duplicate": "Duplizieren",
Expand Down
2 changes: 1 addition & 1 deletion designer_v2/lib/localization/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"study_settings_publish_results": "Publish results",
"study_settings_publish_results_tooltip": "Make your anonymized study results & data available in the study registry. \nOther researchers & clinicians will be able to access, export and \nanalyze the results from your study (the Analyze page will be available). \n This will automatically publish your study design to the registry.",
"action_button_study_launch": "Launch",
"action_button_study_close": "Close study",
"notification_study_deleted": "Study was deleted",
"notification_study_closed": "Study was closed",
"dialog_study_close_title": "Close participation?",
Expand Down Expand Up @@ -599,7 +600,6 @@
"action_pin": "Pin",
"action_unpin": "Remove pin",
"action_delete": "Delete",
"action_close": "Close",
"action_remove": "Remove",
"action_duplicate": "Duplicate",
"action_clipboard": "Copy to clipboard",
Expand Down
Loading