You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
9.4 KiB
Go
258 lines
9.4 KiB
Go
package insight
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
BaseApiUrl = "https://jds.projectinsight.net/api/"
|
|
|
|
insightTokenFile = "insight.token"
|
|
|
|
projectEndpoint = ""
|
|
projectListEndpoint = "project/list?ids="
|
|
timeEntryForUserInRangeEndpoint = "time-entry/user-date-range?startDate=2020-01-01&endDate=%s&userId=%s"
|
|
userEndpoint = "user/list-active?modelProperties=Id,FirstName,LastName,EmailAddress"
|
|
)
|
|
|
|
type (
|
|
Interconnect struct {
|
|
Client *IClient
|
|
DBConnector *DBConnector
|
|
}
|
|
|
|
IClient struct {
|
|
client *http.Client
|
|
tokens *map[string]string
|
|
}
|
|
|
|
Project struct {
|
|
BillableExpenseVarianceEarnedValueBillableVsWorkBillableTotalPercent float64 `json:"BillableExpense_VarianceEarnedValueBillableVsWorkBillableTotalPercent,omitempty"`
|
|
CompanyDefaultId string `json:"CompanyDefault_Id,omitempty"`
|
|
CustomFieldValueId string `json:"CustomFieldValue_Id,omitempty"`
|
|
DepartmentId string `json:"Department_Id,omitempty"`
|
|
DurationSeconds int64
|
|
DurationString string
|
|
EndDateTimeUTC string
|
|
EstimateAtCompletionToTargetTotalVariance float64 `json:"EstimateAtCompletionToTargetTotal_Variance,omitempty"`
|
|
EstimateAtCompletionToTargetTotalPercentVariance float64 `json:"EstimateAtCompletionToTargetTotalPercent_Variance,omitempty"`
|
|
IsActive bool
|
|
IsOpen bool
|
|
LocationDefaultId string `json:"LocationDefault_Id,omitempty"`
|
|
Name string
|
|
ProfitSalesOrderActual float64
|
|
ProjectPriorityId string `json:"ProjectPriority_Id,omitempty"`
|
|
ProjectState int64 `json:"project_state,omitempty"`
|
|
ProjectStatusId string `json:"ProjectStatus_Id,omitempty"`
|
|
ProjectTypeId string `json:"ProjectType_Id,omitempty"`
|
|
ScheduleStartDate string
|
|
StartDateTimeUTC string
|
|
UrlFull string
|
|
VarSalesOrderExpenseVsBillableExpense float64
|
|
VarSalesOrderExpenseVsBillableExpensePercent float64
|
|
VarSalesOrderExpenseVsInvoiceExpense float64
|
|
VarSalesOrderExpenseVsInvoiceExpensePercent float64
|
|
VarSalesOrderHoursVsBillableHours float64
|
|
VarSalesOrderHoursVsBillableHoursPercent float64
|
|
VarSalesOrderHoursVsInvoiceHours float64
|
|
VarSalesOrderHoursVsInvoiceHoursPercent float64
|
|
VarSalesOrderRateVsBillableRate float64
|
|
VarSalesOrderRateVsBillableRatePercent float64
|
|
VarSalesOrderRateVsInvoiceRate float64
|
|
VarSalesOrderRateVsInvoiceRatePercent float64
|
|
VarSalesOrderTimeVsBillableTime float64
|
|
VarSalesOrderTimeVsBillableTimePercent float64
|
|
VarSalesOrderTimeVsInvoiceTime float64
|
|
VarSalesOrderTimeVsInvoiceTimePercent float64
|
|
VarSalesOrderTotalVsBillableTotal float64
|
|
VarSalesOrderTotalVsBillableTotalPercent float64
|
|
VarSalesOrderTotalVsInvoiceTotal float64
|
|
VarSalesOrderTotalVsInvoiceTotalPercent float64
|
|
WorkPercentComplete float64
|
|
WorkSeconds int64
|
|
}
|
|
|
|
TimeEntry struct {
|
|
ActualHours float64
|
|
ActualHoursFormattedString string
|
|
ActualTimeString string
|
|
ActualTotal float64
|
|
BillableHours float64
|
|
BillableHoursFormattedString string
|
|
BillableTimeString string
|
|
BillableTotal float64
|
|
Date string
|
|
Description string
|
|
ProjectId string `json:"Project_Id,omitempty"`
|
|
RateBill float64
|
|
RateBurden float64
|
|
TaskId string `json:"Task_Id,omitempty"`
|
|
TimeSheetId string `json:"TimeSheet_Id,omitempty"`
|
|
UserId string `json:"User_Id,omitempty"`
|
|
}
|
|
|
|
User struct {
|
|
Id string
|
|
FirstName string
|
|
LastName string
|
|
EmailAddress string
|
|
}
|
|
)
|
|
|
|
//<editor-fold name="interconnect">
|
|
/*======================================================================================
|
|
interconnect
|
|
======================================================================================*/
|
|
|
|
func NewInsightConnect() *Interconnect {
|
|
connect := Interconnect{}
|
|
connect.Client = NewIClient()
|
|
connect.DBConnector = &DBConnector{}
|
|
return &connect
|
|
}
|
|
|
|
func (ic *Interconnect) ResetTables() {
|
|
ic.DBConnector.CreateTables()
|
|
}
|
|
|
|
func (ic *Interconnect) UpdateUsers() {
|
|
users := ic.Client.GetUsers()
|
|
ic.DBConnector.UpdateUsers(users)
|
|
}
|
|
|
|
func (ic *Interconnect) UpdateTimeEntries() {
|
|
users := ic.DBConnector.FetchUsers()
|
|
for _, userPtr := range *users {
|
|
user := *userPtr
|
|
entries := ic.Client.GetTimeAllTimeEntriesForUserThroughDate(user.Id, "2022-04-28")
|
|
ic.DBConnector.UpdateTimeEntries(entries)
|
|
}
|
|
}
|
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold name="IClient">
|
|
/*======================================================================================
|
|
IClient
|
|
======================================================================================*/
|
|
|
|
func NewIClient() *IClient {
|
|
c := IClient{
|
|
client: &http.Client{},
|
|
tokens: cacheTokens(),
|
|
}
|
|
|
|
return &c
|
|
}
|
|
|
|
func (iClient *IClient) GetTimeAllTimeEntriesForUserThroughDate(userId string, endDate string) *[]*TimeEntry {
|
|
urlString := fmt.Sprintf(timeEntryForUserInRangeEndpoint, endDate, userId)
|
|
log.Println(urlString)
|
|
req := iClient.createRequest(urlString, insightTokenFile)
|
|
rawBytes := iClient.doGet(req)
|
|
container := make([]*TimeEntry, 0, 1000) //do we want to make the default result size parametric?
|
|
err := json.Unmarshal(*rawBytes, &container)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return &container
|
|
}
|
|
|
|
func (iClient *IClient) GetProjectsInList(projectIds []string) *[]Project {
|
|
ids := strings.Join(projectIds, ",")
|
|
req := iClient.createRequest(projectListEndpoint+ids, insightTokenFile)
|
|
rawBytes := iClient.doGet(req)
|
|
container := make([]Project, 0, 1000) //do we want to make the default result size parametric?
|
|
|
|
err := json.Unmarshal(*rawBytes, &container)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return &container
|
|
}
|
|
|
|
func (iClient *IClient) GetUsers() *[]User {
|
|
users := iClient.getRawUsers()
|
|
for idx, user := range *users {
|
|
user.EmailAddress = strings.ToLower(user.EmailAddress)
|
|
user.EmailAddress = strings.ReplaceAll(user.EmailAddress, "jdsdesignonline.com", "jdsconsulting.net")
|
|
user.EmailAddress = strings.ReplaceAll(user.EmailAddress, "jdsfaulkner.com", "jdsconsulting.net")
|
|
(*users)[idx] = user
|
|
}
|
|
return users
|
|
}
|
|
|
|
func (iClient *IClient) getRawUsers() *[]User {
|
|
req := iClient.createRequest(userEndpoint, insightTokenFile)
|
|
rawBytes := iClient.doGet(req)
|
|
container := make([]User, 0, 150) //do we want to make the default result size parametric?
|
|
|
|
err := json.Unmarshal(*rawBytes, &container)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return &container
|
|
}
|
|
|
|
func (iClient *IClient) doGet(req *http.Request) *[]byte {
|
|
resp, err := iClient.client.Do(req)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return &bodyBytes
|
|
}
|
|
|
|
func (iClient *IClient) createRequest(urlEndpoint string, tokenFile string) *http.Request {
|
|
reqUrl := BaseApiUrl + urlEndpoint
|
|
token := (*iClient.tokens)[tokenFile]
|
|
request, err := http.NewRequest("GET", reqUrl, nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
request.Header.Add("api-token", token)
|
|
|
|
return request
|
|
}
|
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold name="utility functions">
|
|
/*======================================================================================
|
|
utility functions
|
|
======================================================================================*/
|
|
|
|
func cacheTokens() *map[string]string {
|
|
files, err := os.ReadDir("./tokens/")
|
|
ret := make(map[string]string)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for _, file := range files {
|
|
subPath := fmt.Sprintf("./tokens/%s", file.Name())
|
|
fileContents, err := os.ReadFile(subPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
ret[file.Name()] = string(fileContents)
|
|
}
|
|
return &ret
|
|
}
|
|
|
|
//</editor-fold>
|