-
Notifications
You must be signed in to change notification settings - Fork 0
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
[DNCR-27] Course creation #25
Conversation
…cripts dartisan.sh and prepenv.sh.
…component. Removed start_date parameter from front-end request.
// TODO remove mock company_id above and inject real one once DNCR-92 is merged | ||
$course = Course::create($courseData); | ||
|
||
$start_date_from_request = ['start_date' => (new DateTime($request->start_time))->format('Y-m-d')]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either camelCase or snake_case and I think we stick to the camelCase.
return response()->json(Course::create($request->all())); | ||
$courseData = $request->only('name', 'price', 'classes_count', 'description', 'seats_count') + ['company_id' => 1]; | ||
// TODO remove mock company_id above and inject real one once DNCR-92 is merged | ||
$course = Course::create($courseData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about transaction for this? https://laravel.com/docs/5.2/database#database-transactions
@@ -100,7 +100,7 @@ | |||
'provider' => 'users', | |||
'email' => 'auth.emails.password', | |||
'table' => 'password_resets', | |||
'expire' => 60, | |||
'expire' => 120, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you prolong token validation? We need to add token refreshing in our frontend app, that's all ;)
@@ -7,7 +7,7 @@ import { Component, Input } from '@angular/core'; | |||
<div class="form-group" [class.has-danger]="errors"> | |||
<ng-content></ng-content> | |||
<div class="form-control-feedback" *ngIf="errors"> | |||
<span *ngFor="let error of errors">{{ error }}</span> | |||
<span *ngFor="let error of errors">{{ error }} </span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably needs to be converted into a list, but as agreed - we don't have to cover this right now.
@@ -21,3 +24,62 @@ export class CourseTime { | |||
export class CourseEvent { | |||
date: string; | |||
} | |||
|
|||
export class CreateCourseRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but I think this is equivalent to Course
model, isn't it? The Course
should have required fields for submodels (like CourseTime
). Why did you add this class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment of creating this there was no Course model and I thought it'd be easier to create flat request structure and make it nested later when adding multiple courseTimes (https://trello.com/c/JtGMgJvu). It's up to you - should I change it now or leave it for mentioned task?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, leave it for now, let's just proceed with enhancements.
return events; | ||
public ngOnInit(): void { | ||
this.service.calendarEvents().subscribe((events) => this.events = events); | ||
this.service.courseUpdates.subscribe( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this should be called courseCreated
instead of courseUpdates
- because in case of update you need to get whole list and re-render everything. In case of creation - just add the events as you do here.
@@ -43,6 +44,7 @@ export class ManagerCalendarComponent { | |||
|
|||
onDayClick(e: CalendarDayClick) { | |||
console.log('clicked on day in manager with date: ' + e.date.format()); | |||
this.router.navigate(['/manager/courses']); | |||
this.service.broadcastCalendarDateClick(e.date); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said earlier - this should be replaced with calendar event (I mean @Output() here
) so the component above properly handles the click. This will allow us to easier control what is done where and how it works in general (see how React is built for example - with explicit control and data flows).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, the thing is now it's only a click that redirects to page / updates the date input. Do you mean to create and insert event on calendar on click (as google calendar does on click) and broadcast it instead of date only? I think it'd look good, but maybe let's do a separate task for it? There are some quirks as to how to handle title, connect drag & drop with create-course form etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As said on Slack - I wanted it to be more tree-like passing data, but it seems impossible to do. Leave it as it is now.
@@ -43,6 +44,7 @@ export class ManagerCalendarComponent { | |||
|
|||
onDayClick(e: CalendarDayClick) { | |||
console.log('clicked on day in manager with date: ' + e.date.format()); | |||
this.router.navigate(['/manager/courses']); | |||
this.service.broadcastCalendarDateClick(e.date); | |||
this.router.navigate(['/manager/courses/create-course']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This navigation should also be moved to the upper component as the calendar should display data only, not redirect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't yet feel that it's wrong, but I'll trust you with handling it outside of manager-calendar
.
On the other hand - how about redirection in CoursesService
? I'm having this problem in DNCR-103, where there is course details (& edit) page and Save button is separated from CourseEditComponent (CreateCourseComponent in this task) with <router-outlet>
and Service seems like the best place to handle both request and redirection, so the same action (creation and update) is handled in one place.
I may have put it in a convoluted way, bottom line is - do you think it's a good idea to handle route changes in service?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't. I think it should be like this: this.service.update(course).subscribe((course) => this.router.navigate(...));
. Why? Because the service is responsible for updating the entity only (SRP) and the component is responsible for proper redirection in case of success.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I started migrating code that navigates router from manager-calendar to ManagerCoursesComponent
(it's parent) and I stopped, because it seems pointless. Think about it. We have CalendarComponent
which wraps calendar from ngPrime. So it only displays data and outputs changes with our interfaces, okay. Then we have ManagerCalendarComponent
and ReceptionCalendarComponent
which add little customization (default view, ability to edit) and are components surrounding calendar, they subscribe to course service to fetch & update data.
And I see three approaches:
- they handle actions on clicks, whether it's notification to courses.service or route change, as it is now.
- they broadcast all changes and pass event handling to parent component, but then
ManagerCoursesComponent
andReception
have to duplicate all event handling interfaces andManagerCalendar
andReceptionCalendar
are additional layer of wrappers that doesn't do much. - handle some of the events and leave some handling to upper component (so in this commented line - should
ManagerCalendar
tell service to broadcast click and then emit change so it's parent changes route? where's the line of responsibility? now that sounds like breaking SRP to me.
I think you're expecting something between 2 and 3, I don't see it clearly. I think leaving 1 is the simplest approach.
EDIT: And think about encapsulating calendar interface - I think it'd be good for ManagerCalendar
and ReceptionCalendar
to be the last ones to have to know about calendar details. And then all the calendar-handling-logic is in one place and we don't have additional layer of abstraction that does almost nothing. The 'dumb displaying of data and propagating events' is already done in CalendarComponent
..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't agree with you on that as you seem to miss the point. How can calendar know what to do with selected course? It's up to the container to decide and this is why ManagerCalendarComponent
became responsible for multiple purposes: showing data (with fetching them!) and navigating to details page (what if we wanted it to behave differently when used in different place?). The reason to abstract it is to make sure that we can reason about the program easily and this means we can open ManagerCoursesComponent
to know what we want to know.
This is course clicked event
rather than date clicked event
so we are abstracting things and pushing "hey, somebody clicked on course 1" to the parent.
BTW. I really would like to hear me as I worked on large project in AngularJS and I know this simple things will make it far easier to update and maintain in the future.
], | ||
imports: [ | ||
BrowserModule, FormsModule, RouterModule.forChild(routes), CommonsModule, AttendeeModule, CourseModule | ||
], | ||
providers: [ | ||
CourseResolve, InstructorsService, LocationsService |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to add CourseResolve
here?
@@ -0,0 +1,15 @@ | |||
app > span.loader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file was in .git-ignore and was removed on purpose. Please remove it again.
… frontend. Todo csses and removal of errors.
{ | ||
$course = Course::create($courseData); | ||
$courseTime = $course->times()->create($courseTimeData); | ||
$courseTime->course()->associate($course); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this line needed? I thought when you do $course->times()->create()
it properly associates it back to itself.
{ | ||
$date = clone($start_date); | ||
$event = new CourseEvent(['date' => $date->add(new DateInterval("P{$week}W"))]); | ||
$event->courseTime()->associate($courseTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of these 2 lines you could do $courseTimes->events()->create(...);
- we have transaction so it does not hurt us anymore.
return $course; | ||
}); | ||
|
||
$course->load('times.events'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? We have added everything as we should so it has all the data it needs, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need it because this single returned course is added to calendar in front-end, so we need it's times and events so CalendarItems
can be parsed from it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But as you see the $course
already contains all events and times you need, don't you?
@@ -21,3 +24,62 @@ export class CourseTime { | |||
export class CourseEvent { | |||
date: string; | |||
} | |||
|
|||
export class CreateCourseRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, leave it for now, let's just proceed with enhancements.
constructor(private http: AuthHttp) { | ||
this.recentlyClickedTimeSource.next(CreateCourseTime.defaultTime()); // default for page refresh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, as this is a prototype simply remove this line. We won't ever need default time and when we click to create term - we just get the time we want, that's all ;)
|
||
<p class="alert alert-danger" role="alert" *ngIf="error">{{ error }}</p> | ||
<form-field [errors]="errors.startTime" class="col-xs-12 row moved-errors"> | ||
<label for="startTime" class="col-xs-3 col-xl-2 col-form-label test">Rozpoczęcie</label> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explained on Slack - if you need help here please ask ;)
|
||
public ngOnInit() { | ||
this.model.repeatWeeksCount = 1; | ||
this.coursesService.recentlyClickedTime.subscribe( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have strong feeling it does not belong here, but this should be updated when multiple terms are being worked on.
@@ -43,6 +44,7 @@ export class ManagerCalendarComponent { | |||
|
|||
onDayClick(e: CalendarDayClick) { | |||
console.log('clicked on day in manager with date: ' + e.date.format()); | |||
this.router.navigate(['/manager/courses']); | |||
this.service.broadcastCalendarDateClick(e.date); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As said on Slack - I wanted it to be more tree-like passing data, but it seems impossible to do. Leave it as it is now.
@@ -43,6 +44,7 @@ export class ManagerCalendarComponent { | |||
|
|||
onDayClick(e: CalendarDayClick) { | |||
console.log('clicked on day in manager with date: ' + e.date.format()); | |||
this.router.navigate(['/manager/courses']); | |||
this.service.broadcastCalendarDateClick(e.date); | |||
this.router.navigate(['/manager/courses/create-course']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't. I think it should be like this: this.service.update(course).subscribe((course) => this.router.navigate(...));
. Why? Because the service is responsible for updating the entity only (SRP) and the component is responsible for proper redirection in case of success.
@@ -68,14 +77,22 @@ export const routes = [ | |||
ManagerCoursesSingleComponent, | |||
ManagerCoursesDetailsComponent, | |||
ManagerCoursesAttendeesComponent, | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty line here? :)
…Improved observables code style in CoursesService. Removed toast error handling and added date check.
<option *ngFor="let i of instructors" [value]="i.id">{{i.name + ' ' + i.surname}}</option> | ||
</select> | ||
</labelled-form-field> | ||
<labelled-form-field [errors]="errors.locationId" [labelFor]="model.locationId" [label]="'Sala'"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before you comment that it could be just [label]="Sala"
-> yes it could, but in most of other places we have either space or polish character with which this syntax doesn't work and single quotes are needed, so I think it's better to just use the same syntax in whole file.
…our. Course title responsivness left for some other time.
return $course; | ||
}); | ||
|
||
$course->load('times.events'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But as you see the $course
already contains all events and times you need, don't you?
selector: 'labelled-form-field', | ||
template: ` | ||
<form-field [errors]="errors" class="row m-r-0"> | ||
<label for="labelFor" class="col-xs-12 col-sm-3 col-xl-2 col-form-label">{{ label }}</label> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for="{{ labelFor }}"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a tutaj znalazłem wyjaśnienie, czemu angular/angular#3803
ale na szczęście [attr.for]="labelFor"
już śmiga. przy okazji znalazłem buga, że podawałem tam pole modelu, a nie id obiektu, więc podwójne dzięks :).
) | ||
export class LabelledFormField { | ||
@Input() errors: string[] = []; | ||
@Input() labelFor: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not simple for
?
@@ -0,0 +1,33 @@ | |||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; | |||
import { InstructorsService } from '../../../instructors/instructors.service'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks ;)
@@ -43,6 +44,7 @@ export class ManagerCalendarComponent { | |||
|
|||
onDayClick(e: CalendarDayClick) { | |||
console.log('clicked on day in manager with date: ' + e.date.format()); | |||
this.router.navigate(['/manager/courses']); | |||
this.service.broadcastCalendarDateClick(e.date); | |||
this.router.navigate(['/manager/courses/create-course']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't agree with you on that as you seem to miss the point. How can calendar know what to do with selected course? It's up to the container to decide and this is why ManagerCalendarComponent
became responsible for multiple purposes: showing data (with fetching them!) and navigating to details page (what if we wanted it to behave differently when used in different place?). The reason to abstract it is to make sure that we can reason about the program easily and this means we can open ManagerCoursesComponent
to know what we want to know.
This is course clicked event
rather than date clicked event
so we are abstracting things and pushing "hey, somebody clicked on course 1" to the parent.
BTW. I really would like to hear me as I worked on large project in AngularJS and I know this simple things will make it far easier to update and maintain in the future.
…endar components.
courseCreated = this.courseCreatedSource.asObservable(); | ||
calendarItemsCreated = this.courseCreated.map(this.courseToCalendarItems); | ||
|
||
recentlyClickedTimeSource = new ReplaySubject<CreateCourseTime>(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be private as well.
|
||
createCourse() { | ||
this.coursesService.create(this.model).subscribe( | ||
(course) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know you don't need braces if you have single line command? :)
this.router.navigate(['/reception/course-details', course.id]); | ||
} | ||
|
||
onDayClick(moment: Moment) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this :)
Things not yet working / known issues:
hidden
anddisabled
andselected
, but couldn't figure it out in reasonable time