Skip to content
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

fix: fetch activities from /projects #847

Merged
merged 5 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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