Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Improve/candidate view #119

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
22 changes: 19 additions & 3 deletions admin/candidate/candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func index(quizId string) string {
email
validity
complete
cancel
quiz_start
invite_sent
candidate.question {
Expand Down Expand Up @@ -125,7 +126,7 @@ func Add(w http.ResponseWriter, r *http.Request) {
}

// Token sent in mail is uid + the random string.
go mail.Send(c.Name, c.Email, t.Format("Mon Jan 2 15:04:05 MST 2006"),
go mail.Send(c.Email, t.Format("Mon Jan 2 15:04:05 MST 2006"),
uid+c.Token)
sr.Message = "Candidate added successfully."
sr.Success = true
Expand Down Expand Up @@ -186,8 +187,6 @@ func Edit(w http.ResponseWriter, r *http.Request) {
http.StatusInternalServerError)
return
}
go mail.Send(c.Name, c.Email, t.Format("Mon Jan 2 15:04:05 MST 2006"),
c.Uid+c.Token)
sr.Success = true
sr.Message = "Candidate info updated successfully."
w.Write(server.MarshalResponse(sr))
Expand Down Expand Up @@ -222,3 +221,20 @@ func Get(w http.ResponseWriter, r *http.Request) {
}
w.Write(res)
}

func ResendInvite(w http.ResponseWriter, r *http.Request) {

Choose a reason for hiding this comment

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

exported function ResendInvite should have comment or be unexported

sr := server.Response{}
vars := mux.Vars(r)
cid := vars["id"]

email := r.PostFormValue("email")
token := r.PostFormValue("token")
validity := r.PostFormValue("validity")
if email == "" || token == "" || validity == "" {
sr.Write(w, "", "Email/token/validity can't be empty.", http.StatusBadRequest)
return
}

go mail.Send(email, validity, cid+token)
sr.Write(w, "", "Invite has been resent.", http.StatusOK)
}
6 changes: 3 additions & 3 deletions admin/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ var SENDGRID_API_KEY = flag.String("sendgrid", "", "Sendgrid API Key")
// TODO - Later just have one IP address with port info.
var Ip = flag.String("ip", "http://localhost:2020", "Public IP address of server")

func Send(name, email, validity, token string) {
func Send(email, validity, token string) {

Choose a reason for hiding this comment

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

exported function Send should have comment or be unexported

if *SENDGRID_API_KEY == "" {
fmt.Println(*Ip + "/#/quiz/" + token)
return
}
from := mail.NewEmail("Dgraph", "join@dgraph.io")
subject := "Invitation for screening quiz from Dgraph"
to := mail.NewEmail(name, email)
to := mail.NewEmail("", email)
// TODO - Move this to a template.
url := fmt.Sprintf("%v/#/quiz/%v", *Ip, token)
body := `
Expand All @@ -30,7 +30,7 @@ func Send(name, email, validity, token string) {
<title></title>
</head>
<body>
Hello ` + name + `,
Hello!
<br/><br/>
You have been invited to take the screening quiz by Dgraph.
<br/>
Expand Down
2 changes: 2 additions & 0 deletions admin/webUI/app/app.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ angular.module('GruiApp').constant('APP_REQUIRES', {
'javascript': ['assets/lib/js/javascript.js'],
'marked': ['https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js'],
'highlight': ['assets/lib/js/highlight.pack.js'],
'moment': ['assets/lib/js/moment.min.js'],
'angular-moment': ['assets/lib/js/angular-moment.min.js']
},
});

Expand Down
1 change: 1 addition & 0 deletions admin/webUI/app/app.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
parent: 'invite',
templateUrl: inviteDashboardTemplate,
authenticate: true,
resolve: helper.resolveFor('moment', 'angular-moment')
})
.state('invite.add', {
url: '/invite-user',
Expand Down
72 changes: 71 additions & 1 deletion admin/webUI/app/components/invite/inviteController.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,15 @@
}
}

function candidatesController($rootScope, $stateParams, $state, inviteService) {
function candidatesController($rootScope, $stateParams, $state, inviteService, moment) {
candidatesVm = this;
candidatesVm.sortType = 'score';
candidatesVm.sortReverse = true;
candidatesVm.expires = expires;
candidatesVm.cancel = cancel;
candidatesVm.resend = resend;
candidatesVm.showModal = showModal;
candidatesVm.candidate_email = "";

candidatesVm.quizID = $stateParams.quizID;

Expand Down Expand Up @@ -299,6 +304,70 @@
}, function(err) {
console.log(err);
});

function showModal(candidateID, email) {
console.log(email)
candidatesVm.candidate_email = email;
dialog.showModal();
dialog.querySelector('.submit').addEventListener('click', function() {
candidatesVm.cancel(candidateID);
dialog.close();
})
}

angular.element(document).ready(function() {
var dialog = document.querySelector('dialog');

dialog.querySelector('.close').addEventListener('click', function() {
dialog.close();
});
});

function expires(validity) {
var numDays = moment(validity).diff(moment(), 'days')
if (numDays == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Moment.js is awesome library, saves time (not pun intended) and easy to use. We should use it only if we will be dealing with time/date heavily. Else we can work around in javascript. We should try to avoid using external libraries because it will add unnecessary load time by adding 68-70kb of file request for doing one task only.
Below code will help you to find difference between "validity date" and "today" in days.

Can decide about this. Moment.js is (time)saviour, has useful methods.

var validity_date = new Date(validity)
var today = new Date();
var diff =  (validity_date - today)/(1000*60*60*24)
noOfDays = Math.round(diff)

return "Today"
} else if (numDays > 0) {
return numDays
}
return "Expired"
}

function cancel(candidateID) {
inviteService.cancelInvite(candidateID).then(function(cancelled) {
if (!cancelled) {
SNACKBAR({
message: "Invite could not be cancelled.",
messageType: "error",
})
return
}
SNACKBAR({
message: "Invite cancelled successfully.",
})
$state.transitionTo("invite.dashboard", {
quizID: candidatesVm.quizID,
})
})
}

function resend(candidateID) {
inviteService.resendInvite(candidateID).then(function(response) {
if (!response.success) {
SNACKBAR({
message: response.message,
messageType: "error",
})
return
}
SNACKBAR({
message: response.message
})
$state.transitionTo("invite.dashboard", {
quizID: candidatesVm.quizID,
})
})
}
}

function candidateReportController($rootScope, $stateParams, $state, inviteService) {
Expand Down Expand Up @@ -372,6 +441,7 @@
"$stateParams",
"$state",
"inviteService",
"moment",
candidatesController
];
angular.module('GruiApp').controller('candidatesController', candidatesDependency);
Expand Down
58 changes: 58 additions & 0 deletions admin/webUI/app/components/invite/inviteService.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
var query = "{\
quiz(_uid_: " + quizId + ") {\
quiz.candidate {\
cancel\
email\
}\
}\
Expand All @@ -39,6 +40,9 @@
var candidates = data.quiz[0]["quiz.candidate"];
if (candidates) {
for (var i = 0; i < candidates.length; i++) {
if (candidates[i].cancel === 'true') {
continue
}
if (candidates[i].email === email) {
return deferred.resolve(true);
}
Expand All @@ -49,6 +53,60 @@
return deferred.promise;
}

services.resendInvite = function(candidateID) {
var deferred = $q.defer();
// TODO - User filter on email after incorporating Dgraph schema.
var query = "{\
quiz.candidate(_uid_: " + candidateID + ") {\
email\
token\
validity\
}\
}"

services.proxy(query).then(function(data) {
var candidate = data["quiz.candidate"][0];
if (candidate == null) {
return deferred.resolve({
success: false,
message: "No candidate found."
});
}
return candidate
}).then(function(candidate) {
var paylaod = {
"email": candidate.email,
"token": candidate.token,
"validity": candidate.validity
}
MainService.post('/candidate/invite/' + candidateID, paylaod).then(function(data) {
return deferred.resolve({
sucess: true,
message: data.Message
})
})
});
return deferred.promise;
}

Choose a reason for hiding this comment

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

Missing semicolon.


services.cancelInvite = function(candidateID) {
var deferred = $q.defer();
var mutation = "mutation {\

Choose a reason for hiding this comment

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

Bad escaping of EOL. Use option multistr if needed.

set {\

Choose a reason for hiding this comment

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

Bad escaping of EOL. Use option multistr if needed.

<_uid_:" + candidateID + "> <cancel> \"true\" .\

Choose a reason for hiding this comment

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

Bad escaping of EOL. Use option multistr if needed.

}\

Choose a reason for hiding this comment

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

Bad escaping of EOL. Use option multistr if needed.

}"

Choose a reason for hiding this comment

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

Missing semicolon.


services.proxy(mutation).then(function(data) {
if (data.code == "ErrorOk") {
return deferred.resolve(true);
}
return deferred.resolve(false);
});
return deferred.promise;
}

Choose a reason for hiding this comment

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

Missing semicolon.

Choose a reason for hiding this comment

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

Missing semicolon.

Choose a reason for hiding this comment

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

Missing semicolon.



// TODO - Move to a location where other services can access this.
services.proxy = function(data) {
return MainService.post('/proxy', data);
Expand Down
55 changes: 38 additions & 17 deletions admin/webUI/app/components/invite/views/invite-dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,30 @@ <h5>Candidate List for Quiz</h5>
<th class="mdl-data-table__cell--non-numeric">Email</th>
<th class="mdl-data-table__cell--non-numeric">
<a href="" ng-click="candidateVm.sortType = 'score'; candidateVm.sortReverse = !candidateVm.sortReverse">
Score
<i ng-show="candidateVm.sortType == 'score' && !candidateVm.sortReverse" class="material-icons">arrow_drop_up</i>
<i ng-show="candidateVm.sortType == 'score' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
Score
<i ng-show="candidateVm.sortType == 'score' && !candidateVm.sortReverse" class="material-icons">arrow_drop_up</i>
<i ng-show="candidateVm.sortType == 'score' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
</th>
<th class="mdl-data-table__cell--non-numeric">
<a href="" ng-click="candidateVm.sortType = 'quiz_start'; candidateVm.sortReverse = !candidateVm.sortReverse">
Quiz Start
<i ng-show="candidateVm.sortType == 'quiz_start' && !candidateVm.sortReverse" class="material-icons">arrow_drop_up</i>
<i ng-show="candidateVm.sortType == 'quiz_start' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
Quiz Start
<i ng-show="candidateVm.sortType == 'quiz_start' && !candidateVm.sortReverse" class="material-icons">arrow_drop_up</i>
<i ng-show="candidateVm.sortType == 'quiz_start' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
</th>
<th class="mdl-data-table__cell--non-numeric">Actions</th>
</tr>
</thead>
<tbody ng-repeat="candidate in candidateVm.quizCandidates | orderBy:candidateVm.sortType:candidateVm.sortReverse | filter:searchCand">
<tr ng-if="candidate.complete == 'true'">
<tbody ng-repeat="candidate in candidateVm.quizCandidates | orderBy:candidateVm.sortType:candidateVm.sortReverse | filter:searchCand" on-finish-render="ngRepeatFinished">
<tr ng-show="candidate.complete == 'true'">
<td class="mdl-data-table__cell--non-numeric">{{candidate.name}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidate.email}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidate.score | number: 2}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidate.quiz_start | date : 'MMM dd, yyyy'}}</td>
<td class="mdl-data-table__cell--non-numeric">
<a ui-sref="invite.report({candidateID: candidate._uid_})">VIEW REPORT</a>
<a ui-sref="invite.report({candidateID: candidate._uid_})">VIEW REPORT</a> &nbsp;|&nbsp;
<a class="delete-cand" href="">DELETE</a>
</td>
</tr>
</tbody>
Expand All @@ -63,8 +64,8 @@ <h5>Candidate List for Quiz</h5>
<div class="mdl-cell mdl-cell--12-col">
<form action="#">
<div class="mdl-textfield mdl-js-textfield">
<input class="mdl-textfield__input" type="text" id="sample1" ng-model="searchCand">
<label class="mdl-textfield__label" for="sample1">Search candidate by Name/Email</label>
<input class="mdl-textfield__input" type="text" id="search-cand" ng-model="searchCand">
<label class="mdl-textfield__label" for="search-cand">Search candidate by Name/Email</label>
</div>
</form>
<table class="mdl-data-table mdl-shadow--2dp">
Expand All @@ -78,17 +79,25 @@ <h5>Candidate List for Quiz</h5>
<i ng-show="candidateVm.sortType == 'invite_sent' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
</th>
<th class="mdl-data-table__cell--non-numeric">Validity</th>
<th class="mdl-data-table__cell--non-numeric">
<a href="" ng-click="candidateVm.sortType = 'validity'; candidateVm.sortReverse = !candidateVm.sortReverse">
Expires (days)
<i ng-show="candidateVm.sortType == 'validity' && !candidateVm.sortReverse" class="material-icons">arrow_drop_up</i>
<i ng-show="candidateVm.sortType == 'validity' && candidateVm.sortReverse" class="material-icons">arrow_drop_down</i>
</a>
</th>
<th class="mdl-data-table__cell--non-numeric">Actions</th>
</tr>
</thead>
<tbody ng-repeat="candidate in candidateVm.quizCandidates | orderBy:candidateVm.sortType:candidateVm.sortReverse | filter:searchCand">
<tr ng-if="candidate.complete != 'true'">
<tr ng-show="candidate.complete != 'true' && candidate.cancel != 'true'">
<td class="mdl-data-table__cell--non-numeric">{{candidate.email}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidate.invite_sent | date : 'MMM dd, yyyy'}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidate.validity}}</td>
<td class="mdl-data-table__cell--non-numeric">{{candidateVm.expires(candidate.validity)}}</td>
<td class="mdl-data-table__cell--non-numeric">
<a ui-sref="invite.edit({quizID: candidateVm.quizID, candidateID: candidate._uid_})">EDIT</a>
<a ui-sref="invite.edit({quizID: candidateVm.quizID, candidateID: candidate._uid_})">EDIT</a> &nbsp;|&nbsp;
<a ng-click="candidateVm.resend(candidate._uid_)" href="">RESEND</a> &nbsp;|&nbsp;
<a ng-click="candidateVm.showModal(candidate._uid_, candidate.email)" href="">CANCEL</a>
</td>
</tr>
</tbody>
Expand All @@ -97,4 +106,16 @@ <h5>Candidate List for Quiz</h5>
</div>
</div>
</div>
<dialog id="dialog" class="mdl-dialog">
<h4 class="mdl-dialog__title">Cancel</h4>
Copy link
Contributor

@Rahul-Sagore Rahul-Sagore Nov 8, 2016

Choose a reason for hiding this comment

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

This Dialog Box won't work. Need to remove this. This dialog modal is from official MDL doc: https://getmdl.io/components/index.html#dialog-section.

The first line of doc says : "Note: Dialogs use the HTML element, which currently has very limited cross-browser support. To ensure support across all modern browsers, please consider using a polyfill or creating your own. There is no polyfill included with MDL."

So <dialog> tag has less support. It is only supported by Chrome 37+ and Opera 24+. So it won't work in Firefox, Safari, IE, Edge, other browsers and versions.

Dialog support in browsers

<div class="mdl-dialog__content">
<p>
Are you sure that you wan't to cancel the invite sent to {{candidateVm.candidate_email}}?
</p>
</div>
<div class="mdl-dialog__actions">
<button type="button" class="mdl-button submit">Yes</button>
<button type="button" class="mdl-button close">Cancel</button>
</div>
</dialog>
</div>
2 changes: 0 additions & 2 deletions admin/webUI/assets/js/gru.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// MATERIAL DESIGN SNACKBAR
(function() {

$(document).ready(function() {
Expand All @@ -18,7 +17,6 @@
);
}


(function() {
setTimeout(function() {
$mdl_input = $(".mdl-textfield__input")
Expand Down
Loading