Skip to content

Commit

Permalink
Refactor storyboard story, goal, and column sortings to use fractiona…
Browse files Browse the repository at this point in the history
…l indexes
  • Loading branch information
StevenWeathers committed Sep 25, 2024
1 parent caa7f1d commit 8f841bd
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 46 deletions.
6 changes: 3 additions & 3 deletions docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12177,7 +12177,7 @@ const docTemplate = `{
}
},
"sort_order": {
"type": "integer"
"type": "string"
},
"stories": {
"type": "array",
Expand Down Expand Up @@ -12209,7 +12209,7 @@ const docTemplate = `{
}
},
"sort_order": {
"type": "integer"
"type": "string"
}
}
},
Expand Down Expand Up @@ -12267,7 +12267,7 @@ const docTemplate = `{
"type": "integer"
},
"sort_order": {
"type": "integer"
"type": "string"
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -12169,7 +12169,7 @@
}
},
"sort_order": {
"type": "integer"
"type": "string"
},
"stories": {
"type": "array",
Expand Down Expand Up @@ -12201,7 +12201,7 @@
}
},
"sort_order": {
"type": "integer"
"type": "string"
}
}
},
Expand Down Expand Up @@ -12259,7 +12259,7 @@
"type": "integer"
},
"sort_order": {
"type": "integer"
"type": "string"
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ definitions:
$ref: '#/definitions/thunderdome.StoryboardPersona'
type: array
sort_order:
type: integer
type: string
stories:
items:
$ref: '#/definitions/thunderdome.StoryboardStory'
Expand All @@ -1343,7 +1343,7 @@ definitions:
$ref: '#/definitions/thunderdome.StoryboardPersona'
type: array
sort_order:
type: integer
type: string
type: object
thunderdome.StoryboardPersona:
properties:
Expand Down Expand Up @@ -1381,7 +1381,7 @@ definitions:
points:
type: integer
sort_order:
type: integer
type: string
type: object
thunderdome.StoryboardUser:
properties:
Expand Down
162 changes: 162 additions & 0 deletions internal/db/migrations/20240925122944_update_storyboard_sorting.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
-- +goose Up
-- +goose StatementBegin

-- Storyboard Story
ALTER TABLE thunderdome.storyboard_story ADD COLUMN display_order text COLLATE "C";
UPDATE thunderdome.storyboard_story SET display_order = 'a' || sort_order::text;
ALTER TABLE thunderdome.storyboard_story DROP CONSTRAINT storyboard_story_column_id_sort_order_key;
ALTER TABLE thunderdome.storyboard_story DROP COLUMN sort_order;
ALTER TABLE thunderdome.storyboard_story ADD CONSTRAINT storyboard_story_column_id_display_order_key UNIQUE (column_id, display_order);

-- Storyboard Column
ALTER TABLE thunderdome.storyboard_column ADD COLUMN display_order text COLLATE "C";
UPDATE thunderdome.storyboard_column SET display_order = 'a' || sort_order::text;
ALTER TABLE thunderdome.storyboard_column DROP CONSTRAINT storyboard_column_goal_id_sort_order_key;
ALTER TABLE thunderdome.storyboard_column DROP COLUMN sort_order;
ALTER TABLE thunderdome.storyboard_column ADD CONSTRAINT storyboard_column_goal_id_display_order_key UNIQUE (goal_id, display_order);

-- Storyboard Goal
ALTER TABLE thunderdome.storyboard_goal ADD COLUMN display_order text COLLATE "C";
UPDATE thunderdome.storyboard_goal SET display_order = 'a' || sort_order::text;
ALTER TABLE thunderdome.storyboard_goal DROP CONSTRAINT storyboard_goal_storyboard_id_sort_order_key;
ALTER TABLE thunderdome.storyboard_goal DROP COLUMN sort_order;
ALTER TABLE thunderdome.storyboard_goal ADD CONSTRAINT storyboard_goal_storyboard_id_display_order_key UNIQUE (storyboard_id, display_order);

DROP PROCEDURE "thunderdome".sb_column_delete(IN columnid uuid);
DROP PROCEDURE "thunderdome".sb_goal_delete(IN goalid uuid);
DROP PROCEDURE "thunderdome".sb_story_delete(IN storyid uuid);
DROP PROCEDURE "thunderdome".sb_story_move(IN storyid uuid, IN goalid uuid, IN columnid uuid, IN placebefore text);

-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin

-- Storyboard Story
ALTER TABLE thunderdome.storyboard_story ADD COLUMN sort_order int4;
UPDATE thunderdome.storyboard_story SET sort_order = substring(display_order, 2)::int4;
ALTER TABLE thunderdome.storyboard_story DROP CONSTRAINT storyboard_story_column_id_display_order_key;
ALTER TABLE thunderdome.storyboard_story DROP COLUMN display_order;
ALTER TABLE thunderdome.storyboard_story ADD CONSTRAINT storyboard_story_column_id_sort_order_key UNIQUE (column_id, sort_order);

-- Storyboard Column
ALTER TABLE thunderdome.storyboard_column ADD COLUMN sort_order int4;
UPDATE thunderdome.storyboard_column SET sort_order = substring(display_order, 2)::int4;
ALTER TABLE thunderdome.storyboard_column DROP CONSTRAINT storyboard_column_goal_id_display_order_key;
ALTER TABLE thunderdome.storyboard_column DROP COLUMN display_order;
ALTER TABLE thunderdome.storyboard_column ADD CONSTRAINT storyboard_column_goal_id_sort_order_key UNIQUE (goal_id, sort_order);

-- Storyboard Goal
ALTER TABLE thunderdome.storyboard_goal ADD COLUMN sort_order int4;
UPDATE thunderdome.storyboard_goal SET sort_order = substring(display_order, 2)::int4;
ALTER TABLE thunderdome.storyboard_goal DROP CONSTRAINT storyboard_goal_storyboard_id_display_order_key;
ALTER TABLE thunderdome.storyboard_goal DROP COLUMN display_order;
ALTER TABLE thunderdome.storyboard_goal ADD CONSTRAINT storyboard_goal_storyboard_id_sort_order_key UNIQUE (storyboard_id, sort_order);

CREATE OR REPLACE PROCEDURE thunderdome.sb_story_move(IN storyid uuid, IN goalid uuid, IN columnid uuid, IN placebefore text)
LANGUAGE plpgsql
AS $procedure$
DECLARE storyboardId UUID;
DECLARE srcGoalId UUID;
DECLARE srcColumnId UUID;
DECLARE srcSortOrder INTEGER;
DECLARE targetSortOrder INTEGER;
BEGIN
SET CONSTRAINTS thunderdome.storyboard_story_column_id_sort_order_key DEFERRED;
-- Get Story current details
SELECT
storyboard_id, goal_id, column_id, sort_order, name, color, content, created_date
INTO
storyboardId, srcGoalId, srcColumnId, srcSortOrder
FROM thunderdome.storyboard_story WHERE id = storyId;

-- Get target sort order
IF placeBefore = '' THEN
SELECT coalesce(max(sort_order), 0) + 1 INTO targetSortOrder FROM thunderdome.storyboard_story WHERE column_id = columnId;
ELSE
SELECT sort_order INTO targetSortOrder FROM thunderdome.storyboard_story WHERE column_id = columnId AND id = placeBefore::UUID;
END IF;

-- Remove from source column
UPDATE thunderdome.storyboard_story SET column_id = columnId, sort_order = 9000 WHERE id = storyId;
-- Update sort order in src column
UPDATE thunderdome.storyboard_story ss SET sort_order = (t.sort_order - 1)
FROM (
SELECT id, sort_order FROM thunderdome.storyboard_story
WHERE column_id = srcColumnId AND sort_order > srcSortOrder
ORDER BY sort_order ASC
FOR UPDATE
) AS t
WHERE ss.id = t.id;

-- Update sort order for any story that should come after newly moved story
UPDATE thunderdome.storyboard_story ss SET sort_order = (t.sort_order + 1)
FROM (
SELECT id, sort_order FROM thunderdome.storyboard_story
WHERE column_id = columnId AND sort_order >= targetSortOrder
ORDER BY sort_order DESC
FOR UPDATE
) AS t
WHERE ss.id = t.id;

-- Finally, insert story in its ordered place
UPDATE thunderdome.storyboard_story SET sort_order = targetSortOrder WHERE id = storyId;

COMMIT;
END;
$procedure$;

CREATE OR REPLACE PROCEDURE thunderdome.sb_story_delete(IN storyid uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE columnId UUID;
DECLARE sortOrder INTEGER;
DECLARE storyboardId UUID;
BEGIN
SELECT column_id, sort_order, storyboard_id INTO columnId, sortOrder, storyboardId
FROM thunderdome.storyboard_story WHERE id = storyId;
DELETE FROM thunderdome.storyboard_story WHERE id = storyId;
UPDATE thunderdome.storyboard_story ss SET sort_order = (ss.sort_order - 1)
WHERE ss.column_id = columnId AND ss.sort_order > sortOrder;

COMMIT;
END;
$procedure$;

CREATE OR REPLACE PROCEDURE thunderdome.sb_goal_delete(IN goalid uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE storyboardId UUID;
DECLARE sortOrder INTEGER;
BEGIN
SELECT sort_order, storyboard_id INTO sortOrder, storyboardId FROM thunderdome.storyboard_goal WHERE id = goalId;

DELETE FROM thunderdome.storyboard_story WHERE goal_id = goalId;
DELETE FROM thunderdome.storyboard_column WHERE goal_id = goalId;
DELETE FROM thunderdome.storyboard_goal WHERE id = goalId;
UPDATE thunderdome.storyboard_goal sg SET sort_order = (sg.sort_order - 1)
WHERE sg.storyboard_id = storyBoardId AND sg.sort_order > sortOrder;

COMMIT;
END;
$procedure$;

CREATE OR REPLACE PROCEDURE thunderdome.sb_column_delete(IN columnid uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE goalId UUID;
DECLARE sortOrder INTEGER;
DECLARE storyboardId UUID;
BEGIN
SELECT goal_id, sort_order INTO goalId, sortOrder FROM thunderdome.storyboard_column WHERE id = columnId;

DELETE FROM thunderdome.storyboard_story WHERE column_id = columnId;
DELETE FROM thunderdome.storyboard_column WHERE id = columnId RETURNING storyboard_id INTO storyboardId;
UPDATE thunderdome.storyboard_column sc SET sort_order = (sc.sort_order - 1)
WHERE sc.goal_id = goalId AND sc.sort_order > sortOrder;

COMMIT;
END;
$procedure$;

-- +goose StatementEnd
75 changes: 68 additions & 7 deletions internal/db/storyboard/column.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,79 @@
package storyboard

import (
"context"
"errors"
"fmt"

"github.com/StevenWeathers/thunderdome-planning-poker/internal/fracindex"
"github.com/StevenWeathers/thunderdome-planning-poker/thunderdome"
"go.uber.org/zap"
)

// CreateStoryboardColumn adds a new column to a Storyboard
func (d *Service) CreateStoryboardColumn(StoryboardID string, GoalID string, userID string) ([]*thunderdome.StoryboardGoal, error) {
if _, err := d.DB.Exec(
`INSERT INTO thunderdome.storyboard_column (storyboard_id, goal_id, sort_order)
VALUES ($1, $2, ((SELECT coalesce(MAX(sort_order), 0) FROM thunderdome.storyboard_column WHERE goal_id = $2) + 1));`,
var betweenAkey *string
var logger = d.Logger.With(
zap.String("user_id", userID),
zap.String("storyboard_id", StoryboardID),
zap.String("goal_id", GoalID),
)

tx, err := d.DB.BeginTx(context.Background(), nil)
if err != nil {
logger.Error("begin transaction error", zap.Error(err))
return nil, err
}
defer tx.Rollback()

if err := tx.QueryRow(
`
SELECT
COALESCE(
(SELECT MAX(display_order)
FROM thunderdome.storyboard_column
WHERE storyboard_id = $1 AND goal_id = $2),
'a0'
) AS last_display_order;`,
StoryboardID, GoalID,
).Scan(&betweenAkey); err != nil {
logger.Error("get display_order between query error",
zap.Error(err),
)
return nil, err
}

displayOrder, err := fracindex.KeyBetween(betweenAkey, nil)
if err != nil {
logger.Error("get display_order between error",
zap.Error(err),
zap.Stringp("display_order_a", betweenAkey),
)
return nil, err
}

if displayOrder == nil {
logger.Error("get display_order returned nil",
zap.Stringp("display_order_a", betweenAkey),
)
return nil, errors.New("display order is nil")
}

if _, err := tx.Exec(
`INSERT INTO thunderdome.storyboard_column (storyboard_id, goal_id, display_order)
VALUES ($1, $2, $3);`,
StoryboardID, GoalID, displayOrder,
); err != nil {
d.Logger.Error("CreateStoryboardColumn error", zap.Error(err))
logger.Error("CreateStoryboardColumn error",
zap.Error(err),
zap.Stringp("display_order", displayOrder),
)
return nil, err
}

if commitErr := tx.Commit(); commitErr != nil {
logger.Error("update drivers: unable to commit", zap.Error(commitErr))
return nil, fmt.Errorf("failed to update storyboard story display_order: %v", commitErr)
}

goals := d.GetStoryboardGoals(StoryboardID)
Expand All @@ -27,7 +88,7 @@ func (d *Service) ReviseStoryboardColumn(StoryboardID string, UserID string, Col
ColumnID,
ColumnName,
); err != nil {
d.Logger.Error("ReviseStoryboardColumn error", zap.Error(err))
d.Logger.Error("revise storyboard column error", zap.Error(err))
}

goals := d.GetStoryboardGoals(StoryboardID)
Expand All @@ -38,8 +99,8 @@ func (d *Service) ReviseStoryboardColumn(StoryboardID string, UserID string, Col
// DeleteStoryboardColumn removes a column from the current board by ID
func (d *Service) DeleteStoryboardColumn(StoryboardID string, userID string, ColumnID string) ([]*thunderdome.StoryboardGoal, error) {
if _, err := d.DB.Exec(
`CALL thunderdome.sb_column_delete($1);`, ColumnID); err != nil {
d.Logger.Error("CALL thunderdome.sb_column_delete error", zap.Error(err))
`DELETE FROM thunderdome.storyboard_column WHERE id = $1;`, ColumnID); err != nil {
d.Logger.Error("delete storyboard column error", zap.Error(err))
}

goals := d.GetStoryboardGoals(StoryboardID)
Expand Down
Loading

0 comments on commit 8f841bd

Please sign in to comment.