diff --git a/components/views/incidents.templ b/components/views/incidents.templ index 5e608d8..328bb60 100644 --- a/components/views/incidents.templ +++ b/components/views/incidents.templ @@ -31,7 +31,7 @@ templ IncidentsTable(rows []IncidentRow) {
-

Incidents

+

Incidents

diff --git a/components/views/incidents_templ.go b/components/views/incidents_templ.go index 5d6ddb3..e728f4c 100644 --- a/components/views/incidents_templ.go +++ b/components/views/incidents_templ.go @@ -61,7 +61,7 @@ func IncidentsTable(rows []IncidentRow) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Incidents

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Incidents

IDCreated AtIncident #SysIdDescriptionShort DescriptionUrgencyImpactStateExternal IDWork NotesAssignment GroupAssigned ToCategorySubcategory
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/db/migrations/20250324105016_inc_update.sql b/db/migrations/20250324105016_inc_update.sql new file mode 100644 index 0000000..d866cea --- /dev/null +++ b/db/migrations/20250324105016_inc_update.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE incidents RENAME COLUMN subcategory to sub_category; +ALTER TABLE "Incoming" RENAME COLUMN subcategory to sub_category; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE incidents RENAME COLUMN sub_category to subcategory; +ALTER TABLE "Incoming" RENAME COLUMN sub_category to subcategory; +-- +goose StatementEnd diff --git a/db/queries/models.go b/db/queries/models.go index e62fd85..f4baf6b 100644 --- a/db/queries/models.go +++ b/db/queries/models.go @@ -21,7 +21,7 @@ type Incident struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString SysID sql.NullString } @@ -38,7 +38,7 @@ type Incoming struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString CreatedAt sql.NullTime } diff --git a/db/queries/query.sql b/db/queries/query.sql index 56deb3e..25f8801 100644 --- a/db/queries/query.sql +++ b/db/queries/query.sql @@ -4,7 +4,7 @@ ORDER BY "id"; -- name: CreateIncoming :one INSERT INTO "Incoming" ( - "incident_number", "description", "short_description", "urgency", "impact", "state", "external_id", "work_notes", "assignment_group", "assigned_to", "category", "subcategory", "created_at" + "incident_number", "description", "short_description", "urgency", "impact", "state", "external_id", "work_notes", "assignment_group", "assigned_to", "category", "sub_category", "created_at" ) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) @@ -12,7 +12,7 @@ RETURNING *; -- name: CreateIncident :one INSERT INTO incidents ( - "description", "short_description", "urgency", "impact", "state", "external_id", "assignment_group", "assigned_to", "category", "subcategory", "created_at", "sys_id" + "description", "short_description", "urgency", "impact", "state", "external_id", "assignment_group", "assigned_to", "category", "sub_category", "created_at", "sys_id" ) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) @@ -23,6 +23,11 @@ UPDATE incidents SET incident_number = sqlc.arg('incidentNumber') WHERE external_id = sqlc.arg('externalId'); +-- name: UpdateIncidentNumberSysId :exec +UPDATE incidents +SET incident_number = sqlc.arg('incidentNumber') +WHERE sys_id = sqlc.arg('sysId'); + -- name: UpdateIncidentState :exec UPDATE incidents SET state = sqlc.arg('state') @@ -40,7 +45,7 @@ SET assignment_group = ?, assigned_to = ?, category = ?, - subcategory = ? + sub_category = ? WHERE incident_number = ?; -- name: GetIncident :one @@ -52,7 +57,8 @@ SELECT * FROM incidents; -- name: GetIncidentReport :many SELECT incidents.*, GROUP_CONCAT(worknotes.note, '
') AS all_notes -FROM incidents LEFT JOIN worknotes ON incidents.incident_number = worknotes.incident_number; +FROM incidents LEFT JOIN worknotes ON incidents.incident_number = worknotes.incident_number +GROUP BY incidents.id; -- name: CreateWorkNote :one INSERT into worknotes ( diff --git a/db/queries/query.sql.go b/db/queries/query.sql.go index f1f9bca..5b73dc3 100644 --- a/db/queries/query.sql.go +++ b/db/queries/query.sql.go @@ -12,11 +12,11 @@ import ( const createIncident = `-- name: CreateIncident :one INSERT INTO incidents ( - "description", "short_description", "urgency", "impact", "state", "external_id", "assignment_group", "assigned_to", "category", "subcategory", "created_at", "sys_id" + "description", "short_description", "urgency", "impact", "state", "external_id", "assignment_group", "assigned_to", "category", "sub_category", "created_at", "sys_id" ) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) -RETURNING id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, subcategory, sys_id +RETURNING id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, sub_category, sys_id ` type CreateIncidentParams struct { @@ -29,7 +29,7 @@ type CreateIncidentParams struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString CreatedAt sql.NullTime SysID sql.NullString } @@ -45,7 +45,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams) arg.AssignmentGroup, arg.AssignedTo, arg.Category, - arg.Subcategory, + arg.SubCategory, arg.CreatedAt, arg.SysID, ) @@ -63,7 +63,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams) &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.SysID, ) return i, err @@ -71,11 +71,11 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams) const createIncoming = `-- name: CreateIncoming :one INSERT INTO "Incoming" ( - "incident_number", "description", "short_description", "urgency", "impact", "state", "external_id", "work_notes", "assignment_group", "assigned_to", "category", "subcategory", "created_at" + "incident_number", "description", "short_description", "urgency", "impact", "state", "external_id", "work_notes", "assignment_group", "assigned_to", "category", "sub_category", "created_at" ) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) -RETURNING id, incident_number, description, short_description, urgency, impact, state, external_id, work_notes, assignment_group, assigned_to, category, subcategory, created_at +RETURNING id, incident_number, description, short_description, urgency, impact, state, external_id, work_notes, assignment_group, assigned_to, category, sub_category, created_at ` type CreateIncomingParams struct { @@ -90,7 +90,7 @@ type CreateIncomingParams struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString CreatedAt sql.NullTime } @@ -107,7 +107,7 @@ func (q *Queries) CreateIncoming(ctx context.Context, arg CreateIncomingParams) arg.AssignmentGroup, arg.AssignedTo, arg.Category, - arg.Subcategory, + arg.SubCategory, arg.CreatedAt, ) var i Incoming @@ -124,7 +124,7 @@ func (q *Queries) CreateIncoming(ctx context.Context, arg CreateIncomingParams) &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.CreatedAt, ) return i, err @@ -152,7 +152,7 @@ func (q *Queries) CreateWorkNote(ctx context.Context, arg CreateWorkNoteParams) } const getIncident = `-- name: GetIncident :one -SELECT id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, subcategory, sys_id from incidents +SELECT id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, sub_category, sys_id from incidents WHERE incident_number = ?1 ` @@ -172,15 +172,16 @@ func (q *Queries) GetIncident(ctx context.Context, incidentnumber sql.NullString &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.SysID, ) return i, err } const getIncidentReport = `-- name: GetIncidentReport :many -SELECT incidents.id, incidents.external_id, incidents.created_at, incidents.incident_number, incidents.description, incidents.short_description, incidents.urgency, incidents.impact, incidents.state, incidents.assignment_group, incidents.assigned_to, incidents.category, incidents.subcategory, incidents.sys_id, GROUP_CONCAT(worknotes.note, '
') AS all_notes +SELECT incidents.id, incidents.external_id, incidents.created_at, incidents.incident_number, incidents.description, incidents.short_description, incidents.urgency, incidents.impact, incidents.state, incidents.assignment_group, incidents.assigned_to, incidents.category, incidents.sub_category, incidents.sys_id, GROUP_CONCAT(worknotes.note, '
') AS all_notes FROM incidents LEFT JOIN worknotes ON incidents.incident_number = worknotes.incident_number +GROUP BY incidents.id ` type GetIncidentReportRow struct { @@ -196,7 +197,7 @@ type GetIncidentReportRow struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString SysID sql.NullString AllNotes string } @@ -223,7 +224,7 @@ func (q *Queries) GetIncidentReport(ctx context.Context) ([]GetIncidentReportRow &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.SysID, &i.AllNotes, ); err != nil { @@ -241,7 +242,7 @@ func (q *Queries) GetIncidentReport(ctx context.Context) ([]GetIncidentReportRow } const listIncidents = `-- name: ListIncidents :many -SELECT id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, subcategory, sys_id FROM incidents +SELECT id, external_id, created_at, incident_number, description, short_description, urgency, impact, state, assignment_group, assigned_to, category, sub_category, sys_id FROM incidents ` func (q *Queries) ListIncidents(ctx context.Context) ([]Incident, error) { @@ -266,7 +267,7 @@ func (q *Queries) ListIncidents(ctx context.Context) ([]Incident, error) { &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.SysID, ); err != nil { return nil, err @@ -283,7 +284,7 @@ func (q *Queries) ListIncidents(ctx context.Context) ([]Incident, error) { } const listIncoming = `-- name: ListIncoming :many -SELECT id, incident_number, description, short_description, urgency, impact, state, external_id, work_notes, assignment_group, assigned_to, category, subcategory, created_at FROM "Incoming" +SELECT id, incident_number, description, short_description, urgency, impact, state, external_id, work_notes, assignment_group, assigned_to, category, sub_category, created_at FROM "Incoming" ORDER BY "id" ` @@ -309,7 +310,7 @@ func (q *Queries) ListIncoming(ctx context.Context) ([]Incoming, error) { &i.AssignmentGroup, &i.AssignedTo, &i.Category, - &i.Subcategory, + &i.SubCategory, &i.CreatedAt, ); err != nil { return nil, err @@ -337,7 +338,7 @@ SET assignment_group = ?, assigned_to = ?, category = ?, - subcategory = ? + sub_category = ? WHERE incident_number = ? ` @@ -351,7 +352,7 @@ type UpdateIncidentParams struct { AssignmentGroup sql.NullString AssignedTo sql.NullString Category sql.NullString - Subcategory sql.NullString + SubCategory sql.NullString IncidentNumber sql.NullString } @@ -366,7 +367,7 @@ func (q *Queries) UpdateIncident(ctx context.Context, arg UpdateIncidentParams) arg.AssignmentGroup, arg.AssignedTo, arg.Category, - arg.Subcategory, + arg.SubCategory, arg.IncidentNumber, ) return err @@ -388,6 +389,22 @@ func (q *Queries) UpdateIncidentNumber(ctx context.Context, arg UpdateIncidentNu return err } +const updateIncidentNumberSysId = `-- name: UpdateIncidentNumberSysId :exec +UPDATE incidents +SET incident_number = ?1 +WHERE sys_id = ?2 +` + +type UpdateIncidentNumberSysIdParams struct { + IncidentNumber sql.NullString + SysId sql.NullString +} + +func (q *Queries) UpdateIncidentNumberSysId(ctx context.Context, arg UpdateIncidentNumberSysIdParams) error { + _, err := q.db.ExecContext(ctx, updateIncidentNumberSysId, arg.IncidentNumber, arg.SysId) + return err +} + const updateIncidentState = `-- name: UpdateIncidentState :exec UPDATE incidents SET state = ?1 diff --git a/server/handler/favicon.go b/server/handler/favicon.go new file mode 100644 index 0000000..c1ec2b5 --- /dev/null +++ b/server/handler/favicon.go @@ -0,0 +1,9 @@ +package handler + +import ( + "net/http" +) + +func (h *Handler) Favicon(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) +} diff --git a/server/handler/incidentReport.go b/server/handler/incidentReport.go index 662dec7..34b2d21 100644 --- a/server/handler/incidentReport.go +++ b/server/handler/incidentReport.go @@ -44,7 +44,7 @@ func (h *Handler) RenderIncidentTable(w http.ResponseWriter, r *http.Request) { func ConvertIncidentList(list []queries.GetIncidentReportRow) []views.IncidentRow { rows := make([]views.IncidentRow, 0, len(list)) for _, in := range list { - //prettyPrint(in) + prettyPrint(in) var row views.IncidentRow utils.ConvertStruct(in, &row) rows = append(rows, row) diff --git a/server/handler/newSnow.go b/server/handler/newSnow.go index 6f7630a..d652b40 100644 --- a/server/handler/newSnow.go +++ b/server/handler/newSnow.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "github.com/google/uuid" @@ -57,7 +58,7 @@ func (h *Handler) NewSnow(w http.ResponseWriter, r *http.Request) { AssignmentGroup: nullStr(incoming.AssignmentGroup), AssignedTo: nullStr(incoming.AssignedTo), Category: nullStr(incoming.Category), - Subcategory: nullStr(incoming.SubCategory), + SubCategory: nullStr(incoming.SubCategory), CreatedAt: sql.NullTime{Time: createdTime, Valid: true}, } @@ -99,7 +100,7 @@ func (h *Handler) NewSnow(w http.ResponseWriter, r *http.Request) { AssignmentGroup: nullStr(incoming.AssignmentGroup), AssignedTo: nullStr(incoming.AssignedTo), Category: nullStr(incoming.Category), - Subcategory: nullStr(incoming.SubCategory), + SubCategory: nullStr(incoming.SubCategory), SysID: nullStr(sysID), CreatedAt: sql.NullTime{Time: createdTime, Valid: true}, } @@ -116,11 +117,11 @@ func (h *Handler) NewSnow(w http.ResponseWriter, r *http.Request) { // Use the returned incidentRecordId to generate the ticket number, and update the database correspondingly ticket := fmt.Sprintf("TKT%06d", incidentRecord.ID) - incNumParams := queries.UpdateIncidentNumberParams{ - ExternalId: incoming.ExternalID, + incNumParams := queries.UpdateIncidentNumberSysIdParams{ + SysId: nullStr(sysID), IncidentNumber: nullStr(ticket), } - err = h.Database.Queries().UpdateIncidentNumber(ctx, incNumParams) + err = h.Database.Queries().UpdateIncidentNumberSysId(ctx, incNumParams) if err != nil { h.Logger.Error("failed to update incident with incident number", "number", ticket, "error", err) w.WriteHeader(http.StatusInternalServerError) @@ -173,13 +174,56 @@ func (h *Handler) NewSnow(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) + return } else if len(incoming.IncidentNumber) > 0 { // Incident already exists because we know the incident number, so update status or worknotes h.Logger.Debug("updating incident database record") - // TODO + inc, err := h.Database.Queries().GetIncident(ctx, nullStr(incoming.IncidentNumber)) + if err != nil { + h.Logger.Error("failed to find existing incident to update", "number", incoming.IncidentNumber, "error", err) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error : %v\n", err) + return + } else { + prettyPrint(inc) + } + + updateParams := h.populateChangedFields(inc, incoming) + + err = h.Database.Queries().UpdateIncident(ctx, updateParams) + if err != nil { + h.Logger.Error("failed to update incident", "number", incoming.IncidentNumber, "error", err) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error : %v\n", err) + return + } else { + h.Logger.Debug("updated incident database record", "number", incoming.IncidentNumber) + } + + // TODO - add any worknotes specified + if len(incoming.WorkNotes) > 0 { + h.Logger.Debug("TODO add worknotes") + + // Create the worknotes entry + wnParms := queries.CreateWorkNoteParams{ + IncidentNumber: incoming.IncidentNumber, + Note: nullStr(incoming.WorkNotes), + } + _, err = h.Database.Queries().CreateWorkNote(ctx, wnParms) + if err != nil { + h.Logger.Error("failed to create worknotes for incident with incident number", "number", incoming.IncidentNumber, "error", err) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error : %v\n", err) + return + } else { + h.Logger.Debug("created worknotes database record", "number", incoming.IncidentNumber) + } + } + } else { // Unexpected condition // TODO - return error @@ -215,3 +259,84 @@ func randomImportSet() string { } return string(b) } + +func (h *Handler) populateChangedFields(incRecord queries.Incident, newData models.Incoming) queries.UpdateIncidentParams { + params := queries.UpdateIncidentParams{ + IncidentNumber: incRecord.IncidentNumber, // Always required + ExternalID: incRecord.ExternalID, + } + + if strings.TrimSpace(newData.Description) != strings.TrimSpace(incRecord.Description.String) && len(strings.TrimSpace(newData.Description)) > 0 { + h.Logger.Debug("Updating Description", "new", newData.Description) + params.Description = nullStr(newData.Description) + } else { + params.Description = incRecord.Description + } + if strings.TrimSpace(newData.ShortDescription) != strings.TrimSpace(incRecord.ShortDescription.String) && len(strings.TrimSpace(newData.ShortDescription)) > 0 { + h.Logger.Debug("Updating ShortDescription", "new", newData.ShortDescription) + params.ShortDescription = nullStr(newData.ShortDescription) + } else { + params.ShortDescription = incRecord.ShortDescription + } + + if strings.TrimSpace(newData.Urgency) != strconv.FormatInt(incRecord.Urgency.Int64, 10) { + if n, err := strconv.ParseInt(newData.Urgency, 10, 64); err == nil { + h.Logger.Debug("Updating Urgency", "new", newData.Urgency) + params.Urgency = nullInt64(n) + } else { + params.Urgency = incRecord.Urgency + } + } else { + params.Urgency = incRecord.Urgency + } + if strings.TrimSpace(newData.State) != strconv.FormatInt(incRecord.State.Int64, 10) { + if n, err := strconv.ParseInt(newData.State, 10, 64); err == nil { + h.Logger.Debug("Updating State", "new", newData.State) + params.State = nullInt64(n) + } else { + params.State = incRecord.State + } + } else { + params.State = incRecord.State + } + if strings.TrimSpace(newData.Impact) != strconv.FormatInt(incRecord.Impact.Int64, 10) { + if n, err := strconv.ParseInt(newData.Impact, 10, 64); err == nil { + h.Logger.Debug("Updating Impact", "new", newData.Impact) + params.Impact = nullInt64(n) + } else { + params.Impact = incRecord.Impact + } + } else { + params.Impact = incRecord.Impact + } + + if strings.TrimSpace(newData.AssignmentGroup) != strings.TrimSpace(incRecord.AssignmentGroup.String) && len(strings.TrimSpace(newData.AssignmentGroup)) > 0 { + h.Logger.Debug("Updating AssignmentGroup", "new", newData.AssignmentGroup) + params.AssignmentGroup = nullStr(newData.AssignmentGroup) + } else { + params.AssignmentGroup = incRecord.AssignmentGroup + } + if strings.TrimSpace(newData.AssignedTo) != strings.TrimSpace(incRecord.AssignedTo.String) && len(strings.TrimSpace(newData.AssignedTo)) > 0 { + h.Logger.Debug("Updating AssignedTo", "new", newData.AssignedTo) + params.AssignedTo = nullStr(newData.AssignedTo) + } else { + params.AssignedTo = incRecord.AssignedTo + } + if strings.TrimSpace(newData.Category) != strings.TrimSpace(incRecord.Category.String) && len(strings.TrimSpace(newData.Category)) > 0 { + h.Logger.Debug("Updating Category", "new", newData.Category) + params.Category = nullStr(newData.Category) + } else { + params.Category = incRecord.Category + } + if strings.TrimSpace(newData.SubCategory) != strings.TrimSpace(incRecord.SubCategory.String) && len(strings.TrimSpace(newData.SubCategory)) > 0 { + h.Logger.Debug("Updating SubCategory", "new", newData.SubCategory) + params.Category = nullStr(newData.SubCategory) + } else { + params.SubCategory = incRecord.SubCategory + } + // TODO + + h.Logger.Debug("populateChangedFields returning", "params", params) + + return params +} diff --git a/server/router/router.go b/server/router/router.go index 0f7936f..f1b8ef6 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -33,6 +33,8 @@ func New(logger *slog.Logger, database db.Database, buildTime string, sha1ver st mux.HandleFunc("/api/print", h.RenderIncomingTable) mux.HandleFunc("/api/print/incidents", h.RenderIncidentTable) + mux.HandleFunc("/favicon.ico", h.Favicon) + // TODO - fallback route that will just echo incoming payload mux.HandleFunc("/", h.Fallback)
IDCreated AtIncident #SysIdDescriptionShort DescriptionUrgencyImpactStateExternal IDWork NotesAssignment GroupAssigned ToCategorySubcategory