From c45e2c46d7acc4120eb0dcc4812e0620ce04ae2e Mon Sep 17 00:00:00 2001 From: Brendan O'Connell Date: Wed, 11 Dec 2024 13:04:47 +0100 Subject: [PATCH 1/3] Updated changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79af24cc7..4691eab2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - +### Changed + - [634](https://github.com/thoth-pub/thoth/issues/634) - Prevent changing a `Work`'s `WorkStatus` to `Forthcoming`, `Cancelled` or `Postponed` once its `WorkStatus` has been set to `Active`. + - [659](https://github.com/thoth-pub/thoth/issues/659) - Prevent deletion of a `Work` by non-superusers after `WorkStatus` has been set to `Active`. ## [[0.13.4]](https://github.com/thoth-pub/thoth/releases/tag/v0.13.4) - 2024-12-11 ### Added From fde822b710a4fbc306b38d8e90a969560ff55012 Mon Sep 17 00:00:00 2001 From: Brendan O'Connell Date: Mon, 16 Dec 2024 11:14:51 +0100 Subject: [PATCH 2/3] Add a warning dialogue before a non-superuser can change an unpublished Work to published --- thoth-api/src/graphql/model.rs | 17 +++ thoth-app/src/component/mod.rs | 1 + thoth-app/src/component/work.rs | 37 ++++++- .../src/component/work_status_dialogue.rs | 103 ++++++++++++++++++ thoth-errors/src/lib.rs | 2 + 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 thoth-app/src/component/work_status_dialogue.rs diff --git a/thoth-api/src/graphql/model.rs b/thoth-api/src/graphql/model.rs index 339ff4e1c..3dd6a53ea 100644 --- a/thoth-api/src/graphql/model.rs +++ b/thoth-api/src/graphql/model.rs @@ -1744,6 +1744,23 @@ impl MutationRoot { } data.validate()?; + + let is_data_unpublished = + data.work_status == WorkStatus::Forthcoming || + data.work_status == WorkStatus::Cancelled|| + data.work_status == WorkStatus::PostponedIndefinitely; + + let is_work_published = + work.work_status == WorkStatus::Active || + work.work_status == WorkStatus::Withdrawn || + work.work_status == WorkStatus::Superseded; + + // return an error if a non-superuser attempts to change the + // Work Status of a published Work to unpublished + if is_work_published && is_data_unpublished && !context.account_access.is_superuser { + return Err(ThothError::ThothSetWorkStatusError.into()); + } + let account_id = context.token.jwt.as_ref().unwrap().account_id(&context.db); // update the work and, if it succeeds, synchronise its children statuses and pub. date match work.update(&context.db, &data, &account_id) { diff --git a/thoth-app/src/component/mod.rs b/thoth-app/src/component/mod.rs index a3c523d77..034ab5bf9 100644 --- a/thoth-app/src/component/mod.rs +++ b/thoth-app/src/component/mod.rs @@ -495,4 +495,5 @@ pub mod serieses; pub mod subjects_form; pub mod utils; pub mod work; +pub mod work_status_dialogue; pub mod works; diff --git a/thoth-app/src/component/work.rs b/thoth-app/src/component/work.rs index 185185bf0..d8d26cd84 100644 --- a/thoth-app/src/component/work.rs +++ b/thoth-app/src/component/work.rs @@ -50,6 +50,7 @@ use crate::component::utils::FormUrlInput; use crate::component::utils::FormWorkStatusSelect; use crate::component::utils::FormWorkTypeSelect; use crate::component::utils::Loader; +use crate::component::work_status_dialogue::ConfirmWorkStatusComponent; use crate::models::work::delete_work_mutation::DeleteWorkRequest; use crate::models::work::delete_work_mutation::DeleteWorkRequestBody; use crate::models::work::delete_work_mutation::PushActionDeleteWork; @@ -83,6 +84,8 @@ pub struct WorkComponent { imprint_id: Uuid, // Track work_type stored in database, as distinct from work_type selected in dropdown work_type: WorkType, + // Track work_status stored in database, as distinct from work_status selected in dropdown + current_work_status: WorkStatus, data: WorkFormData, fetch_work: FetchWork, push_work: PushUpdateWork, @@ -169,6 +172,7 @@ impl Component for WorkComponent { let doi_warning = Default::default(); let imprint_id = work.imprint.imprint_id; let work_type = work.work_type; + let current_work_status = work.work_status; let data: WorkFormData = Default::default(); let resource_access = ctx.props().current_user.resource_access.clone(); let work_id = ctx.props().work_id; @@ -181,6 +185,7 @@ impl Component for WorkComponent { doi_warning, imprint_id, work_type, + current_work_status, data, fetch_work, push_work, @@ -207,6 +212,7 @@ impl Component for WorkComponent { self.doi = self.work.doi.clone().unwrap_or_default().to_string(); self.imprint_id = self.work.imprint.imprint_id; self.work_type = self.work.work_type; + self.current_work_status = self.work.work_status; body.data.imprints.clone_into(&mut self.data.imprints); body.data .work_types @@ -575,14 +581,20 @@ impl Component for WorkComponent { true => vec![WorkType::BookChapter], false => vec![], }; - // Grey out chapter-specific or "book"-specific fields + + // Variables required to grey out chapter-specific or "book"-specific fields // based on currently selected work type. let is_chapter = self.work.work_type == WorkType::BookChapter; let is_not_withdrawn_or_superseded = self.work.work_status != WorkStatus::Withdrawn && self.work.work_status != WorkStatus::Superseded; - let is_active_withdrawn_or_superseded = self.work.work_status == WorkStatus::Active + let is_published = self.work.work_status == WorkStatus::Active || self.work.work_status == WorkStatus::Withdrawn || self.work.work_status == WorkStatus::Superseded; + + let current_state_unpublished = self.current_work_status == WorkStatus::Forthcoming || + self.current_work_status == WorkStatus::PostponedIndefinitely || + self.current_work_status == WorkStatus::Cancelled; + html! { <> -
+
- } else { - - } + current_user={ ctx.props().current_user.clone() } + current_state_unpublished={ current_state_unpublished } + is_published={ is_published } + + + /> + // } else { + // + // }
diff --git a/thoth-app/src/component/work_status_dialogue.rs b/thoth-app/src/component/work_status_dialogue.rs index 484a7efcf..ea0a480a8 100644 --- a/thoth-app/src/component/work_status_dialogue.rs +++ b/thoth-app/src/component/work_status_dialogue.rs @@ -1,5 +1,6 @@ use crate::string::CANCEL_BUTTON; use crate::string::SAVE_BUTTON; +use thoth_api::account::model::AccountDetails; use yew::html; use yew::prelude::*; @@ -11,12 +12,17 @@ pub struct ConfirmWorkStatusComponent { pub struct Props { pub onclick: Option>, pub object_name: String, + pub current_user: AccountDetails, + pub current_state_unpublished: bool, + pub is_published: bool, + // pub form_callback: Callback<()>, #[prop_or_default] pub deactivated: bool, } pub enum Msg { ToggleConfirmWorkStatusDisplay(bool), + ExecuteCallback } impl Component for ConfirmWorkStatusComponent { @@ -33,6 +39,16 @@ impl Component for ConfirmWorkStatusComponent { self.show = value; true } + Msg::ExecuteCallback => { + self.show = false; + // trigger the callback + // _ctx.props().form_callback(|_| form_callback).emit(()); + // form_callback.emit(()); + // when set as true, the modal closes and it saves correctly + // true + // when set as false, the modal also closes and saves correctly. + false + } } } @@ -45,11 +61,20 @@ impl Component for ConfirmWorkStatusComponent { e.prevent_default(); Msg::ToggleConfirmWorkStatusDisplay(false) }); + let modal_behavior = if !ctx.props().current_user.resource_access.is_superuser + && ctx.props().current_state_unpublished + && ctx.props().is_published { + &open_modal + } else { + &close_modal + }; + html! { <>