Skip to content

Split report by reviewer via modal ( Frontend Side Only )#1227

Open
MitanOmar wants to merge 3 commits into
adfinis:mainfrom
MitanOmar:split-report-by-reviewer-via-modal
Open

Split report by reviewer via modal ( Frontend Side Only )#1227
MitanOmar wants to merge 3 commits into
adfinis:mainfrom
MitanOmar:split-report-by-reviewer-via-modal

Conversation

@MitanOmar

@MitanOmar MitanOmar commented May 28, 2026

Copy link
Copy Markdown
Member

a part of #1121

  • split the modal content UI Original report in the left, and new one in the right
  • rename new report to be Second Report
  • show the label in top of the input
  • show the Task Selection of the original report
  • bring the cancel to float start
  • split reports for all users who can edit the report
  • Automated Tests

@MitanOmar MitanOmar changed the title Split report by reviewer via modal Split report by reviewer via modal ( Frontend Side Only ) May 28, 2026
@trowik trowik linked an issue May 28, 2026 that may be closed by this pull request
1 task
@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch from 5089d6b to 7ccb9e9 Compare May 28, 2026 13:36

get canSplitReport() {
return (
this.id.length === 1 &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this.id.length === 1 ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is one of many coming conditions that should be applied before allowing split report,
and the first condition that the selected reports for edit have to be one

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is id here? why are we taking its length? isn't id usually an increasing number? (for like records)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

image it's an array of selected reports id as string

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ahhh, that's cursed, maybe rename it to ids or reports/reportIds? (that would greatly improve readability)

@action
handleClick(e) {
if (e.target.id === this.id) {
if (e.target.id === this.id && this.args.closeOnBackdropClick !== false) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is this for?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is for disable interactivity with the background, so no one will accidentally close the modal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What would be the usecase for that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

user experience preferred after collection opinions from the team

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No I mean, of this implementation, why not have the browser handle this for us

Comment on lines +13 to +19
@tracked newTask = null;
@tracked newComment = "";
@tracked newDuration = moment.duration();

@tracked oldTask = null;
@tracked oldComment = "";
@tracked oldDuration = moment.duration();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you didn't use models here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

do you mean passing a reference from parent to child ( Split Report Component ) ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I meant using ember-data models, for boith?

@MitanOmar MitanOmar May 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it's not the final version yet, this part was done after a request of making a demo.
and this was easier for quick demo, let's see later what will be the final results

@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch 2 times, most recently from 15465ad to 222964c Compare June 3, 2026 14:28
@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch from 222964c to 3e880b8 Compare June 11, 2026 04:21
@c0rydoras

Copy link
Copy Markdown
Collaborator

you might want to rebase ^^

@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch 7 times, most recently from 4be047f to 2cb6db4 Compare June 12, 2026 15:54
@MitanOmar

MitanOmar commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

@g-kedev fyi: the modified information is sent to endpoint /api/v1/reports/{original-report-id}/split,
and the payload is structured as

old_report: {
     task: "original report id",
      comment: "original content or modified content",
       duration: "modified duration",
},
new_report: {
       task: "The new task id"
       comment: "The new content",
       duration: "the new duration",
},

in case that you prepared another endpoint, or expected it somewhere else, just comment here, and i will modify the URL in the frontend

@MitanOmar MitanOmar marked this pull request as ready for review June 12, 2026 16:02
@MitanOmar MitanOmar requested a review from a team as a code owner June 12, 2026 16:02
Comment on lines +24 to +30
@tracked newTask = null;
@tracked newComment = "";
@tracked newDuration = Duration.fromMillis(0);

@tracked oldTask = null;
@tracked oldComment = "";
@tracked oldDuration = Duration.fromMillis(0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we're not going to use ember data models, you could have these as a separate data structure, to improve readability and maintainability :)

class Report {
  @tracked
  task = null;
  
  @tracked 
  comment = "";
  
  @tracked 
  duration = Duration.fromMillis(0);
};


export default class SplitReportModal extends Component {
  report = new Report();
  newReport = new Report();
  [...]
}

What's the rationale for not using ember data models?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

because it need deep watching of the changes of the model.
and this was easier for implementation, and also more clear for reviewer

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

so what about using a native class with @tracked attributes? (instead of the model)

Comment on lines +52 to +60
@action
onSetOldTask(task) {
this.oldTask = task;
}

@action
onSetNewTask(task) {
this.newTask = task;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

you could inline these in the template with fn (mut this.newTask), or fn (mut this.splitReport.task)

which is what I personally prefer (for readability)

Comment on lines +69 to +81
@action
onNewDurationChange(value) {
const totalMs = this.oldDuration.as("milliseconds");
const ms = Math.min(value?.as("milliseconds") ?? 0, totalMs);
this.newDuration = Duration.fromMillis(Math.max(ms, 0));
}

@action
onRemainingDurationChange(value) {
const totalMs = this.oldDuration.as("milliseconds");
const remainingMs = Math.min(value?.as("milliseconds") ?? 0, totalMs);
this.newDuration = Duration.fromMillis(Math.max(totalMs - remainingMs, 0));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You set newDuration in both of these 🤔

Consider adding a comment or two to explain what is happening here.

There's likely a much neater way to implement this, maybe @derrabauke has some ideas?

Comment on lines +83 to +95
get isValidSplit() {
if (!this.newTask) return false;
if (!this.newDuration || this.newDuration.as("milliseconds") <= 0) {
return false;
}
if (
!this.oldDuration ||
this.newDuration.as("milliseconds") >= this.oldDuration.as("milliseconds")
) {
return false;
}
return true;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Have you considered using ember-changeset-validations? (see ember/frontend/app/validators/datetime.js)

Comment on lines +109 to +123
await this.fetch.fetch(`/api/v1/reports/${reportId}/split`, {
method: "POST",
body: {
old_report: {
task: this.oldTask.id,
comment: this.oldComment,
duration: durationTransform.serialize(this.remainingOldDuration),
},
new_report: {
task: this.newTask.id,
comment: this.newComment,
duration: durationTransform.serialize(this.newDuration),
},
},
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would imagine ember data could help us here? Like you could probably create an ember data model for this (not quite sure how well this would work)

Maybe @derrabauke has some input/pointers?

@g-kedev g-kedev requested a review from a team as a code owner June 15, 2026 14:43
@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch 4 times, most recently from 32e26c0 to f324e6b Compare June 16, 2026 11:00
Comment thread ember/frontend/app/analysis/edit/controller.js Outdated
@MitanOmar

MitanOmar commented Jun 16, 2026

Copy link
Copy Markdown
Member Author
image

@derrabauke @c0rydoras @Jana90JL @g-kedev @trowik
should we keep this button disabled, or just remove it
👍 for removing
❤️ for disabling it

@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch from 1c38efb to 18a0b32 Compare June 16, 2026 12:48
@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch 2 times, most recently from 5bd342a to af01923 Compare June 16, 2026 14:16
@MitanOmar MitanOmar force-pushed the split-report-by-reviewer-via-modal branch from af01923 to 694185a Compare June 18, 2026 08:38
Comment on lines 158 to 160
get canSplitReport() {
return (
this.id?.length === 1 &&
this.abilities.can(
"editSync report",
this.intersection.lastSuccessful.value.model,
)
);
return this.id?.length === 1;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

refactor: remove the validation if the current user is a reviewer

AFAICT this just fully removes the validation?

nit: refactor(frontend): instead of refactor:

@c0rydoras

Copy link
Copy Markdown
Collaborator

instead of feat: split reports as commit message, I'd suggest something like feat(frontend): allow splitting reports

@g-kedev

g-kedev commented Jun 19, 2026

Copy link
Copy Markdown

@g-kedev fyi: the modified information is sent to endpoint /api/v1/reports/{original-report-id}/split, and the payload is structured as

old_report: {
     task: "original report id",
      comment: "original content or modified content",
       duration: "modified duration",
},
new_report: {
       task: "The new task id"
       comment: "The new content",
       duration: "the new duration",
},

in case that you prepared another endpoint, or expected it somewhere else, just comment here, and i will modify the URL in the frontend

@MitanOmar Endpoint is correct. The data structure is a little different, here is an example:

{
  "data": {
    "attributes": {
      "new_report": {
        "comment": "some new comment",
        "duration": "00:15:00",
        "task": {
          "type": "tasks",
          "id": 16
        }
      },
      "updated_report": {
        "comment": "some updated comment",
        "duration": "00:15:00",
        "task": {
          "type": "tasks",
          "id": 16
        }
      }
    },
    "type": "report-splits"
  }
}

@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch from 694185a to b13607e Compare June 19, 2026 07:25
@c0rydoras

Copy link
Copy Markdown
Collaborator

"duration_as_seconds"

Why not use a DurationField?

@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch from b13607e to f15c50c Compare June 19, 2026 10:54
@g-kedev

g-kedev commented Jun 19, 2026

Copy link
Copy Markdown

Why not use a DurationField?

Since the data is nested, I wasn't sure how to validate the fields via the serializer attributes, but I created a second data serializer so it can use said attribute-validation.

@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch from f15c50c to f51c0d9 Compare June 19, 2026 11:05
Comment thread backend/timed/tracking/serializers.py Outdated
Comment thread backend/timed/tracking/serializers.py Outdated
class ReportDataSerializer(serializers.Serializer):
comment = serializers.CharField()
duration = serializers.DurationField()
task_id = serializers.IntegerField()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm fairly sure you could use some RelatedField or sth like that for task_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Adjusted it, now it uses ResourceRelatedField.

Comment thread backend/timed/tracking/serializers.py Outdated
Comment on lines +523 to +524
if not original_report:
raise ValidationError(_("Original report doesn't exist"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this ever happen?

@g-kedev g-kedev Jun 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No it already gets caught before it reaches this point, removed it.

Comment thread backend/timed/tracking/views.py Outdated
Comment on lines +404 to +405
new_report_task = Task.objects.get(id=new_report.get("task_id"))
updated_report_task = Task.objects.get(id=updated_report.get("task_id"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why do this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

To get the billed flag from the project afterwards.
If you're asking why to put it in a variable: To get the id also from the object, thought it would be nicer than taking it from the request, since we have to get the object anyway AFAICT (for the reason above).

Comment thread backend/timed/tracking/views.py Outdated
@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch 6 times, most recently from e03b3f2 to 1617db3 Compare June 22, 2026 13:21
@g-kedev g-kedev force-pushed the split-report-by-reviewer-via-modal branch from 1617db3 to d58bac4 Compare June 22, 2026 14:17
class ReportDataSerializer(Serializer):
comment = serializers.CharField()
duration = serializers.DurationField()
task = ResourceRelatedField(queryset=Task.objects.all())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe we could use Task.objects.filter(archived=False) instead of Task.objects.all() here 🤔

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.

[FEATURE]: Allow splitting up timed entries by reviewer

3 participants