frontend: allow players to respond to issues
This commit is contained in:
parent
73eb3ab1c0
commit
edbac43423
@ -13,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IssueUpload struct {
|
type IssueUpload struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
IdExercice int64 `json:"id_exercice"`
|
IdExercice int64 `json:"id_exercice"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
@ -32,9 +33,35 @@ func treatIssue(pathname string, team fic.Team) {
|
|||||||
} else if err := json.Unmarshal(cnt_raw, &issue); err != nil {
|
} else if err := json.Unmarshal(cnt_raw, &issue); err != nil {
|
||||||
log.Printf("%s [ERR] %s\n", id, err)
|
log.Printf("%s [ERR] %s\n", id, err)
|
||||||
} else if len(issue.Subject) == 0 && len(issue.Description) == 0 {
|
} else if len(issue.Subject) == 0 && len(issue.Description) == 0 {
|
||||||
|
if err = os.Remove(pathname); err != nil {
|
||||||
|
log.Printf("%s [ERR] %s\n", id, err)
|
||||||
|
}
|
||||||
log.Printf("%s Empty issue: not treated.\n", id)
|
log.Printf("%s Empty issue: not treated.\n", id)
|
||||||
} else if len(issue.Subject) == 0 {
|
} else if len(issue.Subject) == 0 {
|
||||||
log.Printf("%s Issue with no subject: not treated.\n", id)
|
if issue.Id <= 0 {
|
||||||
|
if err = os.Remove(pathname); err != nil {
|
||||||
|
log.Printf("%s [ERR] %s\n", id, err)
|
||||||
|
}
|
||||||
|
log.Printf("%s Issue with no subject: not treated.\n", id)
|
||||||
|
} else if claim, err := team.GetClaim(issue.Id); err != nil {
|
||||||
|
log.Printf("%s [ERR] Team id=%d,name=%q tries to access issue id=%d, but not granted: %s.\n", id, team.Id, team.Name, issue.Id, err)
|
||||||
|
} else if len(issue.Description) == 0 {
|
||||||
|
if err = os.Remove(pathname); err != nil {
|
||||||
|
log.Printf("%s [ERR] %s\n", id, err)
|
||||||
|
}
|
||||||
|
log.Printf("%s Empty issue: not treated.\n", id)
|
||||||
|
} else if desc, err := claim.AddDescription(issue.Description, fic.ClaimAssignee{Id: 0}, true); err != nil {
|
||||||
|
log.Printf("%s [WRN] Unable to add description to issue: %s\n", id, err)
|
||||||
|
} else {
|
||||||
|
claim.State = "new"
|
||||||
|
claim.Update()
|
||||||
|
|
||||||
|
log.Printf("%s [OOK] New comment added to issue id=%d: id_description=%s\n", id, claim.Id, desc.Id)
|
||||||
|
if err = os.Remove(pathname); err != nil {
|
||||||
|
log.Printf("%s [ERR] %s\n", id, err)
|
||||||
|
}
|
||||||
|
genTeamIssuesFile(team)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var exercice *fic.Exercice = nil
|
var exercice *fic.Exercice = nil
|
||||||
if e, err := fic.GetExercice(issue.IdExercice); err == nil {
|
if e, err := fic.GetExercice(issue.IdExercice); err == nil {
|
||||||
|
@ -91,6 +91,11 @@ server {
|
|||||||
|
|
||||||
rewrite ^/.*$ /index.html;
|
rewrite ^/.*$ /index.html;
|
||||||
}
|
}
|
||||||
|
location /issues {
|
||||||
|
include fic-auth.conf;
|
||||||
|
|
||||||
|
rewrite ^/.*$ /index.html;
|
||||||
|
}
|
||||||
location /rank {
|
location /rank {
|
||||||
include fic-auth.conf;
|
include fic-auth.conf;
|
||||||
|
|
||||||
|
@ -83,6 +83,11 @@ server {
|
|||||||
|
|
||||||
rewrite ^/.*$ /index.html;
|
rewrite ^/.*$ /index.html;
|
||||||
}
|
}
|
||||||
|
location /issues {
|
||||||
|
include fic-auth.conf;
|
||||||
|
|
||||||
|
rewrite ^/.*$ /index.html;
|
||||||
|
}
|
||||||
location /rank {
|
location /rank {
|
||||||
include fic-auth.conf;
|
include fic-auth.conf;
|
||||||
|
|
||||||
|
@ -75,5 +75,6 @@ func reloadSettings(config settings.FICSettings) {
|
|||||||
|
|
||||||
enableResolutionRoute = config.EnableResolutionRoute
|
enableResolutionRoute = config.EnableResolutionRoute
|
||||||
denyNameChange = config.DenyNameChange
|
denyNameChange = config.DenyNameChange
|
||||||
|
acceptNewIssues = config.AcceptNewIssue
|
||||||
allowRegistration = config.AllowRegistration
|
allowRegistration = config.AllowRegistration
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@
|
|||||||
<a class="nav-link" href="/rank">Classement</a>
|
<a class="nav-link" href="/rank">Classement</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" ng-if="issues.length > 0">
|
<li class="nav-item" ng-if="issues.length > 0">
|
||||||
<a class="nav-link" href="/issue">
|
<a class="nav-link" href="/issues">
|
||||||
Problèmes <span class="badge" ng-class="{'badge-danger': issues_need_info && issues_known_responses != issues_nb_responses,'badge-warning': !issues_need_info && issues_known_responses != issues_nb_responses,'badge-light': issues_known_responses == issues_nb_responses}">{{ issues_nb_responses }}</span>
|
Problèmes <span class="badge" ng-class="{'badge-danger': issues_need_info && issues_known_responses != issues_nb_responses,'badge-warning': !issues_need_info && issues_known_responses != issues_nb_responses,'badge-light': issues_known_responses == issues_nb_responses}">{{ issues_nb_responses }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -57,11 +57,15 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
controller: "MyTeamController",
|
controller: "MyTeamController",
|
||||||
templateUrl: "views/team-edit.html"
|
templateUrl: "views/team-edit.html"
|
||||||
})
|
})
|
||||||
.when("/issue", {
|
.when("/issue/:eid", {
|
||||||
controller: "IssueController",
|
controller: "IssueController",
|
||||||
templateUrl: "views/issue.html"
|
templateUrl: "views/issue.html"
|
||||||
})
|
})
|
||||||
.when("/issue/:eid", {
|
.when("/issues", {
|
||||||
|
controller: "IssueController",
|
||||||
|
templateUrl: "views/issue.html"
|
||||||
|
})
|
||||||
|
.when("/issues/:iid", {
|
||||||
controller: "IssueController",
|
controller: "IssueController",
|
||||||
templateUrl: "views/issue.html"
|
templateUrl: "views/issue.html"
|
||||||
})
|
})
|
||||||
@ -227,10 +231,12 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
refreshIssuesInterval = $interval(refreshIssues, Math.floor(Math.random() * 24000) + 32000);
|
refreshIssuesInterval = $interval(refreshIssues, Math.floor(Math.random() * 24000) + 32000);
|
||||||
|
|
||||||
$http.get("issues.json").then(function(response) {
|
$http.get("issues.json").then(function(response) {
|
||||||
|
$rootScope.issues_idx = {};
|
||||||
$rootScope.issues_nb_responses = 0;
|
$rootScope.issues_nb_responses = 0;
|
||||||
$rootScope.issues_need_info = 0;
|
$rootScope.issues_need_info = 0;
|
||||||
$rootScope.issues = response.data;
|
$rootScope.issues = response.data;
|
||||||
$rootScope.issues.forEach(function(issue) {
|
$rootScope.issues.forEach(function(issue) {
|
||||||
|
$rootScope.issues_idx[issue.id] = issue;
|
||||||
$rootScope.issues_nb_responses += issue.texts.length;
|
$rootScope.issues_nb_responses += issue.texts.length;
|
||||||
if (issue.state == 'need-info') $rootScope.issues_need_info++;
|
if (issue.state == 'need-info') $rootScope.issues_need_info++;
|
||||||
})
|
})
|
||||||
@ -468,6 +474,7 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
}
|
}
|
||||||
refreshMy();
|
refreshMy();
|
||||||
}
|
}
|
||||||
|
$rootScope.refreshIssues = refreshIssues;
|
||||||
$rootScope.refresh();
|
$rootScope.refresh();
|
||||||
})
|
})
|
||||||
.controller("ExerciceController", function($scope, $routeParams, $http, $rootScope, $timeout) {
|
.controller("ExerciceController", function($scope, $routeParams, $http, $rootScope, $timeout) {
|
||||||
@ -669,6 +676,7 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
$rootScope.issues_known_responses = $rootScope.issues_nb_responses;
|
$rootScope.issues_known_responses = $rootScope.issues_nb_responses;
|
||||||
|
|
||||||
$scope.issue = {
|
$scope.issue = {
|
||||||
|
id: parseInt($routeParams.iid, 10),
|
||||||
id_exercice: parseInt($routeParams.eid, 10),
|
id_exercice: parseInt($routeParams.eid, 10),
|
||||||
subject: "",
|
subject: "",
|
||||||
description: "",
|
description: "",
|
||||||
@ -676,9 +684,9 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
|
|
||||||
$scope.isubmit = function() {
|
$scope.isubmit = function() {
|
||||||
$rootScope.sberr = "";
|
$rootScope.sberr = "";
|
||||||
if ($scope.issue.subject.length < 3) {
|
if (!$scope.issue.id && $scope.issue.subject.length < 3) {
|
||||||
$rootScope.messageClass = {"text-danger": true};
|
$rootScope.messageClass = {"text-danger": true};
|
||||||
$rootScope.sberr = "L'object de votre rapport d'anomalie est trop court !";
|
$rootScope.sberr = "L'objet de votre rapport d'anomalie est trop court !";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,6 +699,7 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
$rootScope.message = response.data.errmsg;
|
$rootScope.message = response.data.errmsg;
|
||||||
$scope.issue.subject = "";
|
$scope.issue.subject = "";
|
||||||
$scope.issue.description = "";
|
$scope.issue.description = "";
|
||||||
|
setTimeout($rootScope.refreshIssues, 1750);
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
$rootScope.messageClass = {"text-danger": true};
|
$rootScope.messageClass = {"text-danger": true};
|
||||||
$rootScope.message = response.data.errmsg;
|
$rootScope.message = response.data.errmsg;
|
||||||
|
@ -15,11 +15,14 @@
|
|||||||
<td>{{ issue.state }} / {{ issue.priority }}</td>
|
<td>{{ issue.state }} / {{ issue.priority }}</td>
|
||||||
<td>{{ issue.assignee }}</td>
|
<td>{{ issue.assignee }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="row" ng-repeat="text in issue.texts | orderBy:'date':'reverse'">
|
<p ng-repeat="text in issue.texts | orderBy:'date':'reverse'" style="margin-left: 15px; text-indent: -15px">
|
||||||
<span ng-if="text.assignee == null || text.assignee == '$team'">Vous</span>
|
<span ng-if="text.assignee == null || text.assignee == '$team'">Vous</span>
|
||||||
<span ng-if="text.assignee != null && text.assignee != '$team'" ng-bind="text.assignee"></span> à {{ text.date | date:"mediumTime" }} :
|
<span ng-if="text.assignee != null && text.assignee != '$team'" ng-bind="text.assignee"></span> à {{ text.date | date:"mediumTime" }} :
|
||||||
<span style="white-space: pre-line">{{ text.cnt }}</span>
|
<span style="white-space: pre-line">{{ text.cnt }}</span>
|
||||||
</div>
|
</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a ng-href="/issues/{{ issue.id }}#myIssue" class="btn btn-sm" ng-class="{'btn-danger': issue.state == 'need-info', 'btn-light': issue.state != 'need-info'}"><span class="glyphicon glyphicon-envelope"></span></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -33,8 +36,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border-warning mt-3 mb-4" ng-if="settings.acceptNewIssue">
|
<div class="card border-warning mt-3 mb-4" ng-if="settings.acceptNewIssue" id="myIssue">
|
||||||
<div class="card-header bg-warning text-light">Rapporter une anomalie <span ng-if="issue.id_exercice">sur un exercice</span></div>
|
<div class="card-header bg-warning text-light" ng-if="!issue.id">Rapporter une anomalie <span ng-if="issue.id_exercice">sur un exercice</span></div>
|
||||||
|
<div class="card-header bg-warning text-light" ng-if="issue.id">Répondre à un rapport d'anomalie</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p ng-class="messageClass" ng-if="message || sberr"><strong ng-if="!sberr">Votre rapport a bien été envoyé !</strong><strong ng-if="sberr">{{ sberr }}</strong> {{ message }}</p>
|
<p ng-class="messageClass" ng-if="message || sberr"><strong ng-if="!sberr">Votre rapport a bien été envoyé !</strong><strong ng-if="sberr">{{ sberr }}</strong> {{ message }}</p>
|
||||||
<form ng-submit="isubmit()">
|
<form ng-submit="isubmit()">
|
||||||
@ -49,7 +53,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group row">
|
<div class="form-group row" ng-if="issue.id && issues_idx[issue.id]">
|
||||||
|
<label for="subject" class="col col-form-label">Objet</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" readonly class="form-control-plaintext" id="subject" value="Re: {{ issues_idx[issue.id].subject }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row" ng-if="!issue.id || !issues_idx[issue.id]">
|
||||||
<label for="subject" class="col col-form-label">Objet</label>
|
<label for="subject" class="col col-form-label">Objet</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" id="subject" ng-model="issue.subject" placeholder="Intitulé succinct">
|
<input type="text" class="form-control" id="subject" ng-model="issue.subject" placeholder="Intitulé succinct">
|
||||||
|
@ -2,6 +2,7 @@ package fic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,6 +44,12 @@ func GetClaims() (res []Claim, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClaim retrieves the claim with the given identifier and registered for the given Team.
|
||||||
|
func (t Team) GetClaim(id int64) (c Claim, err error) {
|
||||||
|
err = DBQueryRow("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims WHERE id_claim = ? AND id_team = ?", id, t.Id).Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetClaims returns a list of all Claim registered for the Team.
|
// GetClaims returns a list of all Claim registered for the Team.
|
||||||
func (t Team) GetClaims() (res []Claim, err error) {
|
func (t Team) GetClaims() (res []Claim, err error) {
|
||||||
var rows *sql.Rows
|
var rows *sql.Rows
|
||||||
@ -357,8 +364,10 @@ type teamIssueText struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type teamIssueFile struct {
|
type teamIssueFile struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Exercice *string `json:"exercice,omitempty"`
|
Exercice *string `json:"exercice,omitempty"`
|
||||||
|
ExerciceURL string `json:"url,omitempty"`
|
||||||
Assignee *string `json:"assignee,omitempty"`
|
Assignee *string `json:"assignee,omitempty"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
Priority string `json:"priority"`
|
Priority string `json:"priority"`
|
||||||
@ -370,9 +379,13 @@ func (t Team) MyIssueFile() (ret []teamIssueFile, err error) {
|
|||||||
if claims, err = t.GetClaims(); err == nil {
|
if claims, err = t.GetClaims(); err == nil {
|
||||||
for _, claim := range claims {
|
for _, claim := range claims {
|
||||||
var exercice *string = nil
|
var exercice *string = nil
|
||||||
|
var url string
|
||||||
|
|
||||||
if exo, err := claim.GetExercice(); err == nil && exo != nil {
|
if exo, err := claim.GetExercice(); err == nil && exo != nil {
|
||||||
exercice = &exo.Title
|
exercice = &exo.Title
|
||||||
|
if theme, err := GetTheme(exo.IdTheme); err == nil {
|
||||||
|
url = path.Join(theme.URLId, exo.URLId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var assignee *string = nil
|
var assignee *string = nil
|
||||||
@ -384,8 +397,10 @@ func (t Team) MyIssueFile() (ret []teamIssueFile, err error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
tif := teamIssueFile{
|
tif := teamIssueFile{
|
||||||
|
Id: claim.Id,
|
||||||
Subject: claim.Subject,
|
Subject: claim.Subject,
|
||||||
Exercice: exercice,
|
Exercice: exercice,
|
||||||
|
ExerciceURL: url,
|
||||||
Assignee: assignee,
|
Assignee: assignee,
|
||||||
State: claim.State,
|
State: claim.State,
|
||||||
Priority: claim.Priority,
|
Priority: claim.Priority,
|
||||||
|
Loading…
Reference in New Issue
Block a user