Add time tracking settings
This commit is contained in:
parent
c84a6ade8e
commit
6afa4dc6d1
@ -139,22 +139,22 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpend)),
|
||||
value,
|
||||
) => {
|
||||
modal.payload.time_spent = value.parse::<i32>().ok();
|
||||
modal.payload.time_spent = value.parse::<f64>().ok();
|
||||
send_ws_msg(WsMsg::IssueUpdateRequest(
|
||||
modal.id,
|
||||
IssueFieldId::TimeSpend,
|
||||
PayloadVariant::OptionI32(modal.payload.time_spent),
|
||||
PayloadVariant::OptionF64(modal.payload.time_spent),
|
||||
));
|
||||
}
|
||||
Msg::InputChanged(
|
||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)),
|
||||
value,
|
||||
) => {
|
||||
modal.payload.time_remaining = value.parse::<i32>().ok();
|
||||
modal.payload.time_remaining = value.parse::<f64>().ok();
|
||||
send_ws_msg(WsMsg::IssueUpdateRequest(
|
||||
modal.id,
|
||||
IssueFieldId::TimeRemaining,
|
||||
PayloadVariant::OptionI32(modal.payload.time_remaining),
|
||||
PayloadVariant::OptionF64(modal.payload.time_remaining),
|
||||
));
|
||||
}
|
||||
Msg::ModalChanged(FieldChange::TabChanged(
|
||||
@ -183,13 +183,13 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
Msg::InputChanged(
|
||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)),
|
||||
value,
|
||||
) => match value.parse::<i32>() {
|
||||
) => match value.parse::<f64>() {
|
||||
Ok(n) if !value.is_empty() => {
|
||||
modal.payload.estimate = Some(n);
|
||||
send_ws_msg(WsMsg::IssueUpdateRequest(
|
||||
modal.id,
|
||||
IssueFieldId::TimeRemaining,
|
||||
PayloadVariant::OptionI32(modal.payload.estimate),
|
||||
PayloadVariant::OptionF64(modal.payload.estimate),
|
||||
));
|
||||
}
|
||||
_ if value.is_empty() => {
|
||||
@ -197,7 +197,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
send_ws_msg(WsMsg::IssueUpdateRequest(
|
||||
modal.id,
|
||||
IssueFieldId::TimeRemaining,
|
||||
PayloadVariant::OptionI32(modal.payload.estimate),
|
||||
PayloadVariant::OptionF64(modal.payload.estimate),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
|
@ -9,7 +9,7 @@ use crate::shared::styled_editor::Mode;
|
||||
use crate::shared::styled_select::StyledSelectState;
|
||||
use crate::{EditIssueModalSection, FieldId, ProjectFieldId, HOST_URL};
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub enum ModalType {
|
||||
AddIssue(Box<AddIssueModal>),
|
||||
EditIssue(IssueId, Box<EditIssueModal>),
|
||||
@ -18,14 +18,14 @@ pub enum ModalType {
|
||||
TimeTracking(IssueId),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub struct CommentForm {
|
||||
pub id: Option<CommentId>,
|
||||
pub body: String,
|
||||
pub creating: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub struct EditIssueModal {
|
||||
pub id: i32,
|
||||
pub link_copied: bool,
|
||||
@ -87,7 +87,7 @@ impl EditIssueModal {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub struct AddIssueModal {
|
||||
pub title: String,
|
||||
pub issue_type: IssueType,
|
||||
@ -95,9 +95,9 @@ pub struct AddIssueModal {
|
||||
pub priority: IssuePriority,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub project_id: Option<i32>,
|
||||
pub user_ids: Vec<i32>,
|
||||
pub reporter_id: Option<i32>,
|
||||
|
@ -73,15 +73,13 @@ pub fn tracking_widget(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
|
||||
#[inline]
|
||||
fn calc_bar_width(
|
||||
estimate: Option<i32>,
|
||||
time_spent: Option<i32>,
|
||||
time_remaining: Option<i32>,
|
||||
estimate: Option<f64>,
|
||||
time_spent: Option<f64>,
|
||||
time_remaining: Option<f64>,
|
||||
) -> f64 {
|
||||
match (estimate, time_spent, time_remaining) {
|
||||
(_, Some(spent), Some(remaining)) => {
|
||||
((spent as f64 / (spent as f64 + remaining as f64)) * 100f64).min(100f64)
|
||||
}
|
||||
(Some(estimate), Some(spent), _) => ((spent as f64 / estimate as f64) * 100f64).min(100f64),
|
||||
(_, Some(spent), Some(remaining)) => ((spent / (spent + remaining)) * 100f64).min(100f64),
|
||||
(Some(estimate), Some(spent), _) => ((spent / estimate) * 100f64).min(100f64),
|
||||
(None, None, _) => 100f64,
|
||||
(None, _, _) => 0f64,
|
||||
_ => 0f64,
|
||||
|
@ -117,7 +117,6 @@ impl Application {
|
||||
output_timestamp: SystemTime,
|
||||
) -> Result<bool, String> {
|
||||
let input_dir = input
|
||||
.clone()
|
||||
.parent()
|
||||
.ok_or_else(|| format!("Not a valid path {:?}", input))?;
|
||||
|
||||
@ -143,7 +142,6 @@ impl Application {
|
||||
fn parse_file(&mut self, input: &Path) -> Result<Css, String> {
|
||||
let file_path = input.display().to_string();
|
||||
let input_dir = input
|
||||
.clone()
|
||||
.parent()
|
||||
.ok_or_else(|| format!("Not a valid path {:?}", input))?;
|
||||
|
||||
@ -179,7 +177,6 @@ impl Application {
|
||||
.replace(";", "")
|
||||
.to_string();
|
||||
let child = input_dir
|
||||
.clone()
|
||||
.join(imported.as_str())
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("{}", e))?;
|
||||
@ -292,11 +289,11 @@ fn main() -> Result<(), String> {
|
||||
|
||||
let output_timestamp = matches
|
||||
.value_of("output")
|
||||
.ok_or(std::io::Error::from_raw_os_error(0))
|
||||
.and_then(|path| File::open(path))
|
||||
.ok_or_else(|| std::io::Error::from_raw_os_error(0))
|
||||
.and_then(File::open)
|
||||
.and_then(|file| file.metadata())
|
||||
.and_then(|meta| meta.modified())
|
||||
.unwrap_or_else(|_| SystemTime::UNIX_EPOCH.clone());
|
||||
.unwrap_or_else(|_| SystemTime::UNIX_EPOCH);
|
||||
|
||||
if app.check_timestamps(root, output_timestamp)? {
|
||||
return Ok(());
|
||||
@ -304,7 +301,7 @@ fn main() -> Result<(), String> {
|
||||
|
||||
let (tx, rx) = channel();
|
||||
app.pipe(tx.clone());
|
||||
let mut watcher = watcher(tx.clone(), Duration::from_secs(1)).unwrap();
|
||||
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
|
||||
|
||||
app.parse()?;
|
||||
app.print();
|
||||
|
@ -429,6 +429,15 @@ impl std::fmt::Display for InvitationState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||
#[cfg_attr(feature = "backend", sql_type = "TimeTrackingType")]
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
||||
pub enum TimeTracking {
|
||||
Untracked,
|
||||
Fibonacci,
|
||||
Hourly,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Debug, PartialEq)]
|
||||
pub struct ErrorResponse {
|
||||
pub errors: Vec<String>,
|
||||
@ -444,6 +453,7 @@ pub struct Project {
|
||||
pub category: ProjectCategory,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub time_tracking: TimeTracking,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
@ -456,9 +466,9 @@ pub struct Issue {
|
||||
pub list_position: i32,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub reporter_id: UserId,
|
||||
pub project_id: ProjectId,
|
||||
pub created_at: NaiveDateTime,
|
||||
@ -517,7 +527,7 @@ pub struct Token {
|
||||
pub bind_token: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd, Hash)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||
pub struct UpdateIssuePayload {
|
||||
pub title: String,
|
||||
pub issue_type: IssueType,
|
||||
@ -526,9 +536,9 @@ pub struct UpdateIssuePayload {
|
||||
pub list_position: i32,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub project_id: ProjectId,
|
||||
pub reporter_id: UserId,
|
||||
pub user_ids: Vec<UserId>,
|
||||
@ -585,9 +595,9 @@ pub struct CreateIssuePayload {
|
||||
pub priority: IssuePriority,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub project_id: ProjectId,
|
||||
pub user_ids: Vec<UserId>,
|
||||
pub reporter_id: UserId,
|
||||
@ -605,6 +615,7 @@ pub struct UpdateProjectPayload {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum PayloadVariant {
|
||||
OptionI32(Option<i32>),
|
||||
OptionF64(Option<f64>),
|
||||
VecI32(Vec<i32>),
|
||||
I32(i32),
|
||||
String(String),
|
||||
|
@ -2,7 +2,9 @@ use std::io::Write;
|
||||
|
||||
use diesel::{deserialize::*, pg::*, serialize::*, *};
|
||||
|
||||
use crate::{InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory, UserRole};
|
||||
use crate::{
|
||||
InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory, TimeTracking, UserRole,
|
||||
};
|
||||
|
||||
#[derive(SqlType)]
|
||||
#[postgres(type_name = "IssuePriorityType")]
|
||||
@ -241,3 +243,43 @@ impl ToSql<InvitationStateType, Pg> for InvitationState {
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SqlType)]
|
||||
#[postgres(type_name = "TimeTrackingType")]
|
||||
pub struct TimeTrackingType;
|
||||
|
||||
impl diesel::query_builder::QueryId for TimeTrackingType {
|
||||
type QueryId = TimeTracking;
|
||||
}
|
||||
|
||||
fn time_tracking_from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
||||
match not_none!(bytes) {
|
||||
b"untracked" => Ok(TimeTracking::Untracked),
|
||||
b"fibnachi" => Ok(TimeTracking::Fibonacci),
|
||||
b"hourly" => Ok(TimeTracking::Hourly),
|
||||
_ => Ok(TimeTracking::Untracked),
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql<TimeTrackingType, Pg> for TimeTracking {
|
||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
||||
time_tracking_from_sql(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql<sql_types::Text, Pg> for TimeTracking {
|
||||
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<TimeTracking> {
|
||||
time_tracking_from_sql(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql<TimeTrackingType, Pg> for TimeTracking {
|
||||
fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result {
|
||||
match *self {
|
||||
TimeTracking::Untracked => out.write_all(b"untracked")?,
|
||||
TimeTracking::Fibonacci => out.write_all(b"fibnacci")?,
|
||||
TimeTracking::Hourly => out.write_all(b"hourly")?,
|
||||
}
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
alter TABLE issues alter COLUMN estimate SET DATA TYPE integer;
|
||||
alter TABLE issues alter COLUMN time_spent SET DATA TYPE integer;
|
||||
alter TABLE issues alter COLUMN time_remaining SET DATA TYPE integer;
|
||||
|
||||
alter TABLE projects drop COLUMN time_tracking;
|
||||
|
||||
drop TYPE IF EXISTS "TimeTrackingType";
|
11
jirs-server/migrations/2020-04-24-163323_add_settings/up.sql
Normal file
11
jirs-server/migrations/2020-04-24-163323_add_settings/up.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TYPE "TimeTrackingType" AS ENUM (
|
||||
'untracked',
|
||||
'fibonacci',
|
||||
'hourly'
|
||||
);
|
||||
|
||||
ALTER TABLE issues ALTER COLUMN estimate SET DATA TYPE double precision;
|
||||
ALTER TABLE issues ALTER COLUMN time_spent SET DATA TYPE double precision;
|
||||
ALTER TABLE issues ALTER COLUMN time_remaining SET DATA TYPE double precision;
|
||||
|
||||
ALTER TABLE projects ADD COLUMN time_tracking "TimeTrackingType" NOT NULL DEFAULT 'untracked';
|
@ -79,9 +79,9 @@ pub struct UpdateIssue {
|
||||
pub list_position: Option<i32>,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub project_id: Option<i32>,
|
||||
pub user_ids: Option<Vec<i32>>,
|
||||
pub reporter_id: Option<i32>,
|
||||
@ -209,9 +209,9 @@ pub struct CreateIssue {
|
||||
pub priority: IssuePriority,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub project_id: i32,
|
||||
pub reporter_id: i32,
|
||||
pub user_ids: Vec<i32>,
|
||||
|
@ -2,7 +2,7 @@ use actix::{Handler, Message};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use jirs_data::{Project, ProjectCategory};
|
||||
use jirs_data::{Project, ProjectCategory, TimeTracking};
|
||||
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
@ -46,6 +46,7 @@ pub struct UpdateProject {
|
||||
pub url: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub category: Option<ProjectCategory>,
|
||||
pub time_tracking: Option<TimeTracking>,
|
||||
}
|
||||
|
||||
impl Message for UpdateProject {
|
||||
@ -68,9 +69,10 @@ impl Handler<UpdateProject> for DbExecutor {
|
||||
msg.url.map(|v| url.eq(v)),
|
||||
msg.description.map(|v| description.eq(v)),
|
||||
msg.category.map(|v| category.eq(v)),
|
||||
msg.time_tracking.map(|v| time_tracking.eq(v)),
|
||||
))
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||
|
||||
projects
|
||||
.filter(id.eq(msg.project_id))
|
||||
|
@ -2,7 +2,9 @@ use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use jirs_data::{InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory};
|
||||
use jirs_data::{
|
||||
InvitationState, IssuePriority, IssueStatus, IssueType, ProjectCategory, TimeTracking,
|
||||
};
|
||||
|
||||
use crate::schema::*;
|
||||
|
||||
@ -24,9 +26,9 @@ pub struct Issue {
|
||||
pub list_position: i32,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub reporter_id: i32,
|
||||
pub project_id: i32,
|
||||
pub created_at: NaiveDateTime,
|
||||
@ -67,9 +69,9 @@ pub struct CreateIssueForm {
|
||||
pub list_position: i32,
|
||||
pub description: Option<String>,
|
||||
pub description_text: Option<String>,
|
||||
pub estimate: Option<i32>,
|
||||
pub time_spent: Option<i32>,
|
||||
pub time_remaining: Option<i32>,
|
||||
pub estimate: Option<f64>,
|
||||
pub time_spent: Option<f64>,
|
||||
pub time_remaining: Option<f64>,
|
||||
pub reporter_id: i32,
|
||||
pub project_id: i32,
|
||||
}
|
||||
@ -88,6 +90,7 @@ pub struct UpdateProjectForm {
|
||||
pub url: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub category: Option<ProjectCategory>,
|
||||
pub time_tracking: Option<TimeTracking>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||
|
@ -211,22 +211,22 @@ table! {
|
||||
description_text -> Nullable<Text>,
|
||||
/// The `estimate` column of the `issues` table.
|
||||
///
|
||||
/// Its SQL type is `Nullable<Int4>`.
|
||||
/// Its SQL type is `Nullable<Float8>`.
|
||||
///
|
||||
/// (Automatically generated by Diesel.)
|
||||
estimate -> Nullable<Int4>,
|
||||
estimate -> Nullable<Float8>,
|
||||
/// The `time_spent` column of the `issues` table.
|
||||
///
|
||||
/// Its SQL type is `Nullable<Int4>`.
|
||||
/// Its SQL type is `Nullable<Float8>`.
|
||||
///
|
||||
/// (Automatically generated by Diesel.)
|
||||
time_spent -> Nullable<Int4>,
|
||||
time_spent -> Nullable<Float8>,
|
||||
/// The `time_remaining` column of the `issues` table.
|
||||
///
|
||||
/// Its SQL type is `Nullable<Int4>`.
|
||||
/// Its SQL type is `Nullable<Float8>`.
|
||||
///
|
||||
/// (Automatically generated by Diesel.)
|
||||
time_remaining -> Nullable<Int4>,
|
||||
time_remaining -> Nullable<Float8>,
|
||||
/// The `reporter_id` column of the `issues` table.
|
||||
///
|
||||
/// Its SQL type is `Int4`.
|
||||
@ -304,6 +304,12 @@ table! {
|
||||
///
|
||||
/// (Automatically generated by Diesel.)
|
||||
updated_at -> Timestamp,
|
||||
/// The `time_tracking` column of the `projects` table.
|
||||
///
|
||||
/// Its SQL type is `TimeTrackingType`.
|
||||
///
|
||||
/// (Automatically generated by Diesel.)
|
||||
time_tracking -> TimeTrackingType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,13 +51,13 @@ impl WsHandler<UpdateIssueHandler> for WebSocketActor {
|
||||
(IssueFieldId::Priority, PayloadVariant::IssuePriority(p)) => {
|
||||
msg.priority = Some(p);
|
||||
}
|
||||
(IssueFieldId::Estimate, PayloadVariant::OptionI32(o)) => {
|
||||
(IssueFieldId::Estimate, PayloadVariant::OptionF64(o)) => {
|
||||
msg.estimate = o;
|
||||
}
|
||||
(IssueFieldId::TimeSpend, PayloadVariant::OptionI32(o)) => {
|
||||
(IssueFieldId::TimeSpend, PayloadVariant::OptionF64(o)) => {
|
||||
msg.time_spent = o;
|
||||
}
|
||||
(IssueFieldId::TimeRemaining, PayloadVariant::OptionI32(o)) => {
|
||||
(IssueFieldId::TimeRemaining, PayloadVariant::OptionF64(o)) => {
|
||||
msg.time_remaining = o;
|
||||
}
|
||||
_ => (),
|
||||
|
@ -35,6 +35,7 @@ impl WsHandler<UpdateProjectPayload> for WebSocketActor {
|
||||
url: msg.url,
|
||||
description: msg.description,
|
||||
category: msg.category,
|
||||
time_tracking: None,
|
||||
})) {
|
||||
Ok(Ok(project)) => project,
|
||||
_ => return Ok(None),
|
||||
|
Loading…
Reference in New Issue
Block a user