Skip to content

Commit

Permalink
Merge pull request #847 from NBISweden/fix/time-entry-acts
Browse files Browse the repository at this point in the history
fix: fetch activities from /projects
  • Loading branch information
jonandernovella authored Sep 12, 2023
2 parents 295c514 + 562fed1 commit 3123a00
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 48 deletions.
2 changes: 1 addition & 1 deletion backend/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func Setup() *fiber.App {

app.Get("/api/issues", getIssuesHandler)

app.Get("/api/activities", getActivitiesHandler)
app.Get("/api/activities", getProjectActivitiesHandler)

app.Get("/api/priority_entries", getPriorityEntriesHandler)

Expand Down
57 changes: 28 additions & 29 deletions backend/api/getActivitiesHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,26 @@ import (
"sort"
"strconv"
"urdr-api/internal/config"
"urdr-api/internal/redmine"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/proxy"
)

type TimeEntryActivityResponse struct {
TimeEntryActivities []struct {
Id int `json:"id"`
Name string `json:"name"`
IsDefault bool `json:"is_default"`
Active bool `json:"active"`
} `json:"time_entry_activities"`
}

// getActivitiesHandler godoc
// @Summary (Mostly) a proxy for the "/enumerations/time_entry_activities.json" Redmine endpoint
// @Summary Get a list of activities from the Redmine projects endpoint
// @Accept json
// @Produce json
// @Failure 401 {string} error "Unauthorized"
// @Failure 500 {string} error "Internal Server Error"
// @Router /api/activities [get]
// @Param session_id query string false "Issue ID" default(0)
func getActivitiesHandler(c *fiber.Ctx) error {
// @Param project_id query string false "Project ID" default(0)
// @Param issue_id query string false "Issue ID" default(0)
func getProjectActivitiesHandler(c *fiber.Ctx) error {
redmineProjectId, err := strconv.Atoi(c.Query("project_id", "0"))
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
redmineIssueId, err := strconv.Atoi(c.Query("issue_id", "0"))
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
Expand All @@ -38,8 +35,16 @@ func getActivitiesHandler(c *fiber.Ctx) error {
return err
}

redmineURL := fmt.Sprintf("%s/enumerations/time_entry_activities.json",
config.Config.Redmine.URL)
var redmineURL string

// If we don't have a real project ID, return an empty list of activities.
if redmineProjectId == 0 {
emptyListResponse := redmine.ProjectEntry{}
return c.JSON(emptyListResponse)
} else {
redmineURL = fmt.Sprintf("%s/projects/%d.json?include=time_entry_activities",
config.Config.Redmine.URL, redmineProjectId)
}

// Proxy the request to Redmine
if err := proxy.Do(c, redmineURL); err != nil {
Expand All @@ -48,33 +53,27 @@ func getActivitiesHandler(c *fiber.Ctx) error {
return nil
}

activitiesResponse := TimeEntryActivityResponse{}
activitiesResponse := redmine.ProjectEntry{}

if err := json.Unmarshal(c.Response().Body(), &activitiesResponse); err != nil {
c.Response().Reset()
return c.SendStatus(fiber.StatusUnprocessableEntity)
}

// Sort the activities list alphabetically on the name.
sort.Slice(activitiesResponse.TimeEntryActivities, func(i, j int) bool {
return activitiesResponse.TimeEntryActivities[i].Name <
activitiesResponse.TimeEntryActivities[j].Name
sort.Slice(activitiesResponse.Project.TimeEntryActivities, func(i, j int) bool {
return activitiesResponse.Project.TimeEntryActivities[i].Name <
activitiesResponse.Project.TimeEntryActivities[j].Name
})

// Bypass filtering if we don't have a real issue ID.
if redmineIssueId == 0 {
// Return all activities.
return c.JSON(activitiesResponse)
}

filteredActivities := TimeEntryActivityResponse{}
filteredActivities := redmine.ProjectEntry{}

for _, activity := range activitiesResponse.TimeEntryActivities {
for _, activity := range activitiesResponse.Project.TimeEntryActivities {
if db.IsValidEntry(redmineIssueId, activity.Id) {
filteredActivities.TimeEntryActivities =
append(filteredActivities.TimeEntryActivities, activity)
filteredActivities.Project.TimeEntryActivities =
append(filteredActivities.Project.TimeEntryActivities, activity)
}
}

return c.JSON(filteredActivities)
return c.JSON(filteredActivities.Project)
}
2 changes: 1 addition & 1 deletion backend/api/getPriorityEntriesHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func getPriorityEntriesHandler(c *fiber.Ctx) error {
// Now fetch the activities from Redmine and fill out the
// activity names.
c.Response().Reset()
if err := getActivitiesHandler(c); err != nil {
if err := getProjectActivitiesHandler(c); err != nil {
// There was some error in the handler.
return err
} else if c.Response().StatusCode() != fiber.StatusOK {
Expand Down
35 changes: 22 additions & 13 deletions backend/api/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,19 @@ func Test_Handlers(t *testing.T) {

createdEntry, _ := json.Marshal(entryResult)
fetchedEntries, _ := json.Marshal(entriesResult)

entryActs := redmine.TimeEntryActivitiesResult{
TimeEntryActivities: []redmine.TimeEntryActivity{
{
Id: 1,
Name: "Test activity",
IsDefault: true,
projectEntry := redmine.ProjectEntry{
Project: redmine.Project{
TimeEntryActivities: []redmine.TimeEntryActivity{
{
Id: 1,
Name: "test activity",
IsDefault: true,
},
},
},
}

entryActsResponse, _ := json.Marshal(entryActs)
projectActivities, _ := json.Marshal(projectEntry)

issueAct := []api.PriorityEntry{
{
Expand Down Expand Up @@ -179,8 +180,8 @@ func Test_Handlers(t *testing.T) {
}
case "/issues.json":
_, err = w.Write(issuesResponse)
case "/enumerations/time_entry_activities.json":
_, err = w.Write(entryActsResponse)
case "/projects/1.json":
_, err = w.Write(projectActivities)
default:
log.Debugf("%s.\n", endpoint)
_, err = w.Write(nil)
Expand Down Expand Up @@ -315,27 +316,35 @@ func Test_Handlers(t *testing.T) {
{
name: "Entry activities",
method: "GET",
endpoint: "/api/activities",
endpoint: "/api/activities?project_id=1&issue_id=1",
testRedmine: fakeRedmine,
useSessionHeader: true,
statusCode: fiber.StatusOK,
},
{
name: "Entry activities 401",
method: "GET",
endpoint: "/api/activities",
endpoint: "/api/activities?project_id=1&issue_id=1",
testRedmine: fakeRedmine,
useSessionHeader: false,
statusCode: fiber.StatusUnauthorized,
},
{
name: "Entry activities 422",
method: "GET",
endpoint: "/api/activities",
endpoint: "/api/activities?project_id=1&issue_id=1",
testRedmine: badRedmine,
useSessionHeader: true,
statusCode: fiber.StatusUnprocessableEntity,
},
{
name: "Entry activities no params",
method: "GET",
endpoint: "/api/activities",
testRedmine: badRedmine,
useSessionHeader: true,
statusCode: fiber.StatusOK,
},
{
name: "priority_entries POST",
method: "POST",
Expand Down
2 changes: 2 additions & 0 deletions backend/api/postTimeEntriesHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ func postTimeEntriesHandler(c *fiber.Ctx) error {
log.Errorf("proxy.Do() failed: %v\n", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
log.Debugf("respose from redmine: %s", c.Response().Body())

if session, err := store.Get(c); err != nil {

return c.SendStatus(fiber.StatusInternalServerError)
} else {
// Extend the session's expiry time to a week.
Expand Down
9 changes: 9 additions & 0 deletions backend/internal/redmine/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,12 @@ type Group struct {
Id int `json:"id"`
Name string `json:"name"`
}
// The Project type is only ever used for holding the activities that are valid for a given project.
// Apart from that, Urdr mainly deals with Issues, not Projects.
type Project struct {
TimeEntryActivities []TimeEntryActivity `json:"time_entry_activities"`
}

type ProjectEntry struct {
Project Project `json:"project"`
}
6 changes: 3 additions & 3 deletions frontend/src/components/QuickAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ export const QuickAdd = ({

React.useEffect(() => {
let endpoint = "/api/activities";
if (issue) endpoint += "?issue_id=" + issue.id;
if (issue) endpoint += "?project_id=" + (issue.project.id ? issue.project.id : "0") + "&issue_id=" + (issue.id ? issue.id : "0");
let didCancel = false;
const loadActivities = async () => {
let result: { time_entry_activities: IdName[] } = await getApiEndpoint(
endpoint,
context
);
if (!didCancel && result) {
setActivities(result.time_entry_activities);
setActivity(activity ? activity : result.time_entry_activities[0]);
setActivities(result.time_entry_activities ? result.time_entry_activities : []);
setActivity(activity ? activity : (Array.isArray(result.time_entry_activities) && result.time_entry_activities.length > 0) ? result.time_entry_activities[0] : null);
}
};

Expand Down
1 change: 1 addition & 0 deletions frontend/src/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface IssueActivityPair {
export interface Issue {
id: number;
subject: string;
project: IdName;
}

export interface TimeEntry {
Expand Down
7 changes: 6 additions & 1 deletion frontend/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ module.exports = {
historyApiFallback: true,
static: './public',
client: {
webSocketURL: 'ws://localhost:4567/ws'
webSocketURL: 'ws://localhost:4567/ws',
overlay: {
errors: true,
warnings: false,
runtimeErrors: false
}
}
},
plugins: [
Expand Down

0 comments on commit 3123a00

Please sign in to comment.